1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
47
48
49
50
51
52 public class CryptoInputStream extends InputStream implements ReadableByteChannel {
53
54
55
56
57 public static final String STREAM_BUFFER_SIZE_KEY = Crypto.CONF_PREFIX
58 + "stream.buffer.size";
59
60
61
62
63
64 private static final int STREAM_BUFFER_SIZE_DEFAULT = 8192;
65
66 private static final int MIN_BUFFER_SIZE = 512;
67
68
69
70
71
72
73 public static final int EOS = -1;
74
75
76
77
78
79
80
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
90
91
92
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
102
103
104
105 static void freeDirectBuffer(final ByteBuffer buffer) {
106 if (buffer != null) {
107 try {
108
109
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
118 final Method getCleaner = Class.forName(SUN_CLASS).getMethod("cleaner");
119 final Object cleaner = getCleaner.invoke(buffer, EMPTY_OBJECT_ARRAY);
120
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) {
127
128 }
129 }
130 }
131
132
133
134
135
136
137
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
147 final CryptoCipher cipher;
148
149
150 private final int bufferSize;
151
152
153 final Key key;
154
155
156 private final AlgorithmParameterSpec params;
157
158
159 private boolean closed;
160
161
162
163
164 private boolean finalDone;
165
166
167 Input input;
168
169
170
171
172
173 ByteBuffer inBuffer;
174
175
176
177
178
179 ByteBuffer outBuffer;
180
181
182
183
184
185
186
187
188
189
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
201
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
214
215
216
217
218
219
220
221
222 @SuppressWarnings("resource")
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
231
232
233
234
235
236
237
238
239 @SuppressWarnings("resource")
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
248
249
250
251
252
253
254
255
256
257
258
259
260 @SuppressWarnings("resource")
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
270
271
272
273
274
275
276
277
278
279
280
281
282 @SuppressWarnings("resource")
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
292
293
294
295
296
297
298
299
300
301 @Override
302 public int available() throws IOException {
303 checkStream();
304
305 return input.available() + outBuffer.remaining();
306 }
307
308
309
310
311
312
313 protected void checkStream() throws IOException {
314 if (closed) {
315 throw new IOException("Stream closed");
316 }
317 }
318
319
320
321
322
323
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
340
341
342
343
344
345 protected void decrypt() throws IOException {
346
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
357 inBuffer.clear();
358 outBuffer.flip();
359 }
360
361
362
363
364
365
366 protected void decryptFinal() throws IOException {
367
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
379 inBuffer.clear();
380 outBuffer.flip();
381 }
382
383
384
385
386
387
388
389
390
391
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
401 decryptFinal();
402
403
404 final int remaining = outBuffer.remaining();
405 if (remaining > 0) {
406 return remaining;
407 }
408
409
410 return EOS;
411 }
412 if (n == 0) {
413
414 return 0;
415 }
416 decrypt();
417 return outBuffer.remaining();
418 }
419
420
421 protected void freeBuffers() {
422 CryptoInputStream.freeDirectBuffer(inBuffer);
423 CryptoInputStream.freeDirectBuffer(outBuffer);
424 }
425
426
427
428
429
430
431 protected int getBufferSize() {
432 return bufferSize;
433 }
434
435
436
437
438
439
440 protected CryptoCipher getCipher() {
441 return cipher;
442 }
443
444
445
446
447
448
449 protected Input getInput() {
450 return input;
451 }
452
453
454
455
456
457
458 protected Key getKey() {
459 return key;
460 }
461
462
463
464
465
466
467 protected AlgorithmParameterSpec getParams() {
468 return params;
469 }
470
471
472
473
474
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
486
487
488
489 @Override
490 public boolean isOpen() {
491 return !closed;
492 }
493
494
495
496
497
498
499
500 @Override
501 public boolean markSupported() {
502 return false;
503 }
504
505
506
507
508
509
510
511
512
513 @Override
514 public int read() throws IOException {
515 int n;
516 while ((n = read(oneByteBuf, 0, 1)) == 0) {
517
518 }
519 return n == EOS ? EOS : oneByteBuf[0] & 0xff;
520 }
521
522
523
524
525
526
527
528
529
530
531
532
533
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
549 final int n = Math.min(len, remaining);
550 outBuffer.get(array, off, n);
551 return n;
552 }
553
554
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
570
571
572
573
574
575
576
577
578 @Override
579 public int read(final ByteBuffer dst) throws IOException {
580 checkStream();
581 int remaining = outBuffer.remaining();
582 if (remaining <= 0) {
583
584
585 int nd = 0;
586 while (nd == 0) {
587 nd = decryptMore();
588 }
589
590 if (nd < 0) {
591 return EOS;
592 }
593 }
594
595
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
611
612
613
614
615
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
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
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 }