1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.shiro.crypto;
20
21 import org.apache.shiro.util.ByteSource;
22 import org.apache.shiro.util.StringUtils;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
25
26 import javax.crypto.CipherInputStream;
27 import javax.crypto.spec.IvParameterSpec;
28 import javax.crypto.spec.SecretKeySpec;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.io.OutputStream;
32 import java.security.Key;
33 import java.security.SecureRandom;
34 import java.security.spec.AlgorithmParameterSpec;
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69 public abstract class JcaCipherService implements CipherService {
70
71
72
73
74 private static final Logger log = LoggerFactory.getLogger(JcaCipherService.class);
75
76
77
78
79 private static final int DEFAULT_KEY_SIZE = 128;
80
81
82
83
84 private static final int DEFAULT_STREAMING_BUFFER_SIZE = 512;
85
86 private static final int BITS_PER_BYTE = 8;
87
88
89
90
91 private static final String RANDOM_NUM_GENERATOR_ALGORITHM_NAME = "SHA1PRNG";
92
93
94
95
96 private String algorithmName;
97
98
99
100
101 private int keySize;
102
103
104
105
106 private int streamingBufferSize;
107
108 private boolean generateInitializationVectors;
109 private int initializationVectorSize;
110
111
112 private SecureRandom secureRandom;
113
114
115
116
117
118
119
120
121
122
123
124
125 protected JcaCipherService(String algorithmName) {
126 if (!StringUtils.hasText(algorithmName)) {
127 throw new IllegalArgumentException("algorithmName argument cannot be null or empty.");
128 }
129 this.algorithmName = algorithmName;
130 this.keySize = DEFAULT_KEY_SIZE;
131 this.initializationVectorSize = DEFAULT_KEY_SIZE;
132 this.streamingBufferSize = DEFAULT_STREAMING_BUFFER_SIZE;
133 this.generateInitializationVectors = true;
134 }
135
136
137
138
139
140
141
142 public String getAlgorithmName() {
143 return algorithmName;
144 }
145
146
147
148
149
150
151 public int getKeySize() {
152 return keySize;
153 }
154
155
156
157
158
159
160 public void setKeySize(int keySize) {
161 this.keySize = keySize;
162 }
163
164 public boolean isGenerateInitializationVectors() {
165 return generateInitializationVectors;
166 }
167
168 public void setGenerateInitializationVectors(boolean generateInitializationVectors) {
169 this.generateInitializationVectors = generateInitializationVectors;
170 }
171
172
173
174
175
176
177 public int getInitializationVectorSize() {
178 return initializationVectorSize;
179 }
180
181
182
183
184
185
186
187
188
189 public void setInitializationVectorSize(int initializationVectorSize) throws IllegalArgumentException {
190 if (initializationVectorSize % BITS_PER_BYTE != 0) {
191 String msg = "Initialization vector sizes are specified in bits, but must be a multiple of 8 so they " +
192 "can be easily represented as a byte array.";
193 throw new IllegalArgumentException(msg);
194 }
195 this.initializationVectorSize = initializationVectorSize;
196 }
197
198 protected boolean isGenerateInitializationVectors(boolean streaming) {
199 return isGenerateInitializationVectors();
200 }
201
202
203
204
205
206
207
208
209
210
211
212 public int getStreamingBufferSize() {
213 return streamingBufferSize;
214 }
215
216
217
218
219
220
221
222
223
224
225
226 public void setStreamingBufferSize(int streamingBufferSize) {
227 this.streamingBufferSize = streamingBufferSize;
228 }
229
230
231
232
233
234
235
236
237 public SecureRandom getSecureRandom() {
238 return secureRandom;
239 }
240
241
242
243
244
245
246
247
248 public void setSecureRandom(SecureRandom secureRandom) {
249 this.secureRandom = secureRandom;
250 }
251
252 protected static SecureRandom getDefaultSecureRandom() {
253 try {
254 return java.security.SecureRandom.getInstance(RANDOM_NUM_GENERATOR_ALGORITHM_NAME);
255 } catch (java.security.NoSuchAlgorithmException e) {
256 log.debug("The SecureRandom SHA1PRNG algorithm is not available on the current platform. Using the " +
257 "platform's default SecureRandom algorithm.", e);
258 return new java.security.SecureRandom();
259 }
260 }
261
262 protected SecureRandom ensureSecureRandom() {
263 SecureRandom random = getSecureRandom();
264 if (random == null) {
265 random = getDefaultSecureRandom();
266 }
267 return random;
268 }
269
270
271
272
273
274
275
276
277
278
279
280 protected String getTransformationString(boolean streaming) {
281 return getAlgorithmName();
282 }
283
284 protected byte[] generateInitializationVector(boolean streaming) {
285 int size = getInitializationVectorSize();
286 if (size <= 0) {
287 String msg = "initializationVectorSize property must be greater than zero. This number is " +
288 "typically set in the " + CipherService.class.getSimpleName() + " subclass constructor. " +
289 "Also check your configuration to ensure that if you are setting a value, it is positive.";
290 throw new IllegalStateException(msg);
291 }
292 if (size % BITS_PER_BYTE != 0) {
293 String msg = "initializationVectorSize property must be a multiple of 8 to represent as a byte array.";
294 throw new IllegalStateException(msg);
295 }
296 int sizeInBytes = size / BITS_PER_BYTE;
297 byte[] ivBytes = new byte[sizeInBytes];
298 SecureRandom random = ensureSecureRandom();
299 random.nextBytes(ivBytes);
300 return ivBytes;
301 }
302
303 public ByteSource encrypt(byte[] plaintext, byte[] key) {
304 byte[] ivBytes = null;
305 boolean generate = isGenerateInitializationVectors(false);
306 if (generate) {
307 ivBytes = generateInitializationVector(false);
308 if (ivBytes == null || ivBytes.length == 0) {
309 throw new IllegalStateException("Initialization vector generation is enabled - generated vector " +
310 "cannot be null or empty.");
311 }
312 }
313 return encrypt(plaintext, key, ivBytes, generate);
314 }
315
316 private ByteSource encrypt(byte[] plaintext, byte[] key, byte[] iv, boolean prependIv) throws CryptoException {
317
318 final int MODE = javax.crypto.Cipher.ENCRYPT_MODE;
319
320 byte[] output;
321
322 if (prependIv && iv != null && iv.length > 0) {
323
324 byte[] encrypted = crypt(plaintext, key, iv, MODE);
325
326 output = new byte[iv.length + encrypted.length];
327
328
329
330
331 System.arraycopy(iv, 0, output, 0, iv.length);
332
333
334 System.arraycopy(encrypted, 0, output, iv.length, encrypted.length);
335 } else {
336 output = crypt(plaintext, key, iv, MODE);
337 }
338
339 if (log.isTraceEnabled()) {
340 log.trace("Incoming plaintext of size " + (plaintext != null ? plaintext.length : 0) + ". Ciphertext " +
341 "byte array is size " + (output != null ? output.length : 0));
342 }
343
344 return ByteSource.Util.bytes(output);
345 }
346
347 public ByteSource decrypt(byte[] ciphertext, byte[] key) throws CryptoException {
348
349 byte[] encrypted = ciphertext;
350
351
352 byte[] iv = null;
353
354 if (isGenerateInitializationVectors(false)) {
355 try {
356
357
358
359
360
361
362
363
364
365 int ivSize = getInitializationVectorSize();
366 int ivByteSize = ivSize / BITS_PER_BYTE;
367
368
369 iv = new byte[ivByteSize];
370 System.arraycopy(ciphertext, 0, iv, 0, ivByteSize);
371
372
373 int encryptedSize = ciphertext.length - ivByteSize;
374 encrypted = new byte[encryptedSize];
375 System.arraycopy(ciphertext, ivByteSize, encrypted, 0, encryptedSize);
376 } catch (Exception e) {
377 String msg = "Unable to correctly extract the Initialization Vector or ciphertext.";
378 throw new CryptoException(msg, e);
379 }
380 }
381
382 return decrypt(encrypted, key, iv);
383 }
384
385 private ByteSource decrypt(byte[] ciphertext, byte[] key, byte[] iv) throws CryptoException {
386 if (log.isTraceEnabled()) {
387 log.trace("Attempting to decrypt incoming byte array of length " +
388 (ciphertext != null ? ciphertext.length : 0));
389 }
390 byte[] decrypted = crypt(ciphertext, key, iv, javax.crypto.Cipher.DECRYPT_MODE);
391 return decrypted == null ? null : ByteSource.Util.bytes(decrypted);
392 }
393
394
395
396
397
398
399
400
401
402
403
404
405 private javax.crypto.Cipher newCipherInstance(boolean streaming) throws CryptoException {
406 String transformationString = getTransformationString(streaming);
407 try {
408 return javax.crypto.Cipher.getInstance(transformationString);
409 } catch (Exception e) {
410 String msg = "Unable to acquire a Java JCA Cipher instance using " +
411 javax.crypto.Cipher.class.getName() + ".getInstance( \"" + transformationString + "\" ). " +
412 getAlgorithmName() + " under this configuration is required for the " +
413 getClass().getName() + " instance to function.";
414 throw new CryptoException(msg, e);
415 }
416 }
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440 private byte[] crypt(byte[] bytes, byte[] key, byte[] iv, int mode) throws IllegalArgumentException, CryptoException {
441 if (key == null || key.length == 0) {
442 throw new IllegalArgumentException("key argument cannot be null or empty.");
443 }
444 javax.crypto.Cipher cipher = initNewCipher(mode, key, iv, false);
445 return crypt(cipher, bytes);
446 }
447
448
449
450
451
452
453
454
455
456
457 private byte[] crypt(javax.crypto.Cipher cipher, byte[] bytes) throws CryptoException {
458 try {
459 return cipher.doFinal(bytes);
460 } catch (Exception e) {
461 String msg = "Unable to execute 'doFinal' with cipher instance [" + cipher + "].";
462 throw new CryptoException(msg, e);
463 }
464 }
465
466
467
468
469
470
471
472
473
474
475
476
477 private void init(javax.crypto.Cipher cipher, int mode, java.security.Key key,
478 AlgorithmParameterSpec spec, SecureRandom random) throws CryptoException {
479 try {
480 if (random != null) {
481 if (spec != null) {
482 cipher.init(mode, key, spec, random);
483 } else {
484 cipher.init(mode, key, random);
485 }
486 } else {
487 if (spec != null) {
488 cipher.init(mode, key, spec);
489 } else {
490 cipher.init(mode, key);
491 }
492 }
493 } catch (Exception e) {
494 String msg = "Unable to init cipher instance.";
495 throw new CryptoException(msg, e);
496 }
497 }
498
499
500 public void encrypt(InputStream in, OutputStream out, byte[] key) throws CryptoException {
501 byte[] iv = null;
502 boolean generate = isGenerateInitializationVectors(true);
503 if (generate) {
504 iv = generateInitializationVector(true);
505 if (iv == null || iv.length == 0) {
506 throw new IllegalStateException("Initialization vector generation is enabled - generated vector " +
507 "cannot be null or empty.");
508 }
509 }
510 encrypt(in, out, key, iv, generate);
511 }
512
513 private void encrypt(InputStream in, OutputStream out, byte[] key, byte[] iv, boolean prependIv) throws CryptoException {
514 if (prependIv && iv != null && iv.length > 0) {
515 try {
516
517 out.write(iv);
518 } catch (IOException e) {
519 throw new CryptoException(e);
520 }
521 }
522
523 crypt(in, out, key, iv, javax.crypto.Cipher.ENCRYPT_MODE);
524 }
525
526 public void decrypt(InputStream in, OutputStream out, byte[] key) throws CryptoException {
527 decrypt(in, out, key, isGenerateInitializationVectors(true));
528 }
529
530 private void decrypt(InputStream in, OutputStream out, byte[] key, boolean ivPrepended) throws CryptoException {
531
532 byte[] iv = null;
533
534 if (ivPrepended) {
535
536
537 int ivSize = getInitializationVectorSize();
538 int ivByteSize = ivSize / BITS_PER_BYTE;
539 iv = new byte[ivByteSize];
540 int read;
541
542 try {
543 read = in.read(iv);
544 } catch (IOException e) {
545 String msg = "Unable to correctly read the Initialization Vector from the input stream.";
546 throw new CryptoException(msg, e);
547 }
548
549 if (read != ivByteSize) {
550 throw new CryptoException("Unable to read initialization vector bytes from the InputStream. " +
551 "This is required when initialization vectors are autogenerated during an encryption " +
552 "operation.");
553 }
554 }
555
556 decrypt(in, out, key, iv);
557 }
558
559 private void decrypt(InputStream in, OutputStream out, byte[] decryptionKey, byte[] iv) throws CryptoException {
560 crypt(in, out, decryptionKey, iv, javax.crypto.Cipher.DECRYPT_MODE);
561 }
562
563 private void crypt(InputStream in, OutputStream out, byte[] keyBytes, byte[] iv, int cryptMode) throws CryptoException {
564 if (in == null) {
565 throw new NullPointerException("InputStream argument cannot be null.");
566 }
567 if (out == null) {
568 throw new NullPointerException("OutputStream argument cannot be null.");
569 }
570
571 javax.crypto.Cipher cipher = initNewCipher(cryptMode, keyBytes, iv, true);
572
573 CipherInputStream cis = new CipherInputStream(in, cipher);
574
575 int bufSize = getStreamingBufferSize();
576 byte[] buffer = new byte[bufSize];
577
578 int bytesRead;
579 try {
580 while ((bytesRead = cis.read(buffer)) != -1) {
581 out.write(buffer, 0, bytesRead);
582 }
583 } catch (IOException e) {
584 throw new CryptoException(e);
585 }
586 }
587
588 private javax.crypto.Cipher initNewCipher(int jcaCipherMode, byte[] key, byte[] iv, boolean streaming)
589 throws CryptoException {
590
591 javax.crypto.Cipher cipher = newCipherInstance(streaming);
592 java.security.Key jdkKey = new SecretKeySpec(key, getAlgorithmName());
593 AlgorithmParameterSpec ivSpec = null;
594
595 if (iv != null && iv.length > 0) {
596 ivSpec = createParameterSpec(iv, streaming);
597 }
598
599 init(cipher, jcaCipherMode, jdkKey, ivSpec, getSecureRandom());
600
601 return cipher;
602 }
603
604 protected AlgorithmParameterSpec createParameterSpec(byte[] iv, boolean streaming) {
605 return new IvParameterSpec(iv);
606 }
607 }