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.nio.ByteBuffer;
23  import java.nio.channels.ReadableByteChannel;
24  import java.security.InvalidAlgorithmParameterException;
25  import java.security.InvalidKeyException;
26  import java.util.Properties;
27  import javax.crypto.BadPaddingException;
28  import javax.crypto.Cipher;
29  import javax.crypto.IllegalBlockSizeException;
30  import javax.crypto.ShortBufferException;
31  import javax.crypto.spec.IvParameterSpec;
32  import javax.crypto.spec.SecretKeySpec;
33  
34  import org.apache.commons.crypto.cipher.CryptoCipher;
35  import org.apache.commons.crypto.cipher.CryptoCipherFactory;
36  import org.apache.commons.crypto.stream.input.ChannelInput;
37  import org.apache.commons.crypto.stream.input.Input;
38  import org.apache.commons.crypto.stream.input.StreamInput;
39  import org.apache.commons.crypto.utils.Utils;
40  
41  /**
42   * <p>
43   * CtrCryptoInputStream decrypts data. AES CTR mode is required in order to
44   * ensure that the plain text and cipher text have a 1:1 mapping. CTR crypto
45   * stream has stream characteristic which is useful for implement features like
46   * random seek. The decryption is buffer based. The key points of the decryption
47   * are (1) calculating the counter and (2) padding through stream position:
48   * </p>
49   * <p>
50   * counter = base + pos/(algorithm blocksize); padding = pos%(algorithm
51   * blocksize);
52   * </p>
53   * The underlying stream offset is maintained as state. It is not thread-safe.
54   */
55  public class CtrCryptoInputStream extends CryptoInputStream {
56      /**
57       * Underlying stream offset
58       */
59      private long streamOffset = 0;
60  
61      /**
62       * The initial IV.
63       */
64      private final byte[] initIV;
65  
66      /**
67       * Initialization vector for the cipher.
68       */
69      private byte[] iv;
70  
71      /**
72       * Padding = pos%(algorithm blocksize); Padding is put into
73       * {@link #inBuffer} before any other data goes in. The purpose of padding
74       * is to put the input data at proper position.
75       */
76      private byte padding;
77  
78      /**
79       * Flag to mark whether the cipher has been reset
80       */
81      private boolean cipherReset = false;
82  
83      /**
84       * Constructs a {@link CtrCryptoInputStream}.
85       *
86       * @param props The <code>Properties</code> class represents a set of
87       *        properties.
88       * @param in the input stream.
89       * @param key crypto key for the cipher.
90       * @param iv Initialization vector for the cipher.
91       * @throws IOException if an I/O error occurs.
92       */
93      public CtrCryptoInputStream(Properties props, InputStream in, byte[] key,
94              byte[] iv) throws IOException {
95          this(props, in, key, iv, 0);
96      }
97  
98      /**
99       * Constructs a {@link CtrCryptoInputStream}.
100      *
101      * @param props The <code>Properties</code> class represents a set of
102      *        properties.
103      * @param in the ReadableByteChannel instance.
104      * @param key crypto key for the cipher.
105      * @param iv Initialization vector for the cipher.
106      * @throws IOException if an I/O error occurs.
107      */
108     public CtrCryptoInputStream(Properties props, ReadableByteChannel in,
109             byte[] key, byte[] iv) throws IOException {
110         this(props, in, key, iv, 0);
111     }
112 
113     /**
114      * Constructs a {@link CtrCryptoInputStream}.
115      *
116      * @param in the input stream.
117      * @param cipher the CryptoCipher instance.
118      * @param bufferSize the bufferSize.
119      * @param key crypto key for the cipher.
120      * @param iv Initialization vector for the cipher.
121      * @throws IOException if an I/O error occurs.
122      */
123     protected CtrCryptoInputStream(InputStream in, CryptoCipher cipher,
124             int bufferSize, byte[] key, byte[] iv) throws IOException {
125         this(in, cipher, bufferSize, key, iv, 0);
126     }
127 
128     /**
129      * Constructs a {@link CtrCryptoInputStream}.
130      *
131      * @param in the ReadableByteChannel instance.
132      * @param cipher the cipher instance.
133      * @param bufferSize the bufferSize.
134      * @param key crypto key for the cipher.
135      * @param iv Initialization vector for the cipher.
136      * @throws IOException if an I/O error occurs.
137      */
138     protected CtrCryptoInputStream(ReadableByteChannel in, CryptoCipher cipher,
139             int bufferSize, byte[] key, byte[] iv) throws IOException {
140         this(in, cipher, bufferSize, key, iv, 0);
141     }
142 
143     /**
144      * Constructs a {@link CtrCryptoInputStream}.
145      *
146      * @param input the input data.
147      * @param cipher the CryptoCipher instance.
148      * @param bufferSize the bufferSize.
149      * @param key crypto key for the cipher.
150      * @param iv Initialization vector for the cipher.
151      * @throws IOException if an I/O error occurs.
152      */
153     protected CtrCryptoInputStream(Input input, CryptoCipher cipher,
154             int bufferSize, byte[] key, byte[] iv) throws IOException {
155         this(input, cipher, bufferSize, key, iv, 0);
156     }
157 
158     /**
159      * Constructs a {@link CtrCryptoInputStream}.
160      *
161      * @param props The <code>Properties</code> class represents a set of
162      *        properties.
163      * @param in the InputStream instance.
164      * @param key crypto key for the cipher.
165      * @param iv Initialization vector for the cipher.
166      * @param streamOffset the start offset in the stream.
167      * @throws IOException if an I/O error occurs.
168      */
169     public CtrCryptoInputStream(Properties props, InputStream in, byte[] key,
170             byte[] iv, long streamOffset) throws IOException {
171         this(in, Utils.getCipherInstance(
172                 "AES/CTR/NoPadding", props),
173                 CryptoInputStream.getBufferSize(props), key, iv, streamOffset);
174     }
175 
176     /**
177      * Constructs a {@link CtrCryptoInputStream}.
178      *
179      * @param props The <code>Properties</code> class represents a set of
180      *        properties.
181      * @param in the ReadableByteChannel instance.
182      * @param key crypto key for the cipher.
183      * @param iv Initialization vector for the cipher.
184      * @param streamOffset the start offset in the stream.
185      * @throws IOException if an I/O error occurs.
186      */
187     public CtrCryptoInputStream(Properties props, ReadableByteChannel in,
188             byte[] key, byte[] iv, long streamOffset) throws IOException {
189         this(in, Utils.getCipherInstance(
190                 "AES/CTR/NoPadding", props),
191                 CryptoInputStream.getBufferSize(props), key, iv, streamOffset);
192     }
193 
194     /**
195      * Constructs a {@link CtrCryptoInputStream}.
196      *
197      * @param in the InputStream instance.
198      * @param cipher the CryptoCipher instance.
199      * @param bufferSize the bufferSize.
200      * @param key crypto key for the cipher.
201      * @param iv Initialization vector for the cipher.
202      * @param streamOffset the start offset in the stream.
203      * @throws IOException if an I/O error occurs.
204      */
205     protected CtrCryptoInputStream(InputStream in, CryptoCipher cipher,
206             int bufferSize, byte[] key, byte[] iv, long streamOffset)
207             throws IOException {
208         this(new StreamInput(in, bufferSize), cipher, bufferSize, key, iv,
209                 streamOffset);
210     }
211 
212     /**
213      * Constructs a {@link CtrCryptoInputStream}.
214      *
215      * @param in the ReadableByteChannel instance.
216      * @param cipher the CryptoCipher instance.
217      * @param bufferSize the bufferSize.
218      * @param key crypto key for the cipher.
219      * @param iv Initialization vector for the cipher.
220      * @param streamOffset the start offset in the stream.
221      * @throws IOException if an I/O error occurs.
222      */
223     protected CtrCryptoInputStream(ReadableByteChannel in, CryptoCipher cipher,
224             int bufferSize, byte[] key, byte[] iv, long streamOffset)
225             throws IOException {
226         this(new ChannelInput(in), cipher, bufferSize, key, iv, streamOffset);
227     }
228 
229     /**
230      * Constructs a {@link CtrCryptoInputStream}.
231      *
232      * @param input the input data.
233      * @param cipher the CryptoCipher instance.
234      * @param bufferSize the bufferSize.
235      * @param key crypto key for the cipher.
236      * @param iv Initialization vector for the cipher.
237      * @param streamOffset the start offset in the stream.
238      * @throws IOException if an I/O error occurs.
239      */
240     protected CtrCryptoInputStream(Input input, CryptoCipher cipher,
241             int bufferSize, byte[] key, byte[] iv, long streamOffset)
242             throws IOException {
243         super(input, cipher, bufferSize, new SecretKeySpec(key, "AES"),
244                 new IvParameterSpec(iv));
245 
246         this.initIV = iv.clone();
247         this.iv = iv.clone();
248 
249         CryptoInputStream.checkStreamCipher(cipher);
250 
251         resetStreamOffset(streamOffset);
252     }
253 
254     /**
255      * Overrides the {@link CryptoInputStream#skip(long)}. Skips over and
256      * discards <code>n</code> bytes of data from this input stream.
257      *
258      * @param n the number of bytes to be skipped.
259      * @return the actual number of bytes skipped.
260      * @throws IOException if an I/O error occurs.
261      */
262     @Override
263     public long skip(long n) throws IOException {
264         Utils.checkArgument(n >= 0, "Negative skip length.");
265         checkStream();
266 
267         if (n == 0) {
268             return 0;
269         } else if (n <= outBuffer.remaining()) {
270             int pos = outBuffer.position() + (int) n;
271             outBuffer.position(pos);
272             return n;
273         } else {
274             /*
275              * Subtract outBuffer.remaining() to see how many bytes we need to
276              * skip in the underlying stream. Add outBuffer.remaining() to the
277              * actual number of skipped bytes in the underlying stream to get
278              * the number of skipped bytes from the user's point of view.
279              */
280             n -= outBuffer.remaining();
281             long skipped = input.skip(n);
282             if (skipped < 0) {
283                 skipped = 0;
284             }
285             long pos = streamOffset + skipped;
286             skipped += outBuffer.remaining();
287             resetStreamOffset(pos);
288             return skipped;
289         }
290     }
291 
292     /**
293      * Overrides the {@link CtrCryptoInputStream#read(ByteBuffer)}. Reads a
294      * sequence of bytes from this channel into the given buffer.
295      *
296      * @param buf The buffer into which bytes are to be transferred.
297      * @return The number of bytes read, possibly zero, or <tt>-1</tt> if the
298      *         channel has reached end-of-stream.
299      * @throws IOException if an I/O error occurs.
300      */
301     @Override
302     public int read(ByteBuffer buf) throws IOException {
303         checkStream();
304         int unread = outBuffer.remaining();
305         if (unread <= 0) { // Fill the unread decrypted data buffer firstly
306             final int n = input.read(inBuffer);
307             if (n <= 0) {
308                 return n;
309             }
310 
311             streamOffset += n; // Read n bytes
312             if (buf.isDirect() && buf.remaining() >= inBuffer.position()
313                     && padding == 0) {
314                 // Use buf as the output buffer directly
315                 decryptInPlace(buf);
316                 padding = postDecryption(streamOffset);
317                 return n;
318             }
319             // Use outBuffer as the output buffer
320             decrypt();
321             padding = postDecryption(streamOffset);
322         }
323 
324         // Copy decrypted data from outBuffer to buf
325         unread = outBuffer.remaining();
326         final int toRead = buf.remaining();
327         if (toRead <= unread) {
328             final int limit = outBuffer.limit();
329             outBuffer.limit(outBuffer.position() + toRead);
330             buf.put(outBuffer);
331             outBuffer.limit(limit);
332             return toRead;
333         }
334         buf.put(outBuffer);
335         return unread;
336     }
337 
338     /**
339      * Seeks the stream to a specific position relative to start of the under
340      * layer stream.
341      *
342      * @param position the given position in the data.
343      * @throws IOException if an I/O error occurs.
344      */
345     public void seek(long position) throws IOException {
346         Utils.checkArgument(position >= 0, "Cannot seek to negative offset.");
347         checkStream();
348         /*
349          * If data of target pos in the underlying stream has already been read
350          * and decrypted in outBuffer, we just need to re-position outBuffer.
351          */
352         if (position >= getStreamPosition() && position <= getStreamOffset()) {
353             int forward = (int) (position - getStreamPosition());
354             if (forward > 0) {
355                 outBuffer.position(outBuffer.position() + forward);
356             }
357         } else {
358             input.seek(position);
359             resetStreamOffset(position);
360         }
361     }
362 
363     /**
364      * Gets the offset of the stream.
365      *
366      * @return the stream offset.
367      */
368     protected long getStreamOffset() {
369         return streamOffset;
370     }
371 
372     /**
373      * Sets the offset of stream.
374      *
375      * @param streamOffset the stream offset.
376      */
377     protected void setStreamOffset(long streamOffset) {
378         this.streamOffset = streamOffset;
379     }
380 
381     /**
382      * Gets the position of the stream.
383      *
384      * @return the position of the stream.
385      */
386     protected long getStreamPosition() {
387         return streamOffset - outBuffer.remaining();
388     }
389 
390     /**
391      * Decrypts more data by reading the under layer stream. The decrypted data
392      * will be put in the output buffer.
393      *
394      * @return The number of decrypted data. -1 if end of the decrypted stream.
395      * @throws IOException if an I/O error occurs.
396      */
397     @Override
398     protected int decryptMore() throws IOException {
399         int n = input.read(inBuffer);
400         if (n <= 0) {
401             return n;
402         }
403 
404         streamOffset += n; // Read n bytes
405         decrypt();
406         padding = postDecryption(streamOffset);
407         return outBuffer.remaining();
408     }
409 
410     /**
411      * Does the decryption using inBuffer as input and outBuffer as output. Upon
412      * return, inBuffer is cleared; the decrypted data starts at
413      * outBuffer.position() and ends at outBuffer.limit().
414      *
415      * @throws IOException if an I/O error occurs.
416      */
417     @Override
418     protected void decrypt() throws IOException {
419         Utils.checkState(inBuffer.position() >= padding);
420         if (inBuffer.position() == padding) {
421             // There is no real data in inBuffer.
422             return;
423         }
424 
425         inBuffer.flip();
426         outBuffer.clear();
427         decryptBuffer(outBuffer);
428         inBuffer.clear();
429         outBuffer.flip();
430 
431         if (padding > 0) {
432             /*
433              * The plain text and cipher text have a 1:1 mapping, they start at
434              * the same position.
435              */
436             outBuffer.position(padding);
437         }
438     }
439 
440     /**
441      * Does the decryption using inBuffer as input and buf as output. Upon
442      * return, inBuffer is cleared; the buf's position will be equal to
443      * <i>p</i>&nbsp;<tt>+</tt>&nbsp;<i>n</i> where <i>p</i> is the position
444      * before decryption, <i>n</i> is the number of bytes decrypted. The buf's
445      * limit will not have changed.
446      *
447      * @param buf The buffer into which bytes are to be transferred.
448      * @throws IOException if an I/O error occurs.
449      */
450     protected void decryptInPlace(ByteBuffer buf) throws IOException {
451         Utils.checkState(inBuffer.position() >= padding);
452         Utils.checkState(buf.isDirect());
453         Utils.checkState(buf.remaining() >= inBuffer.position());
454         Utils.checkState(padding == 0);
455 
456         if (inBuffer.position() == padding) {
457             // There is no real data in inBuffer.
458             return;
459         }
460         inBuffer.flip();
461         decryptBuffer(buf);
462         inBuffer.clear();
463     }
464 
465     /**
466      * Decrypts all data in buf: total n bytes from given start position. Output
467      * is also buf and same start position. buf.position() and buf.limit()
468      * should be unchanged after decryption.
469      *
470      * @param buf The buffer into which bytes are to be transferred.
471      * @param offset the start offset in the data.
472      * @param len the the maximum number of decrypted data bytes to read.
473      * @throws IOException if an I/O error occurs.
474      */
475     protected void decrypt(ByteBuffer buf, int offset, int len)
476             throws IOException {
477         final int pos = buf.position();
478         final int limit = buf.limit();
479         int n = 0;
480         while (n < len) {
481             buf.position(offset + n);
482             buf.limit(offset + n + Math.min(len - n, inBuffer.remaining()));
483             inBuffer.put(buf);
484             // Do decryption
485             try {
486                 decrypt();
487                 buf.position(offset + n);
488                 buf.limit(limit);
489                 n += outBuffer.remaining();
490                 buf.put(outBuffer);
491             } finally {
492                 padding = postDecryption(streamOffset - (len - n));
493             }
494         }
495         buf.position(pos);
496     }
497 
498     /**
499      * This method is executed immediately after decryption. Checks whether
500      * cipher should be updated and recalculate padding if needed.
501      *
502      * @param position the given position in the data..
503      * @return the byte.
504      * @throws IOException if an I/O error occurs.
505      */
506     protected byte postDecryption(long position) throws IOException {
507         byte padding = 0;
508         if (cipherReset) {
509             /*
510              * This code is generally not executed since the cipher usually
511              * maintains cipher context (e.g. the counter) internally. However,
512              * some implementations can't maintain context so a re-init is
513              * necessary after each decryption call.
514              */
515             resetCipher(position);
516             padding = getPadding(position);
517             inBuffer.position(padding);
518         }
519         return padding;
520     }
521 
522     /**
523      * Gets the initialization vector.
524      *
525      * @return the initIV.
526      */
527     protected byte[] getInitIV() {
528         return initIV;
529     }
530 
531     /**
532      * Gets the counter for input stream position.
533      *
534      * @param position the given position in the data.
535      * @return the counter for input stream position.
536      */
537     protected long getCounter(long position) {
538         return position / cipher.getBlockSize();
539     }
540 
541     /**
542      * Gets the padding for input stream position.
543      *
544      * @param position the given position in the data.
545      * @return the padding for input stream position.
546      */
547     protected byte getPadding(long position) {
548         return (byte) (position % cipher.getBlockSize());
549     }
550 
551     /**
552      * Overrides the {@link CtrCryptoInputStream#initCipher()}. Initializes the
553      * cipher.
554      */
555     @Override
556     protected void initCipher() {
557         // Do nothing for initCipher
558         // Will reset the cipher when reset the stream offset
559     }
560 
561     /**
562      * Calculates the counter and iv, resets the cipher.
563      *
564      * @param position the given position in the data.
565      * @throws IOException if an I/O error occurs.
566      */
567     protected void resetCipher(long position) throws IOException {
568         final long counter = getCounter(position);
569         CtrCryptoInputStream.calculateIV(initIV, counter, iv);
570         try {
571             cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
572         } catch (InvalidKeyException e) {
573             throw new IOException(e);
574         } catch (InvalidAlgorithmParameterException e) {
575             throw new IOException(e);
576         }
577         cipherReset = false;
578     }
579 
580     /**
581      * Resets the underlying stream offset; clear {@link #inBuffer} and
582      * {@link #outBuffer}. This Typically happens during {@link #skip(long)}.
583      *
584      * @param offset the offset of the stream.
585      * @throws IOException if an I/O error occurs.
586      */
587     protected void resetStreamOffset(long offset) throws IOException {
588         streamOffset = offset;
589         inBuffer.clear();
590         outBuffer.clear();
591         outBuffer.limit(0);
592         resetCipher(offset);
593         padding = getPadding(offset);
594         inBuffer.position(padding); // Set proper position for input data.
595     }
596 
597     /**
598      * Does the decryption using out as output.
599      *
600      * @param out the output ByteBuffer.
601      * @throws IOException if an I/O error occurs.
602      */
603     protected void decryptBuffer(ByteBuffer out) throws IOException {
604         int inputSize = inBuffer.remaining();
605         try {
606             int n = cipher.update(inBuffer, out);
607             if (n < inputSize) {
608                 /**
609                  * Typically code will not get here. CryptoCipher#update will
610                  * consume all input data and put result in outBuffer.
611                  * CryptoCipher#doFinal will reset the cipher context.
612                  */
613                 cipher.doFinal(inBuffer, out);
614                 cipherReset = true;
615             }
616         } catch (ShortBufferException e) {
617             throw new IOException(e);
618         } catch (IllegalBlockSizeException e) {
619             throw new IOException(e);
620         } catch (BadPaddingException e) {
621             throw new IOException(e);
622         }
623     }
624 
625     /**
626      * <p>
627      * This method is only for Counter (CTR) mode. Generally the CryptoCipher
628      * calculates the IV and maintain encryption context internally.For example
629      * a Cipher will maintain its encryption context internally when we do
630      * encryption/decryption using the CryptoCipher#update interface.
631      * </p>
632      * <p>
633      * Encryption/Decryption is not always on the entire file. For example, in
634      * Hadoop, a node may only decrypt a portion of a file (i.e. a split). In
635      * these situations, the counter is derived from the file position.
636      * </p>
637      * The IV can be calculated by combining the initial IV and the counter with
638      * a lossless operation (concatenation, addition, or XOR).
639      *
640      * @see <a
641      *      href="http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29">
642      *      http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29</a>
643      *
644      * @param initIV initial IV
645      * @param counter counter for input stream position
646      * @param IV the IV for input stream position
647      */
648     static void calculateIV(byte[] initIV, long counter, byte[] IV) {
649         Utils.checkArgument(initIV.length == CryptoCipherFactory.AES_BLOCK_SIZE);
650         Utils.checkArgument(IV.length == CryptoCipherFactory.AES_BLOCK_SIZE);
651     
652         int i = IV.length; // IV length
653         int j = 0; // counter bytes index
654         int sum = 0;
655         while (i-- > 0) {
656             // (sum >>> Byte.SIZE) is the carry for addition
657             sum = (initIV[i] & 0xff) + (sum >>> Byte.SIZE); // NOPMD
658             if (j++ < 8) { // Big-endian, and long is 8 bytes length
659                 sum += (byte) counter & 0xff;
660                 counter >>>= 8;
661             }
662             IV[i] = (byte) sum;
663         }
664     }
665 }