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.nio.ByteBuffer;
22 import java.security.GeneralSecurityException;
23 import java.util.Properties;
24 import java.util.Queue;
25 import java.util.concurrent.ConcurrentLinkedQueue;
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.stream.input.Input;
32 import org.apache.commons.crypto.utils.AES;
33 import org.apache.commons.crypto.utils.IoUtils;
34 import org.apache.commons.crypto.utils.Utils;
35
36
37
38
39
40
41 public class PositionedCryptoInputStream extends CtrCryptoInputStream {
42
43 private static class CipherState {
44
45 private final CryptoCipher cryptoCipher;
46 private boolean reset;
47
48
49
50
51
52
53 public CipherState(final CryptoCipher cryptoCipher) {
54 this.cryptoCipher = cryptoCipher;
55 this.reset = false;
56 }
57
58
59
60
61
62
63 public CryptoCipher getCryptoCipher() {
64 return cryptoCipher;
65 }
66
67
68
69
70
71
72 public boolean isReset() {
73 return reset;
74 }
75
76
77
78
79
80
81 public void reset(final boolean reset) {
82 this.reset = reset;
83 }
84 }
85
86
87
88
89 private final Queue<ByteBuffer> byteBufferPool = new ConcurrentLinkedQueue<>();
90
91
92
93
94 private final Queue<CipherState> cipherStatePool = new ConcurrentLinkedQueue<>();
95
96
97
98
99 private final Properties properties;
100
101
102
103
104
105
106
107
108
109
110
111
112 @SuppressWarnings("resource")
113 public PositionedCryptoInputStream(final Properties properties, final Input in, final byte[] key,
114 final byte[] iv, final long streamOffset) throws IOException {
115 this(properties, in, Utils.getCipherInstance(AES.CTR_NO_PADDING, properties),
116 CryptoInputStream.getBufferSize(properties), key, iv, streamOffset);
117 }
118
119
120
121
122
123
124
125
126
127
128
129
130
131 protected PositionedCryptoInputStream(final Properties properties, final Input input, final CryptoCipher cipher,
132 final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset)
133 throws IOException {
134 super(input, cipher, bufferSize, key, iv, streamOffset);
135 this.properties = properties;
136 }
137
138
139 private void cleanByteBufferPool() {
140 ByteBuffer buf;
141 while ((buf = byteBufferPool.poll()) != null) {
142 CryptoInputStream.freeDirectBuffer(buf);
143 }
144 }
145
146
147 private void cleanCipherStatePool() {
148 CipherState cs;
149 while ((cs = cipherStatePool.poll()) != null) {
150 try {
151 cs.getCryptoCipher().close();
152 } catch (IOException ignored) {
153
154 }
155 }
156 }
157
158
159
160
161
162
163
164 @Override
165 public void close() throws IOException {
166 if (!isOpen()) {
167 return;
168 }
169
170 cleanByteBufferPool();
171 cleanCipherStatePool();
172 super.close();
173 }
174
175
176
177
178
179
180
181
182
183
184
185
186 private void decrypt(final CipherState state, final ByteBuffer inByteBuffer,
187 final ByteBuffer outByteBuffer, final byte padding) throws IOException {
188 Utils.checkState(inByteBuffer.position() >= padding);
189 if (inByteBuffer.position() == padding) {
190
191 return;
192 }
193 inByteBuffer.flip();
194 outByteBuffer.clear();
195 decryptBuffer(state, inByteBuffer, outByteBuffer);
196 inByteBuffer.clear();
197 outByteBuffer.flip();
198 if (padding > 0) {
199
200
201
202
203 outByteBuffer.position(padding);
204 }
205 }
206
207
208
209
210
211
212
213
214
215
216
217 protected void decrypt(final long position, final byte[] buffer, final int offset, final int length)
218 throws IOException {
219 final ByteBuffer inByteBuffer = getBuffer();
220 final ByteBuffer outByteBuffer = getBuffer();
221 CipherState state = null;
222 try {
223 state = getCipherState();
224 final byte[] iv = getInitIV().clone();
225 resetCipher(state, position, iv);
226 byte padding = getPadding(position);
227 inByteBuffer.position(padding);
228
229 int n = 0;
230 while (n < length) {
231 final int toDecrypt = Math.min(length - n, inByteBuffer.remaining());
232 inByteBuffer.put(buffer, offset + n, toDecrypt);
233
234
235 decrypt(state, inByteBuffer, outByteBuffer, padding);
236
237 outByteBuffer.get(buffer, offset + n, toDecrypt);
238 n += toDecrypt;
239 padding = postDecryption(state, inByteBuffer, position + n, iv);
240 }
241 } finally {
242 returnToPool(inByteBuffer);
243 returnToPool(outByteBuffer);
244 returnToPool(state);
245 }
246 }
247
248
249
250
251
252
253
254
255
256 @SuppressWarnings("resource")
257 private void decryptBuffer(final CipherState state, final ByteBuffer inByteBuffer,
258 final ByteBuffer outByteBuffer) throws IOException {
259 final int inputSize = inByteBuffer.remaining();
260 try {
261 final int n = state.getCryptoCipher().update(inByteBuffer, outByteBuffer);
262 if (n < inputSize) {
263
264
265
266
267
268 state.getCryptoCipher().doFinal(inByteBuffer, outByteBuffer);
269 state.reset(true);
270 }
271 } catch (final GeneralSecurityException e) {
272 throw new IOException(e);
273 }
274 }
275
276
277
278
279
280
281
282 private ByteBuffer getBuffer() {
283 final ByteBuffer buffer = byteBufferPool.poll();
284 return buffer != null ? buffer : ByteBuffer.allocateDirect(getBufferSize());
285 }
286
287
288
289
290
291
292
293 @SuppressWarnings("resource")
294 private CipherState getCipherState() throws IOException {
295 final CipherState state = cipherStatePool.poll();
296 return state != null ? state : new CipherState(Utils.getCipherInstance(AES.CTR_NO_PADDING, properties));
297 }
298
299
300
301
302
303
304
305
306
307
308
309 private byte postDecryption(final CipherState state, final ByteBuffer inByteBuffer,
310 final long position, final byte[] iv) {
311 byte padding = 0;
312 if (state.isReset()) {
313
314
315
316
317
318
319 resetCipher(state, position, iv);
320 padding = getPadding(position);
321 inByteBuffer.position(padding);
322 }
323 return padding;
324 }
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339 public int read(final long position, final byte[] buffer, final int offset, final int length)
340 throws IOException {
341 checkStream();
342 final int n = input.read(position, buffer, offset, length);
343 if (n > 0) {
344
345 decrypt(position, buffer, offset, n);
346 }
347 return n;
348 }
349
350
351
352
353
354
355
356
357
358
359 public void readFully(final long position, final byte[] buffer) throws IOException {
360 readFully(position, buffer, 0, buffer.length);
361 }
362
363
364
365
366
367
368
369
370
371
372
373
374 public void readFully(final long position, final byte[] buffer, final int offset, final int length)
375 throws IOException {
376 checkStream();
377 IoUtils.readFully(input, position, buffer, offset, length);
378 if (length > 0) {
379
380 decrypt(position, buffer, offset, length);
381 }
382 }
383
384
385
386
387
388
389
390
391 @SuppressWarnings("resource")
392 private void resetCipher(final CipherState state, final long position, final byte[] iv) {
393 final long counter = getCounter(position);
394 CtrCryptoInputStream.calculateIV(getInitIV(), counter, iv);
395 try {
396 state.getCryptoCipher().init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
397 } catch (final GeneralSecurityException e) {
398
399 }
400 state.reset(false);
401 }
402
403
404
405
406
407
408 private void returnToPool(final ByteBuffer buf) {
409 if (buf != null) {
410 buf.clear();
411 byteBufferPool.add(buf);
412 }
413 }
414
415
416
417
418
419
420 private void returnToPool(final CipherState state) {
421 if (state != null) {
422 cipherStatePool.add(state);
423 }
424 }
425 }