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> {@code +} <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 }