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