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