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  package org.apache.commons.crypto.stream;
19  
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.lang.reflect.Method;
23  import java.nio.ByteBuffer;
24  import java.nio.channels.ReadableByteChannel;
25  import java.security.InvalidAlgorithmParameterException;
26  import java.security.InvalidKeyException;
27  import java.security.Key;
28  import java.security.spec.AlgorithmParameterSpec;
29  import java.util.Properties;
30  import javax.crypto.BadPaddingException;
31  import javax.crypto.Cipher;
32  import javax.crypto.IllegalBlockSizeException;
33  import javax.crypto.ShortBufferException;
34  import javax.crypto.spec.IvParameterSpec;
35  
36  import org.apache.commons.crypto.Crypto;
37  import org.apache.commons.crypto.cipher.CryptoCipher;
38  import org.apache.commons.crypto.stream.input.ChannelInput;
39  import org.apache.commons.crypto.stream.input.Input;
40  import org.apache.commons.crypto.stream.input.StreamInput;
41  import org.apache.commons.crypto.utils.Utils;
42  
43  /**
44   * CryptoInputStream reads input data and decrypts data in stream manner. It
45   * supports any mode of operations such as AES CBC/CTR/GCM mode in concept.It is
46   * not thread-safe.
47   *
48   */
49  
50  public class CryptoInputStream extends InputStream implements
51          ReadableByteChannel {
52      private final byte[] oneByteBuf = new byte[1];
53  
54      /**
55       * The configuration key of the buffer size for stream.
56       */
57      public static final String STREAM_BUFFER_SIZE_KEY = Crypto.CONF_PREFIX
58              + "stream.buffer.size";
59  
60      /** The CryptoCipher instance. */
61      final CryptoCipher cipher;
62  
63      /** The buffer size. */
64      final int bufferSize;
65  
66      /** Crypto key for the cipher. */
67      final Key key;
68  
69      /** the algorithm parameters */
70      final AlgorithmParameterSpec params;
71  
72      /** Flag to mark whether the input stream is closed. */
73      private boolean closed;
74  
75      /**
76       * Flag to mark whether do final of the cipher to end the decrypting stream.
77       */
78      private boolean finalDone = false;
79  
80      /** The input data. */
81      Input input;
82  
83      /**
84       * Input data buffer. The data starts at inBuffer.position() and ends at to
85       * inBuffer.limit().
86       */
87      ByteBuffer inBuffer;
88  
89      /**
90       * The decrypted data buffer. The data starts at outBuffer.position() and
91       * ends at outBuffer.limit().
92       */
93      ByteBuffer outBuffer;
94  
95      // stream related configuration keys
96      /**
97       * The default value of the buffer size for stream.
98       */
99      private static final int STREAM_BUFFER_SIZE_DEFAULT = 8192;
100 
101     private static final int MIN_BUFFER_SIZE = 512;
102 
103     /**
104      * Constructs a {@link CryptoInputStream}.
105      *
106      * @param transformation the name of the transformation, e.g.,
107      * <i>AES/CBC/PKCS5Padding</i>.
108      * See the Java Cryptography Architecture Standard Algorithm Name Documentation
109      * for information about standard transformation names.
110      * @param props The <code>Properties</code> class represents a set of
111      *        properties.
112      * @param in the input stream.
113      * @param key crypto key for the cipher.
114      * @param params the algorithm parameters.
115      * @throws IOException if an I/O error occurs.
116      */
117     public CryptoInputStream(String transformation,
118             Properties props, InputStream in, Key key,
119             AlgorithmParameterSpec params) throws IOException {
120         this(in, Utils.getCipherInstance(transformation, props),
121                 CryptoInputStream.getBufferSize(props), key, params);
122     }
123 
124     /**
125      * Constructs a {@link CryptoInputStream}.
126      *
127      * @param transformation the name of the transformation, e.g.,
128      * <i>AES/CBC/PKCS5Padding</i>.
129      * See the Java Cryptography Architecture Standard Algorithm Name Documentation
130      * for information about standard transformation names.
131      * @param props The <code>Properties</code> class represents a set of
132      *        properties.
133      * @param in the ReadableByteChannel object.
134      * @param key crypto key for the cipher.
135      * @param params the algorithm parameters.
136      * @throws IOException if an I/O error occurs.
137      */
138     public CryptoInputStream(String transformation,
139             Properties props, ReadableByteChannel in, Key key,
140             AlgorithmParameterSpec params) throws IOException {
141         this(in, Utils.getCipherInstance(transformation, props), CryptoInputStream
142                 .getBufferSize(props), key, params);
143     }
144 
145     /**
146      * Constructs a {@link CryptoInputStream}.
147      *
148      * @param cipher the cipher instance.
149      * @param in the input stream.
150      * @param bufferSize the bufferSize.
151      * @param key crypto key for the cipher.
152      * @param params the algorithm parameters.
153      * @throws IOException if an I/O error occurs.
154      */
155     protected CryptoInputStream(InputStream in, CryptoCipher cipher,
156             int bufferSize, Key key, AlgorithmParameterSpec params)
157             throws IOException {
158         this(new StreamInput(in, bufferSize), cipher, bufferSize, key, params);
159     }
160 
161     /**
162      * Constructs a {@link CryptoInputStream}.
163      *
164      * @param in the ReadableByteChannel instance.
165      * @param cipher the cipher instance.
166      * @param bufferSize the bufferSize.
167      * @param key crypto key for the cipher.
168      * @param params the algorithm parameters.
169      * @throws IOException if an I/O error occurs.
170      */
171     protected CryptoInputStream(ReadableByteChannel in, CryptoCipher cipher,
172             int bufferSize, Key key, AlgorithmParameterSpec params)
173             throws IOException {
174         this(new ChannelInput(in), cipher, bufferSize, key, params);
175     }
176 
177     /**
178      * Constructs a {@link CryptoInputStream}.
179      *
180      * @param input the input data.
181      * @param cipher the cipher instance.
182      * @param bufferSize the bufferSize.
183      * @param key crypto key for the cipher.
184      * @param params the algorithm parameters.
185      * @throws IOException if an I/O error occurs.
186      */
187     protected CryptoInputStream(Input input, CryptoCipher cipher, int bufferSize,
188             Key key, AlgorithmParameterSpec params) throws IOException {
189         this.input = input;
190         this.cipher = cipher;
191         this.bufferSize = CryptoInputStream.checkBufferSize(cipher, bufferSize);
192 
193         this.key = key;
194         this.params = params;
195         if (!(params instanceof IvParameterSpec)) {
196             // other AlgorithmParameterSpec such as GCMParameterSpec is not
197             // supported now.
198             throw new IOException("Illegal parameters");
199         }
200 
201         inBuffer = ByteBuffer.allocateDirect(this.bufferSize);
202         outBuffer = ByteBuffer.allocateDirect(this.bufferSize
203                 + cipher.getBlockSize());
204         outBuffer.limit(0);
205 
206         initCipher();
207     }
208 
209     /**
210      * Overrides the {@link java.io.InputStream#read()}. Reads the next byte of
211      * data from the input stream.
212      *
213      * @return the next byte of data, or <code>-1</code> if the end of the
214      *         stream is reached.
215      * @throws IOException if an I/O error occurs.
216      */
217     @Override
218     public int read() throws IOException {
219         int n;
220         while ((n = read(oneByteBuf, 0, 1)) == 0) { //NOPMD
221             /* no op */
222         }
223         return (n == -1) ? -1 : oneByteBuf[0] & 0xff;
224     }
225 
226     /**
227      * Overrides the {@link java.io.InputStream#read(byte[], int, int)}.
228      * Decryption is buffer based. If there is data in {@link #outBuffer}, then
229      * read it out of this buffer. If there is no data in {@link #outBuffer},
230      * then read more from the underlying stream and do the decryption.
231      *
232      * @param b the buffer into which the decrypted data is read.
233      * @param off the buffer offset.
234      * @param len the maximum number of decrypted data bytes to read.
235      * @return int the total number of decrypted data bytes read into the
236      *         buffer.
237      * @throws IOException if an I/O error occurs.
238      */
239     @Override
240     public int read(byte[] b, int off, int len) throws IOException {
241         checkStream();
242         if (b == null) {
243             throw new NullPointerException();
244         } else if (off < 0 || len < 0 || len > b.length - off) {
245             throw new IndexOutOfBoundsException();
246         } else if (len == 0) {
247             return 0;
248         }
249 
250         int remaining = outBuffer.remaining();
251         if (remaining > 0) {
252             // Satisfy the read with the existing data
253             int n = Math.min(len, remaining);
254             outBuffer.get(b, off, n);
255             return n;
256         }
257         // No data in the out buffer, try read new data and decrypt it
258         int nd = decryptMore();
259         if (nd <= 0) {
260             return nd;
261         }
262 
263         int n = Math.min(len, outBuffer.remaining());
264         outBuffer.get(b, off, n);
265         return n;
266     }
267 
268     /**
269      * Overrides the {@link java.io.InputStream#skip(long)}. Skips over and
270      * discards <code>n</code> bytes of data from this input stream.
271      *
272      * @param n the number of bytes to be skipped.
273      * @return the actual number of bytes skipped.
274      * @throws IOException if an I/O error occurs.
275      */
276     @Override
277     public long skip(long n) throws IOException {
278         Utils.checkArgument(n >= 0, "Negative skip length.");
279         checkStream();
280 
281         if (n == 0) {
282             return 0;
283         }
284 
285         long remaining = n;
286         int nd;
287 
288         while (remaining > 0) {
289             if (remaining <= outBuffer.remaining()) {
290                 // Skip in the remaining buffer
291                 int pos = outBuffer.position() + (int) remaining;
292                 outBuffer.position(pos);
293 
294                 remaining = 0;
295                 break;
296             }
297             remaining -= outBuffer.remaining();
298             outBuffer.clear();
299 
300             nd = decryptMore();
301             if (nd < 0) {
302                 break;
303             }
304         }
305 
306         return n - remaining;
307     }
308 
309     /**
310      * Overrides the {@link InputStream#available()}. Returns an estimate of the
311      * number of bytes that can be read (or skipped over) from this input stream
312      * without blocking by the next invocation of a method for this input
313      * stream.
314      *
315      * @return an estimate of the number of bytes that can be read (or skipped
316      *         over) from this input stream without blocking or {@code 0} when
317      *         it reaches the end of the input stream.
318      * @throws IOException if an I/O error occurs.
319      */
320     @Override
321     public int available() throws IOException {
322         checkStream();
323 
324         return input.available() + outBuffer.remaining();
325     }
326 
327     /**
328      * Overrides the {@link InputStream#close()}. Closes this input stream and
329      * releases any system resources associated with the stream.
330      *
331      * @throws IOException if an I/O error occurs.
332      */
333     @Override
334     public void close() throws IOException {
335         if (closed) {
336             return;
337         }
338 
339         input.close();
340         freeBuffers();
341         cipher.close();
342         super.close();
343         closed = true;
344     }
345 
346     /**
347      * Overrides the {@link java.io.InputStream#mark(int)}. For
348      * {@link CryptoInputStream},we don't support the mark method.
349      *
350      * @param readlimit the maximum limit of bytes that can be read before the
351      *        mark position becomes invalid.
352      */
353     @Override
354     public void mark(int readlimit) {
355     }
356 
357     /**
358      * Overrides the {@link InputStream#reset()}. For {@link CryptoInputStream}
359      * ,we don't support the reset method.
360      *
361      * @throws IOException if an I/O error occurs.
362      */
363     @Override
364     public void reset() throws IOException {
365         throw new IOException("Mark/reset not supported");
366     }
367 
368     /**
369      * Overrides the {@link InputStream#markSupported()}.
370      *
371      * @return false,the {@link CtrCryptoInputStream} don't support the mark
372      *         method.
373      */
374     @Override
375     public boolean markSupported() {
376         return false;
377     }
378 
379     /**
380      * Overrides the {@link java.nio.channels.Channel#isOpen()}.
381      *
382      * @return <tt>true</tt> if, and only if, this channel is open.
383      */
384     @Override
385     public boolean isOpen() {
386         return !closed;
387     }
388 
389     /**
390      * Overrides the
391      * {@link java.nio.channels.ReadableByteChannel#read(ByteBuffer)}. Reads a
392      * sequence of bytes from this channel into the given buffer.
393      *
394      * @param dst The buffer into which bytes are to be transferred.
395      * @return The number of bytes read, possibly zero, or <tt>-1</tt> if the
396      *         channel has reached end-of-stream.
397      * @throws IOException if an I/O error occurs.
398      */
399     @Override
400     public int read(ByteBuffer dst) throws IOException {
401         checkStream();
402         int remaining = outBuffer.remaining();
403         if (remaining <= 0) {
404             // Decrypt more data
405             int nd = decryptMore();
406             if (nd < 0) {
407                 return -1;
408             }
409         }
410 
411         // Copy decrypted data from outBuffer to dst
412         remaining = outBuffer.remaining();
413         final int toRead = dst.remaining();
414         if (toRead <= remaining) {
415             final int limit = outBuffer.limit();
416             outBuffer.limit(outBuffer.position() + toRead);
417             dst.put(outBuffer);
418             outBuffer.limit(limit);
419             return toRead;
420         }
421         dst.put(outBuffer);
422         return remaining;
423     }
424 
425     /**
426      * Gets the buffer size.
427      *
428      * @return the bufferSize.
429      */
430     protected int getBufferSize() {
431         return bufferSize;
432     }
433 
434     /**
435      * Gets the key.
436      *
437      * @return the key.
438      */
439     protected Key getKey() {
440         return key;
441     }
442 
443     /**
444      * Gets the internal CryptoCipher.
445      *
446      * @return the cipher instance.
447      */
448     protected CryptoCipher getCipher() {
449         return cipher;
450     }
451 
452     /**
453      * Gets the specification of cryptographic parameters.
454      *
455      * @return the params.
456      */
457     protected AlgorithmParameterSpec getParams() {
458         return params;
459     }
460 
461     /**
462      * Gets the input.
463      *
464      * @return the input.
465      */
466     protected Input getInput() {
467         return input;
468     }
469 
470     /**
471      * Initializes the cipher.
472      *
473      * @throws IOException if an I/O error occurs.
474      */
475     protected void initCipher() throws IOException {
476         try {
477             cipher.init(Cipher.DECRYPT_MODE, key, params);
478         } catch (InvalidKeyException e) {
479             throw new IOException(e);
480         } catch (InvalidAlgorithmParameterException e) {
481             throw new IOException(e);
482         }
483     }
484 
485     /**
486      * Decrypts more data by reading the under layer stream. The decrypted data
487      * will be put in the output buffer. If the end of the under stream reached,
488      * we will do final of the cipher to finish all the decrypting of data.
489      *
490      * @return The number of decrypted data. -1 if end of the decrypted stream.
491      * @throws IOException if an I/O error occurs.
492      */
493     protected int decryptMore() throws IOException {
494         if (finalDone) {
495             return -1;
496         }
497 
498         int n = input.read(inBuffer);
499         if (n < 0) {
500             // The stream is end, finalize the cipher stream
501             decryptFinal();
502 
503             // Satisfy the read with the remaining
504             int remaining = outBuffer.remaining();
505             if (remaining > 0) {
506                 return remaining;
507             }
508 
509             // End of the stream
510             return -1;
511         } else if (n == 0) {
512             // No data is read, but the stream is not end yet
513             return 0;
514         } else {
515             decrypt();
516             return outBuffer.remaining();
517         }
518     }
519 
520     /**
521      * Does the decryption using inBuffer as input and outBuffer as output. Upon
522      * return, inBuffer is cleared; the decrypted data starts at
523      * outBuffer.position() and ends at outBuffer.limit().
524      *
525      * @throws IOException if an I/O error occurs.
526      */
527     protected void decrypt() throws IOException {
528         // Prepare the input buffer and clear the out buffer
529         inBuffer.flip();
530         outBuffer.clear();
531 
532         try {
533             cipher.update(inBuffer, outBuffer);
534         } catch (ShortBufferException e) {
535             throw new IOException(e);
536         }
537 
538         // Clear the input buffer and prepare out buffer
539         inBuffer.clear();
540         outBuffer.flip();
541     }
542 
543     /**
544      * Does final of the cipher to end the decrypting stream.
545      *
546      * @throws IOException if an I/O error occurs.
547      */
548     protected void decryptFinal() throws IOException {
549         // Prepare the input buffer and clear the out buffer
550         inBuffer.flip();
551         outBuffer.clear();
552 
553         try {
554             cipher.doFinal(inBuffer, outBuffer);
555             finalDone = true;
556         } catch (ShortBufferException e) {
557             throw new IOException(e);
558         } catch (IllegalBlockSizeException e) {
559             throw new IOException(e);
560         } catch (BadPaddingException e) {
561             throw new IOException(e);
562         }
563 
564         // Clear the input buffer and prepare out buffer
565         inBuffer.clear();
566         outBuffer.flip();
567     }
568 
569     /**
570      * Checks whether the stream is closed.
571      *
572      * @throws IOException if an I/O error occurs.
573      */
574     protected void checkStream() throws IOException {
575         if (closed) {
576             throw new IOException("Stream closed");
577         }
578     }
579 
580     /** Forcibly free the direct buffers. */
581     protected void freeBuffers() {
582         CryptoInputStream.freeDirectBuffer(inBuffer);
583         CryptoInputStream.freeDirectBuffer(outBuffer);
584     }
585 
586     /**
587      * Forcibly free the direct buffer.
588      *
589      * @param buffer the bytebuffer to be freed.
590      */
591     static void freeDirectBuffer(ByteBuffer buffer) {
592         try {
593             /* Using reflection to implement sun.nio.ch.DirectBuffer.cleaner()
594             .clean(); */
595             final String SUN_CLASS = "sun.nio.ch.DirectBuffer";
596             Class<?>[] interfaces = buffer.getClass().getInterfaces();
597     
598             for (Class<?> clazz : interfaces) {
599                 if (clazz.getName().equals(SUN_CLASS)) {
600                     final Object[] NO_PARAM = new Object[0];
601                     /* DirectBuffer#cleaner() */
602                     Method getCleaner = Class.forName(SUN_CLASS).getMethod("cleaner");
603                     Object cleaner = getCleaner.invoke(buffer, NO_PARAM);
604                     /* Cleaner#clean() */
605                     Method cleanMethod = Class.forName("sun.misc.Cleaner").getMethod("clean");
606                     cleanMethod.invoke(cleaner, NO_PARAM);
607                     return;
608                 }
609             }
610         } catch (ReflectiveOperationException e) { // NOPMD
611             // Ignore the Reflection exception.
612         }
613     }
614 
615     /**
616      * Reads crypto buffer size.
617      *
618      * @param props The <code>Properties</code> class represents a set of
619      *        properties.
620      * @return the buffer size.
621      * */
622     static int getBufferSize(Properties props) {
623         String bufferSizeStr = props.getProperty(CryptoInputStream.STREAM_BUFFER_SIZE_KEY);
624         if (bufferSizeStr == null || bufferSizeStr.isEmpty()) {
625             return CryptoInputStream.STREAM_BUFFER_SIZE_DEFAULT;
626         }
627         return Integer.parseInt(bufferSizeStr);
628     }
629 
630     /**
631      * Checks whether the cipher is supported streaming.
632      *
633      * @param cipher the {@link CryptoCipher} instance.
634      * @throws IOException if an I/O error occurs.
635      */
636     static void checkStreamCipher(CryptoCipher cipher)
637             throws IOException {
638         if (!cipher.getAlgorithm().equals("AES/CTR/NoPadding")) {
639             throw new IOException("AES/CTR/NoPadding is required");
640         }
641     }
642 
643     /**
644      * Checks and floors buffer size.
645      *
646      * @param cipher the {@link CryptoCipher} instance.
647      * @param bufferSize the buffer size.
648      * @return the remaining buffer size.
649      */
650     static int checkBufferSize(CryptoCipher cipher, int bufferSize) {
651         Utils.checkArgument(bufferSize >= CryptoInputStream.MIN_BUFFER_SIZE,
652                 "Minimum value of buffer size is " + CryptoInputStream.MIN_BUFFER_SIZE + ".");
653         return bufferSize - bufferSize
654                 % cipher.getBlockSize();
655     }
656 }