001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.commons.crypto.stream;
020
021import java.io.IOException;
022import java.io.OutputStream;
023import java.nio.ByteBuffer;
024import java.nio.channels.WritableByteChannel;
025import java.security.GeneralSecurityException;
026import java.security.Key;
027import java.security.spec.AlgorithmParameterSpec;
028import java.util.Objects;
029import java.util.Properties;
030
031import javax.crypto.Cipher;
032import javax.crypto.ShortBufferException;
033import javax.crypto.spec.IvParameterSpec;
034
035import org.apache.commons.crypto.cipher.CryptoCipher;
036import org.apache.commons.crypto.stream.output.ChannelOutput;
037import org.apache.commons.crypto.stream.output.Output;
038import org.apache.commons.crypto.stream.output.StreamOutput;
039import org.apache.commons.crypto.utils.Utils;
040
041/**
042 * {@link CryptoOutputStream} encrypts data and writes to the under layer
043 * output. It supports any mode of operations such as AES CBC/CTR/GCM mode in
044 * concept. It is not thread-safe.
045 * <p>
046 * This class should only be used with blocking sinks. Using this class to wrap
047 * a non-blocking sink may lead to high CPU usage.
048 * </p>
049 */
050
051public class CryptoOutputStream extends OutputStream implements
052        WritableByteChannel {
053    private final byte[] oneByteBuf = new byte[1];
054
055    /** The output. */
056    final Output output; // package protected for access by rypto classes; do not expose further
057
058    /** the CryptoCipher instance */
059    final CryptoCipher cipher; // package protected for access by crypto classes; do not expose further
060
061    /** The buffer size. */
062    private final int bufferSize;
063
064    /** Crypto key for the cipher. */
065    final Key key; // package protected for access by crypto classes; do not expose further
066
067    /** the algorithm parameters */
068    private final AlgorithmParameterSpec params;
069
070    /** Flag to mark whether the output stream is closed. */
071    private boolean closed;
072
073    /**
074     * Input data buffer. The data starts at inBuffer.position() and ends at
075     * inBuffer.limit().
076     */
077    ByteBuffer inBuffer; // package protected for access by crypto classes; do not expose further
078
079    /**
080     * Encrypted data buffer. The data starts at outBuffer.position() and ends
081     * at outBuffer.limit().
082     */
083    ByteBuffer outBuffer; // package protected for access by crypto classes; do not expose further
084
085    /**
086     * Constructs a {@link CryptoOutputStream}.
087     *
088     * @param output the output stream.
089     * @param cipher the CryptoCipher instance.
090     * @param bufferSize the bufferSize.
091     * @param key crypto key for the cipher.
092     * @param params the algorithm parameters.
093     * @throws IOException if an I/O error occurs.
094     */
095    protected CryptoOutputStream(final Output output, final CryptoCipher cipher,
096            final int bufferSize, final Key key, final AlgorithmParameterSpec params)
097            throws IOException {
098
099        this.output = output;
100        this.bufferSize = CryptoInputStream.checkBufferSize(cipher, bufferSize);
101        this.cipher = cipher;
102
103        this.key = key;
104        this.params = params;
105
106        if (!(params instanceof IvParameterSpec)) {
107            // other AlgorithmParameterSpec such as GCMParameterSpec is not
108            // supported now.
109            throw new IOException("Illegal parameters");
110        }
111
112        inBuffer = ByteBuffer.allocateDirect(this.bufferSize);
113        outBuffer = ByteBuffer.allocateDirect(this.bufferSize
114                + cipher.getBlockSize());
115
116        initCipher();
117    }
118
119    /**
120     * Constructs a {@link CryptoOutputStream}.
121     *
122     * @param outputStream the output stream.
123     * @param cipher the CryptoCipher instance.
124     * @param bufferSize the bufferSize.
125     * @param key crypto key for the cipher.
126     * @param params the algorithm parameters.
127     * @throws IOException if an I/O error occurs.
128     */
129    @SuppressWarnings("resource") // Closing the instance closes the StreamOutput
130    protected CryptoOutputStream(final OutputStream outputStream, final CryptoCipher cipher,
131            final int bufferSize, final Key key, final AlgorithmParameterSpec params)
132            throws IOException {
133        this(new StreamOutput(outputStream, bufferSize), cipher, bufferSize, key, params);
134    }
135
136    /**
137     * Constructs a {@link CryptoOutputStream}.
138     *
139     * @param transformation the name of the transformation, e.g.,
140     * <i>AES/CBC/PKCS5Padding</i>.
141     * See the Java Cryptography Architecture Standard Algorithm Name Documentation
142     * for information about standard transformation names.
143     * @param properties The {@code Properties} class represents a set of
144     *        properties.
145     * @param outputStream the output stream.
146     * @param key crypto key for the cipher.
147     * @param params the algorithm parameters.
148     * @throws IOException if an I/O error occurs.
149     */
150    @SuppressWarnings("resource") // The CryptoCipher returned by getCipherInstance() is closed by CryptoOutputStream.
151    public CryptoOutputStream(final String transformation,
152            final Properties properties, final OutputStream outputStream, final Key key,
153            final AlgorithmParameterSpec params) throws IOException {
154        this(outputStream, Utils.getCipherInstance(transformation, properties),
155                CryptoInputStream.getBufferSize(properties), key, params);
156
157    }
158
159    /**
160     * Constructs a {@link CryptoOutputStream}.
161     *
162     * @param transformation the name of the transformation, e.g.,
163     * <i>AES/CBC/PKCS5Padding</i>.
164     * See the Java Cryptography Architecture Standard Algorithm Name Documentation
165     * for information about standard transformation names.
166     * @param properties The {@code Properties} class represents a set of
167     *        properties.
168     * @param out the WritableByteChannel instance.
169     * @param key crypto key for the cipher.
170     * @param params the algorithm parameters.
171     * @throws IOException if an I/O error occurs.
172     */
173    @SuppressWarnings("resource") // The CryptoCipher returned by getCipherInstance() is closed by CryptoOutputStream.
174    public CryptoOutputStream(final String transformation,
175            final Properties properties, final WritableByteChannel out, final Key key,
176            final AlgorithmParameterSpec params) throws IOException {
177        this(out, Utils.getCipherInstance(transformation, properties), CryptoInputStream
178                .getBufferSize(properties), key, params);
179
180    }
181
182    /**
183     * Constructs a {@link CryptoOutputStream}.
184     *
185     * @param channel the WritableByteChannel instance.
186     * @param cipher the cipher instance.
187     * @param bufferSize the bufferSize.
188     * @param key crypto key for the cipher.
189     * @param params the algorithm parameters.
190     * @throws IOException if an I/O error occurs.
191     */
192    @SuppressWarnings("resource") // Closing the instance closes the ChannelOutput
193    protected CryptoOutputStream(final WritableByteChannel channel, final CryptoCipher cipher,
194            final int bufferSize, final Key key, final AlgorithmParameterSpec params)
195            throws IOException {
196        this(new ChannelOutput(channel), cipher, bufferSize, key, params);
197    }
198
199    /**
200     * Checks whether the stream is closed.
201     *
202     * @throws IOException if an I/O error occurs.
203     */
204    protected void checkStream() throws IOException {
205        if (closed) {
206            throw new IOException("Stream closed");
207        }
208    }
209
210    /**
211     * Overrides the {@link OutputStream#close()}. Closes this output stream and
212     * releases any system resources associated with this stream.
213     *
214     * @throws IOException if an I/O error occurs.
215     */
216    @Override
217    public void close() throws IOException {
218        if (closed) {
219            return;
220        }
221
222        try {
223            encryptFinal();
224            output.close();
225            freeBuffers();
226            cipher.close();
227            super.close();
228        } finally {
229            closed = true;
230        }
231    }
232
233    /**
234     * Does the encryption, input is {@link #inBuffer} and output is
235     * {@link #outBuffer}.
236     *
237     * @throws IOException if an I/O error occurs.
238     */
239    protected void encrypt() throws IOException {
240
241        inBuffer.flip();
242        outBuffer.clear();
243
244        try {
245            cipher.update(inBuffer, outBuffer);
246        } catch (final ShortBufferException e) {
247            throw new IOException(e);
248        }
249
250        inBuffer.clear();
251        outBuffer.flip();
252
253        // write to output
254        while (outBuffer.hasRemaining()) {
255            output.write(outBuffer);
256        }
257    }
258
259    /**
260     * Does final encryption of the last data.
261     *
262     * @throws IOException if an I/O error occurs.
263     */
264    protected void encryptFinal() throws IOException {
265        inBuffer.flip();
266        outBuffer.clear();
267
268        try {
269            cipher.doFinal(inBuffer, outBuffer);
270        } catch (final GeneralSecurityException e) {
271            throw new IOException(e);
272        }
273
274        inBuffer.clear();
275        outBuffer.flip();
276
277        // write to output
278        while (outBuffer.hasRemaining()) {
279            output.write(outBuffer);
280        }
281    }
282
283    /**
284     * Overrides the {@link OutputStream#flush()}. To flush, we need to encrypt
285     * the data in the buffer and write to the underlying stream, then do the
286     * flush.
287     *
288     * @throws IOException if an I/O error occurs.
289     */
290    @Override
291    public void flush() throws IOException {
292        checkStream();
293        encrypt();
294        output.flush();
295        super.flush();
296    }
297
298    /** Forcibly free the direct buffers. */
299    protected void freeBuffers() {
300        CryptoInputStream.freeDirectBuffer(inBuffer);
301        CryptoInputStream.freeDirectBuffer(outBuffer);
302    }
303
304    /**
305     * Gets the buffer size.
306     *
307     * @return the buffer size.
308     */
309    protected int getBufferSize() {
310        return bufferSize;
311    }
312
313    /**
314     * Gets the internal Cipher.
315     *
316     * @return the cipher instance.
317     */
318    protected CryptoCipher getCipher() {
319        return cipher;
320    }
321
322    /**
323     * Gets the inBuffer.
324     *
325     * @return the inBuffer.
326     */
327    protected ByteBuffer getInBuffer() {
328        return inBuffer;
329    }
330
331    /**
332     * Gets the outBuffer.
333     *
334     * @return the outBuffer.
335     */
336    protected ByteBuffer getOutBuffer() {
337        return outBuffer;
338    }
339
340    /**
341     * Initializes the cipher.
342     *
343     * @throws IOException if an I/O error occurs.
344     */
345    protected void initCipher() throws IOException {
346        try {
347            cipher.init(Cipher.ENCRYPT_MODE, key, params);
348        } catch (final GeneralSecurityException e) {
349            throw new IOException(e);
350        }
351    }
352
353    /**
354     * Overrides the {@link java.nio.channels.Channel#isOpen()}. Tells whether or not this channel
355     * is open.
356     *
357     * @return {@code true} if, and only if, this channel is open
358     */
359    @Override
360    public boolean isOpen() {
361        return !closed;
362    }
363
364    /**
365     * Overrides the {@link java.io.OutputStream#write(byte[], int, int)}.
366     * Encryption is buffer based. If there is enough room in {@link #inBuffer},
367     * then write to this buffer. If {@link #inBuffer} is full, then do
368     * encryption and write data to the underlying stream.
369     *
370     * @param array the data.
371     * @param off the start offset in the data.
372     * @param len the number of bytes to write.
373     * @throws IOException if an I/O error occurs.
374     */
375    @Override
376    public void write(final byte[] array, int off, int len) throws IOException {
377        checkStream();
378        Objects.requireNonNull(array, "array");
379        final int arrayLength = array.length;
380        if (off < 0 || len < 0 || off > arrayLength || len > arrayLength - off) {
381            throw new IndexOutOfBoundsException();
382        }
383
384        while (len > 0) {
385            final int remaining = inBuffer.remaining();
386            if (len < remaining) {
387                inBuffer.put(array, off, len);
388                len = 0;
389            } else {
390                inBuffer.put(array, off, remaining);
391                off += remaining;
392                len -= remaining;
393                encrypt();
394            }
395        }
396    }
397
398    /**
399     * Overrides the
400     * {@link java.nio.channels.WritableByteChannel#write(ByteBuffer)}. Writes a
401     * sequence of bytes to this channel from the given buffer.
402     *
403     * @param src The buffer from which bytes are to be retrieved.
404     * @return The number of bytes written, possibly zero.
405     * @throws IOException if an I/O error occurs.
406     */
407    @Override
408    public int write(final ByteBuffer src) throws IOException {
409        checkStream();
410        final int len = src.remaining();
411        int remaining = len;
412        while (remaining > 0) {
413            final int space = inBuffer.remaining();
414            if (remaining < space) {
415                inBuffer.put(src);
416                remaining = 0;
417            } else {
418                // to void copy twice, we set the limit to copy directly
419                final int oldLimit = src.limit();
420                final int newLimit = src.position() + space;
421                src.limit(newLimit);
422
423                inBuffer.put(src);
424
425                // restore the old limit
426                src.limit(oldLimit);
427
428                remaining -= space;
429                encrypt();
430            }
431        }
432
433        return len;
434    }
435
436    /**
437     * Overrides the {@link java.io.OutputStream#write(byte[])}. Writes the
438     * specified byte to this output stream.
439     *
440     * @param b the data.
441     * @throws IOException if an I/O error occurs.
442     */
443    @Override
444    public void write(final int b) throws IOException {
445        oneByteBuf[0] = (byte) (b & 0xff);
446        write(oneByteBuf, 0, oneByteBuf.length);
447    }
448}