Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
JcaCipherService |
|
| 3.0;3 |
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, | |
13 | * software distributed under the License is distributed on an | |
14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
15 | * KIND, either express or implied. See the License for the | |
16 | * specific language governing permissions and limitations | |
17 | * under the License. | |
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 | * Abstract {@code CipherService} implementation utilizing Java's JCA APIs. | |
38 | * <h2>Auto-generated Initialization Vectors</h2> | |
39 | * Shiro does something by default for all of its {@code CipherService} implementations that the JCA | |
40 | * {@link javax.crypto.Cipher Cipher} does not do: by default, | |
41 | * <a href="http://en.wikipedia.org/wiki/Initialization_vector">initialization vector</a>s are automatically randomly | |
42 | * generated and prepended to encrypted data before returning from the {@code encrypt} methods. That is, the returned | |
43 | * byte array or {@code OutputStream} is actually a concatenation of an initialization vector byte array plus the actual | |
44 | * encrypted data byte array. The {@code decrypt} methods in turn know to read this prepended initialization vector | |
45 | * before decrypting the real data that follows. | |
46 | * <p/> | |
47 | * This is highly desirable because initialization vectors guarantee that, for a key and any plaintext, the encrypted | |
48 | * output will always be different <em>even if you call {@code encrypt} multiple times with the exact same arguments</em>. | |
49 | * This is essential in cryptography to ensure that data patterns cannot be identified across multiple input sources | |
50 | * that are the same or similar. | |
51 | * <p/> | |
52 | * You can turn off this behavior by setting the | |
53 | * {@link #setGenerateInitializationVectors(boolean) generateInitializationVectors} property to {@code false}, but it | |
54 | * is highly recommended that you do not do this unless you have a very good reason to do so, since you would be losing | |
55 | * a critical security feature. | |
56 | * <h3>Initialization Vector Size</h3> | |
57 | * This implementation defaults the {@link #setInitializationVectorSize(int) initializationVectorSize} attribute to | |
58 | * {@code 128} bits, a fairly common size. Initialization vector sizes are very algorithm specific however, so subclass | |
59 | * implementations will often override this value in their constructor if necessary. | |
60 | * <p/> | |
61 | * Also note that {@code initializationVectorSize} values are specified in the number of | |
62 | * bits (not bytes!) to match common references in most cryptography documentation. In practice though, initialization | |
63 | * vectors are always specified as a byte array, so ensure that if you set this property, that the value is a multiple | |
64 | * of {@code 8} to ensure that the IV can be correctly represented as a byte array (the | |
65 | * {@link #setInitializationVectorSize(int) setInitializationVectorSize} mutator method enforces this). | |
66 | * | |
67 | * @since 1.0 | |
68 | */ | |
69 | public abstract class JcaCipherService implements CipherService { | |
70 | ||
71 | /** | |
72 | * Internal private log instance. | |
73 | */ | |
74 | 1 | private static final Logger log = LoggerFactory.getLogger(JcaCipherService.class); |
75 | ||
76 | /** | |
77 | * Default key size (in bits) for generated keys. | |
78 | */ | |
79 | private static final int DEFAULT_KEY_SIZE = 128; | |
80 | ||
81 | /** | |
82 | * Default size of the internal buffer (in bytes) used to transfer data between streams during stream operations | |
83 | */ | |
84 | private static final int DEFAULT_STREAMING_BUFFER_SIZE = 512; | |
85 | ||
86 | private static final int BITS_PER_BYTE = 8; | |
87 | ||
88 | /** | |
89 | * Default SecureRandom algorithm name to use when acquiring the SecureRandom instance. | |
90 | */ | |
91 | private static final String RANDOM_NUM_GENERATOR_ALGORITHM_NAME = "SHA1PRNG"; | |
92 | ||
93 | /** | |
94 | * The name of the cipher algorithm to use for all encryption, decryption, and key operations | |
95 | */ | |
96 | private String algorithmName; | |
97 | ||
98 | /** | |
99 | * The size in bits (not bytes) of generated cipher keys | |
100 | */ | |
101 | private int keySize; | |
102 | ||
103 | /** | |
104 | * The size of the internal buffer (in bytes) used to transfer data from one stream to another during stream operations | |
105 | */ | |
106 | private int streamingBufferSize; | |
107 | ||
108 | private boolean generateInitializationVectors; | |
109 | private int initializationVectorSize; | |
110 | ||
111 | ||
112 | private SecureRandom secureRandom; | |
113 | ||
114 | /** | |
115 | * Creates a new {@code JcaCipherService} instance which will use the specified cipher {@code algorithmName} | |
116 | * for all encryption, decryption, and key operations. Also, the following defaults are set: | |
117 | * <ul> | |
118 | * <li>{@link #setKeySize keySize} = 128 bits</li> | |
119 | * <li>{@link #setInitializationVectorSize(int) initializationVectorSize} = 128 bits</li> | |
120 | * <li>{@link #setStreamingBufferSize(int) streamingBufferSize} = 512 bytes</li> | |
121 | * </ul> | |
122 | * | |
123 | * @param algorithmName the name of the cipher algorithm to use for all encryption, decryption, and key operations | |
124 | */ | |
125 | 7 | protected JcaCipherService(String algorithmName) { |
126 | 7 | if (!StringUtils.hasText(algorithmName)) { |
127 | 0 | throw new IllegalArgumentException("algorithmName argument cannot be null or empty."); |
128 | } | |
129 | 7 | this.algorithmName = algorithmName; |
130 | 7 | this.keySize = DEFAULT_KEY_SIZE; |
131 | 7 | this.initializationVectorSize = DEFAULT_KEY_SIZE; //default to same size as the key size (a common algorithm practice) |
132 | 7 | this.streamingBufferSize = DEFAULT_STREAMING_BUFFER_SIZE; |
133 | 7 | this.generateInitializationVectors = true; |
134 | 7 | } |
135 | ||
136 | /** | |
137 | * Returns the cipher algorithm name that will be used for all encryption, decryption, and key operations (for | |
138 | * example, 'AES', 'Blowfish', 'RSA', 'DSA', 'TripleDES', etc). | |
139 | * | |
140 | * @return the cipher algorithm name that will be used for all encryption, decryption, and key operations | |
141 | */ | |
142 | public String getAlgorithmName() { | |
143 | 26 | return algorithmName; |
144 | } | |
145 | ||
146 | /** | |
147 | * Returns the size in bits (not bytes) of generated cipher keys. | |
148 | * | |
149 | * @return the size in bits (not bytes) of generated cipher keys. | |
150 | */ | |
151 | public int getKeySize() { | |
152 | 6 | return keySize; |
153 | } | |
154 | ||
155 | /** | |
156 | * Sets the size in bits (not bytes) of generated cipher keys. | |
157 | * | |
158 | * @param keySize the size in bits (not bytes) of generated cipher keys. | |
159 | */ | |
160 | public void setKeySize(int keySize) { | |
161 | 0 | this.keySize = keySize; |
162 | 0 | } |
163 | ||
164 | public boolean isGenerateInitializationVectors() { | |
165 | 9 | return generateInitializationVectors; |
166 | } | |
167 | ||
168 | public void setGenerateInitializationVectors(boolean generateInitializationVectors) { | |
169 | 0 | this.generateInitializationVectors = generateInitializationVectors; |
170 | 0 | } |
171 | ||
172 | /** | |
173 | * Returns the algorithm-specific size in bits of generated initialization vectors. | |
174 | * | |
175 | * @return the algorithm-specific size in bits of generated initialization vectors. | |
176 | */ | |
177 | public int getInitializationVectorSize() { | |
178 | 17 | return initializationVectorSize; |
179 | } | |
180 | ||
181 | /** | |
182 | * Sets the algorithm-specific initialization vector size in bits (not bytes!) to be used when generating | |
183 | * initialization vectors. The value must be a multiple of {@code 8} to ensure that the IV can be represented | |
184 | * as a byte array. | |
185 | * | |
186 | * @param initializationVectorSize the size in bits (not bytes) of generated initialization vectors. | |
187 | * @throws IllegalArgumentException if the size is not a multiple of {@code 8}. | |
188 | */ | |
189 | public void setInitializationVectorSize(int initializationVectorSize) throws IllegalArgumentException { | |
190 | 2 | if (initializationVectorSize % BITS_PER_BYTE != 0) { |
191 | 0 | 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 | 0 | throw new IllegalArgumentException(msg); |
194 | } | |
195 | 2 | this.initializationVectorSize = initializationVectorSize; |
196 | 2 | } |
197 | ||
198 | protected boolean isGenerateInitializationVectors(boolean streaming) { | |
199 | 1 | return isGenerateInitializationVectors(); |
200 | } | |
201 | ||
202 | /** | |
203 | * Returns the size in bytes of the internal buffer used to transfer data from one stream to another during stream | |
204 | * operations ({@link #encrypt(java.io.InputStream, java.io.OutputStream, byte[])} and | |
205 | * {@link #decrypt(java.io.InputStream, java.io.OutputStream, byte[])}). | |
206 | * <p/> | |
207 | * Default size is {@code 512} bytes. | |
208 | * | |
209 | * @return the size of the internal buffer used to transfer data from one stream to another during stream | |
210 | * operations | |
211 | */ | |
212 | public int getStreamingBufferSize() { | |
213 | 8 | return streamingBufferSize; |
214 | } | |
215 | ||
216 | /** | |
217 | * Sets the size in bytes of the internal buffer used to transfer data from one stream to another during stream | |
218 | * operations ({@link #encrypt(java.io.InputStream, java.io.OutputStream, byte[])} and | |
219 | * {@link #decrypt(java.io.InputStream, java.io.OutputStream, byte[])}). | |
220 | * <p/> | |
221 | * Default size is {@code 512} bytes. | |
222 | * | |
223 | * @param streamingBufferSize the size of the internal buffer used to transfer data from one stream to another | |
224 | * during stream operations | |
225 | */ | |
226 | public void setStreamingBufferSize(int streamingBufferSize) { | |
227 | 0 | this.streamingBufferSize = streamingBufferSize; |
228 | 0 | } |
229 | ||
230 | /** | |
231 | * Returns a source of randomness for encryption operations. If one is not configured, and the underlying | |
232 | * algorithm needs one, the JDK {@code SHA1PRNG} instance will be used by default. | |
233 | * | |
234 | * @return a source of randomness for encryption operations. If one is not configured, and the underlying | |
235 | * algorithm needs one, the JDK {@code SHA1PRNG} instance will be used by default. | |
236 | */ | |
237 | public SecureRandom getSecureRandom() { | |
238 | 24 | return secureRandom; |
239 | } | |
240 | ||
241 | /** | |
242 | * Sets a source of randomness for encryption operations. If one is not configured, and the underlying | |
243 | * algorithm needs one, the JDK {@code SHA1PRNG} instance will be used by default. | |
244 | * | |
245 | * @param secureRandom a source of randomness for encryption operations. If one is not configured, and the | |
246 | * underlying algorithm needs one, the JDK {@code SHA1PRNG} instance will be used by default. | |
247 | */ | |
248 | public void setSecureRandom(SecureRandom secureRandom) { | |
249 | 0 | this.secureRandom = secureRandom; |
250 | 0 | } |
251 | ||
252 | protected static SecureRandom getDefaultSecureRandom() { | |
253 | try { | |
254 | 8 | return java.security.SecureRandom.getInstance(RANDOM_NUM_GENERATOR_ALGORITHM_NAME); |
255 | 0 | } catch (java.security.NoSuchAlgorithmException e) { |
256 | 0 | log.debug("The SecureRandom SHA1PRNG algorithm is not available on the current platform. Using the " + |
257 | "platform's default SecureRandom algorithm.", e); | |
258 | 0 | return new java.security.SecureRandom(); |
259 | } | |
260 | } | |
261 | ||
262 | protected SecureRandom ensureSecureRandom() { | |
263 | 8 | SecureRandom random = getSecureRandom(); |
264 | 8 | if (random == null) { |
265 | 8 | random = getDefaultSecureRandom(); |
266 | } | |
267 | 8 | return random; |
268 | } | |
269 | ||
270 | /** | |
271 | * Returns the transformation string to use with the {@link javax.crypto.Cipher#getInstance} invocation when | |
272 | * creating a new {@code Cipher} instance. This default implementation always returns | |
273 | * {@link #getAlgorithmName() getAlgorithmName()}. Block cipher implementations will want to override this method | |
274 | * to support appending cipher operation modes and padding schemes. | |
275 | * | |
276 | * @param streaming if the transformation string is going to be used for a Cipher for stream-based encryption or not. | |
277 | * @return the transformation string to use with the {@link javax.crypto.Cipher#getInstance} invocation when | |
278 | * creating a new {@code Cipher} instance. | |
279 | */ | |
280 | protected String getTransformationString(boolean streaming) { | |
281 | 0 | return getAlgorithmName(); |
282 | } | |
283 | ||
284 | protected byte[] generateInitializationVector(boolean streaming) { | |
285 | 8 | int size = getInitializationVectorSize(); |
286 | 8 | if (size <= 0) { |
287 | 0 | 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 | 0 | throw new IllegalStateException(msg); |
291 | } | |
292 | 8 | if (size % BITS_PER_BYTE != 0) { |
293 | 0 | String msg = "initializationVectorSize property must be a multiple of 8 to represent as a byte array."; |
294 | 0 | throw new IllegalStateException(msg); |
295 | } | |
296 | 8 | int sizeInBytes = size / BITS_PER_BYTE; |
297 | 8 | byte[] ivBytes = new byte[sizeInBytes]; |
298 | 8 | SecureRandom random = ensureSecureRandom(); |
299 | 8 | random.nextBytes(ivBytes); |
300 | 8 | return ivBytes; |
301 | } | |
302 | ||
303 | public ByteSource encrypt(byte[] plaintext, byte[] key) { | |
304 | 4 | byte[] ivBytes = null; |
305 | 4 | boolean generate = isGenerateInitializationVectors(false); |
306 | 4 | if (generate) { |
307 | 4 | ivBytes = generateInitializationVector(false); |
308 | 4 | if (ivBytes == null || ivBytes.length == 0) { |
309 | 0 | throw new IllegalStateException("Initialization vector generation is enabled - generated vector" + |
310 | "cannot be null or empty."); | |
311 | } | |
312 | } | |
313 | 4 | return encrypt(plaintext, key, ivBytes, generate); |
314 | } | |
315 | ||
316 | private ByteSource encrypt(byte[] plaintext, byte[] key, byte[] iv, boolean prependIv) throws CryptoException { | |
317 | ||
318 | 4 | final int MODE = javax.crypto.Cipher.ENCRYPT_MODE; |
319 | ||
320 | byte[] output; | |
321 | ||
322 | 4 | if (prependIv && iv != null && iv.length > 0) { |
323 | ||
324 | 4 | byte[] encrypted = crypt(plaintext, key, iv, MODE); |
325 | ||
326 | 4 | output = new byte[iv.length + encrypted.length]; |
327 | ||
328 | //now copy the iv bytes + encrypted bytes into one output array: | |
329 | ||
330 | // iv bytes: | |
331 | 4 | System.arraycopy(iv, 0, output, 0, iv.length); |
332 | ||
333 | // + encrypted bytes: | |
334 | 4 | System.arraycopy(encrypted, 0, output, iv.length, encrypted.length); |
335 | 4 | } else { |
336 | 0 | output = crypt(plaintext, key, iv, MODE); |
337 | } | |
338 | ||
339 | 4 | if (log.isTraceEnabled()) { |
340 | 4 | 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 | 4 | return ByteSource.Util.bytes(output); |
345 | } | |
346 | ||
347 | public ByteSource decrypt(byte[] ciphertext, byte[] key) throws CryptoException { | |
348 | ||
349 | 5 | byte[] encrypted = ciphertext; |
350 | ||
351 | //No IV, check if we need to read the IV from the stream: | |
352 | 5 | byte[] iv = null; |
353 | ||
354 | 5 | if (isGenerateInitializationVectors(false)) { |
355 | try { | |
356 | //We are generating IVs, so the ciphertext argument array is not actually 100% cipher text. Instead, it | |
357 | //is: | |
358 | // - the first N bytes is the initialization vector, where N equals the value of the | |
359 | // 'initializationVectorSize' attribute. | |
360 | // - the remaining bytes in the method argument (arg.length - N) is the real cipher text. | |
361 | ||
362 | //So we need to chunk the method argument into its constituent parts to find the IV and then use | |
363 | //the IV to decrypt the real ciphertext: | |
364 | ||
365 | 5 | int ivSize = getInitializationVectorSize(); |
366 | 5 | int ivByteSize = ivSize / BITS_PER_BYTE; |
367 | ||
368 | //now we know how large the iv is, so extract the iv bytes: | |
369 | 5 | iv = new byte[ivByteSize]; |
370 | 5 | System.arraycopy(ciphertext, 0, iv, 0, ivByteSize); |
371 | ||
372 | //remaining data is the actual encrypted ciphertext. Isolate it: | |
373 | 4 | int encryptedSize = ciphertext.length - ivByteSize; |
374 | 4 | encrypted = new byte[encryptedSize]; |
375 | 4 | System.arraycopy(ciphertext, ivByteSize, encrypted, 0, encryptedSize); |
376 | 1 | } catch (Exception e) { |
377 | 1 | String msg = "Unable to correctly extract the Initialization Vector or ciphertext."; |
378 | 1 | throw new CryptoException(msg, e); |
379 | 4 | } |
380 | } | |
381 | ||
382 | 4 | return decrypt(encrypted, key, iv); |
383 | } | |
384 | ||
385 | private ByteSource decrypt(byte[] ciphertext, byte[] key, byte[] iv) throws CryptoException { | |
386 | 4 | if (log.isTraceEnabled()) { |
387 | 4 | log.trace("Attempting to decrypt incoming byte array of length " + |
388 | (ciphertext != null ? ciphertext.length : 0)); | |
389 | } | |
390 | 4 | byte[] decrypted = crypt(ciphertext, key, iv, javax.crypto.Cipher.DECRYPT_MODE); |
391 | 4 | return decrypted == null ? null : ByteSource.Util.bytes(decrypted); |
392 | } | |
393 | ||
394 | /** | |
395 | * Returns a new {@link javax.crypto.Cipher Cipher} instance to use for encryption/decryption operations. The | |
396 | * Cipher's {@code transformationString} for the {@code Cipher}.{@link javax.crypto.Cipher#getInstance getInstance} | |
397 | * call is obtaind via the {@link #getTransformationString(boolean) getTransformationString} method. | |
398 | * | |
399 | * @param streaming {@code true} if the cipher instance will be used as a stream cipher, {@code false} if it will be | |
400 | * used as a block cipher. | |
401 | * @return a new JDK {@code Cipher} instance. | |
402 | * @throws CryptoException if a new Cipher instance cannot be constructed based on the | |
403 | * {@link #getTransformationString(boolean) getTransformationString} value. | |
404 | */ | |
405 | private javax.crypto.Cipher newCipherInstance(boolean streaming) throws CryptoException { | |
406 | 16 | String transformationString = getTransformationString(streaming); |
407 | try { | |
408 | 16 | return javax.crypto.Cipher.getInstance(transformationString); |
409 | 0 | } catch (Exception e) { |
410 | 0 | 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 | 0 | throw new CryptoException(msg, e); |
415 | } | |
416 | } | |
417 | ||
418 | /** | |
419 | * Functions as follows: | |
420 | * <ol> | |
421 | * <li>Creates a {@link #newCipherInstance(boolean) new JDK cipher instance}</li> | |
422 | * <li>Converts the specified key bytes into an {@link #getAlgorithmName() algorithm}-compatible JDK | |
423 | * {@link Key key} instance</li> | |
424 | * <li>{@link #init(javax.crypto.Cipher, int, java.security.Key, AlgorithmParameterSpec, SecureRandom) Initializes} | |
425 | * the JDK cipher instance with the JDK key</li> | |
426 | * <li>Calls the {@link #crypt(javax.crypto.Cipher, byte[]) crypt(cipher,bytes)} method to either encrypt or | |
427 | * decrypt the data based on the specified Cipher behavior mode | |
428 | * ({@link javax.crypto.Cipher#ENCRYPT_MODE Cipher.ENCRYPT_MODE} or | |
429 | * {@link javax.crypto.Cipher#DECRYPT_MODE Cipher.DECRYPT_MODE})</li> | |
430 | * </ol> | |
431 | * | |
432 | * @param bytes the bytes to crypt | |
433 | * @param key the key to use to perform the encryption or decryption. | |
434 | * @param iv the initialization vector to use for the crypt operation (optional, may be {@code null}). | |
435 | * @param mode the JDK Cipher behavior mode (Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE). | |
436 | * @return the resulting crypted byte array | |
437 | * @throws IllegalArgumentException if {@code bytes} are null or empty. | |
438 | * @throws CryptoException if Cipher initialization or the crypt operation fails | |
439 | */ | |
440 | private byte[] crypt(byte[] bytes, byte[] key, byte[] iv, int mode) throws IllegalArgumentException, CryptoException { | |
441 | 8 | if (key == null || key.length == 0) { |
442 | 0 | throw new IllegalArgumentException("key argument cannot be null or empty."); |
443 | } | |
444 | 8 | javax.crypto.Cipher cipher = initNewCipher(mode, key, iv, false); |
445 | 8 | return crypt(cipher, bytes); |
446 | } | |
447 | ||
448 | /** | |
449 | * Calls the {@link javax.crypto.Cipher#doFinal(byte[]) doFinal(bytes)} method, propagating any exception that | |
450 | * might arise in an {@link CryptoException} | |
451 | * | |
452 | * @param cipher the JDK Cipher to finalize (perform the actual cryption) | |
453 | * @param bytes the bytes to crypt | |
454 | * @return the resulting crypted byte array. | |
455 | * @throws CryptoException if there is an illegal block size or bad padding | |
456 | */ | |
457 | private byte[] crypt(javax.crypto.Cipher cipher, byte[] bytes) throws CryptoException { | |
458 | try { | |
459 | 8 | return cipher.doFinal(bytes); |
460 | 0 | } catch (Exception e) { |
461 | 0 | String msg = "Unable to execute 'doFinal' with cipher instance [" + cipher + "]."; |
462 | 0 | throw new CryptoException(msg, e); |
463 | } | |
464 | } | |
465 | ||
466 | /** | |
467 | * Initializes the JDK Cipher with the specified mode and key. This is primarily a utility method to catch any | |
468 | * potential {@link java.security.InvalidKeyException InvalidKeyException} that might arise. | |
469 | * | |
470 | * @param cipher the JDK Cipher to {@link javax.crypto.Cipher#init(int, java.security.Key) init}. | |
471 | * @param mode the Cipher mode | |
472 | * @param key the Cipher's Key | |
473 | * @param spec the JDK AlgorithmParameterSpec for cipher initialization (optional, may be null). | |
474 | * @param random the SecureRandom to use for cipher initialization (optional, may be null). | |
475 | * @throws CryptoException if the key is invalid | |
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 | 16 | if (random != null) { |
481 | 0 | if (spec != null) { |
482 | 0 | cipher.init(mode, key, spec, random); |
483 | } else { | |
484 | 0 | cipher.init(mode, key, random); |
485 | } | |
486 | } else { | |
487 | 16 | if (spec != null) { |
488 | 16 | cipher.init(mode, key, spec); |
489 | } else { | |
490 | 0 | cipher.init(mode, key); |
491 | } | |
492 | } | |
493 | 0 | } catch (Exception e) { |
494 | 0 | String msg = "Unable to init cipher instance."; |
495 | 0 | throw new CryptoException(msg, e); |
496 | 16 | } |
497 | 16 | } |
498 | ||
499 | ||
500 | public void encrypt(InputStream in, OutputStream out, byte[] key) throws CryptoException { | |
501 | 4 | byte[] iv = null; |
502 | 4 | boolean generate = isGenerateInitializationVectors(true); |
503 | 4 | if (generate) { |
504 | 4 | iv = generateInitializationVector(true); |
505 | 4 | if (iv == null || iv.length == 0) { |
506 | 0 | throw new IllegalStateException("Initialization vector generation is enabled - generated vector" + |
507 | "cannot be null or empty."); | |
508 | } | |
509 | } | |
510 | 4 | encrypt(in, out, key, iv, generate); |
511 | 4 | } |
512 | ||
513 | private void encrypt(InputStream in, OutputStream out, byte[] key, byte[] iv, boolean prependIv) throws CryptoException { | |
514 | 4 | if (prependIv && iv != null && iv.length > 0) { |
515 | try { | |
516 | //first write the IV: | |
517 | 4 | out.write(iv); |
518 | 0 | } catch (IOException e) { |
519 | 0 | throw new CryptoException(e); |
520 | 4 | } |
521 | } | |
522 | ||
523 | 4 | crypt(in, out, key, iv, javax.crypto.Cipher.ENCRYPT_MODE); |
524 | 4 | } |
525 | ||
526 | public void decrypt(InputStream in, OutputStream out, byte[] key) throws CryptoException { | |
527 | 4 | decrypt(in, out, key, isGenerateInitializationVectors(true)); |
528 | 4 | } |
529 | ||
530 | private void decrypt(InputStream in, OutputStream out, byte[] key, boolean ivPrepended) throws CryptoException { | |
531 | ||
532 | 4 | byte[] iv = null; |
533 | //No Initialization Vector provided as a method argument - check if we need to read the IV from the stream: | |
534 | 4 | if (ivPrepended) { |
535 | //we are generating IVs, so we need to read the previously-generated IV from the stream before | |
536 | //we decrypt the rest of the stream (we need the IV to decrypt): | |
537 | 4 | int ivSize = getInitializationVectorSize(); |
538 | 4 | int ivByteSize = ivSize / BITS_PER_BYTE; |
539 | 4 | iv = new byte[ivByteSize]; |
540 | int read; | |
541 | ||
542 | try { | |
543 | 4 | read = in.read(iv); |
544 | 0 | } catch (IOException e) { |
545 | 0 | String msg = "Unable to correctly read the Initialization Vector from the input stream."; |
546 | 0 | throw new CryptoException(msg, e); |
547 | 4 | } |
548 | ||
549 | 4 | if (read != ivByteSize) { |
550 | 0 | 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 | 4 | decrypt(in, out, key, iv); |
557 | 4 | } |
558 | ||
559 | private void decrypt(InputStream in, OutputStream out, byte[] decryptionKey, byte[] iv) throws CryptoException { | |
560 | 4 | crypt(in, out, decryptionKey, iv, javax.crypto.Cipher.DECRYPT_MODE); |
561 | 4 | } |
562 | ||
563 | private void crypt(InputStream in, OutputStream out, byte[] keyBytes, byte[] iv, int cryptMode) throws CryptoException { | |
564 | 8 | if (in == null) { |
565 | 0 | throw new NullPointerException("InputStream argument cannot be null."); |
566 | } | |
567 | 8 | if (out == null) { |
568 | 0 | throw new NullPointerException("OutputStream argument cannot be null."); |
569 | } | |
570 | ||
571 | 8 | javax.crypto.Cipher cipher = initNewCipher(cryptMode, keyBytes, iv, true); |
572 | ||
573 | 8 | CipherInputStream cis = new CipherInputStream(in, cipher); |
574 | ||
575 | 8 | int bufSize = getStreamingBufferSize(); |
576 | 8 | byte[] buffer = new byte[bufSize]; |
577 | ||
578 | int bytesRead; | |
579 | try { | |
580 | 24 | while ((bytesRead = cis.read(buffer)) != -1) { |
581 | 16 | out.write(buffer, 0, bytesRead); |
582 | } | |
583 | 0 | } catch (IOException e) { |
584 | 0 | throw new CryptoException(e); |
585 | 8 | } |
586 | 8 | } |
587 | ||
588 | private javax.crypto.Cipher initNewCipher(int jcaCipherMode, byte[] key, byte[] iv, boolean streaming) | |
589 | throws CryptoException { | |
590 | ||
591 | 16 | javax.crypto.Cipher cipher = newCipherInstance(streaming); |
592 | 16 | java.security.Key jdkKey = new SecretKeySpec(key, getAlgorithmName()); |
593 | 16 | IvParameterSpec ivSpec = null; |
594 | 16 | if (iv != null && iv.length > 0) { |
595 | 16 | ivSpec = new IvParameterSpec(iv); |
596 | } | |
597 | ||
598 | 16 | init(cipher, jcaCipherMode, jdkKey, ivSpec, getSecureRandom()); |
599 | ||
600 | 16 | return cipher; |
601 | } | |
602 | } |