View Javadoc
1    /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.commons.crypto.stream;
20  
21  import java.io.IOException;
22  import java.io.OutputStream;
23  import java.nio.ByteBuffer;
24  import java.nio.channels.WritableByteChannel;
25  import java.security.GeneralSecurityException;
26  import java.security.Key;
27  import java.security.spec.AlgorithmParameterSpec;
28  import java.util.Objects;
29  import java.util.Properties;
30  
31  import javax.crypto.Cipher;
32  import javax.crypto.ShortBufferException;
33  import javax.crypto.spec.IvParameterSpec;
34  
35  import org.apache.commons.crypto.cipher.CryptoCipher;
36  import org.apache.commons.crypto.stream.output.ChannelOutput;
37  import org.apache.commons.crypto.stream.output.Output;
38  import org.apache.commons.crypto.stream.output.StreamOutput;
39  import org.apache.commons.crypto.utils.Utils;
40  
41  /**
42   * {@link CryptoOutputStream} encrypts data and writes to the under layer
43   * output. It supports any mode of operations such as AES CBC/CTR/GCM mode in
44   * concept. It is not thread-safe.
45   * <p>
46   * This class should only be used with blocking sinks. Using this class to wrap
47   * a non-blocking sink may lead to high CPU usage.
48   * </p>
49   */
50  
51  public class CryptoOutputStream extends OutputStream implements
52          WritableByteChannel {
53      private final byte[] oneByteBuf = new byte[1];
54  
55      /** The output. */
56      final Output output; // package protected for access by rypto classes; do not expose further
57  
58      /** the CryptoCipher instance */
59      final CryptoCipher cipher; // package protected for access by crypto classes; do not expose further
60  
61      /** The buffer size. */
62      private final int bufferSize;
63  
64      /** Crypto key for the cipher. */
65      final Key key; // package protected for access by crypto classes; do not expose further
66  
67      /** the algorithm parameters */
68      private final AlgorithmParameterSpec params;
69  
70      /** Flag to mark whether the output stream is closed. */
71      private boolean closed;
72  
73      /**
74       * Input data buffer. The data starts at inBuffer.position() and ends at
75       * inBuffer.limit().
76       */
77      ByteBuffer inBuffer; // package protected for access by crypto classes; do not expose further
78  
79      /**
80       * Encrypted data buffer. The data starts at outBuffer.position() and ends
81       * at outBuffer.limit().
82       */
83      ByteBuffer outBuffer; // package protected for access by crypto classes; do not expose further
84  
85      /**
86       * Constructs a {@link CryptoOutputStream}.
87       *
88       * @param output the output stream.
89       * @param cipher the CryptoCipher instance.
90       * @param bufferSize the bufferSize.
91       * @param key crypto key for the cipher.
92       * @param params the algorithm parameters.
93       * @throws IOException if an I/O error occurs.
94       */
95      protected CryptoOutputStream(final Output output, final CryptoCipher cipher,
96              final int bufferSize, final Key key, final AlgorithmParameterSpec params)
97              throws IOException {
98  
99          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 }