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 }