· 6 years ago · Aug 24, 2019, 09:46 AM
1/*
2 * ----------------------------------------------------------------------------
3 * "THE BEER-WARE LICENSE" (Revision 42):
4 * <dweymouth@gmail.com> wrote this file. As long as you retain this notice you
5 * can do whatever you want with this stuff. If we meet some day, and you think
6 * this stuff is worth it, you can buy me a beer in return. D. Weymouth 4/2014
7 * ----------------------------------------------------------------------------
8 */
9
10import java.io.*;
11import java.security.*;
12import java.security.spec.*;
13import java.util.*;
14
15import javax.crypto.*;
16import javax.crypto.spec.*;
17
18/**
19 * A class to perform password-based AES encryption and decryption in CBC mode.
20 * 128, 192, and 256-bit encryption are supported, provided that the latter two
21 * are permitted by the Java runtime's jurisdiction policy files.
22 * <br/>
23 * The public interface for this class consists of the static methods
24 * {@link #encrypt} and {@link #decrypt}, which encrypt and decrypt arbitrary
25 * streams of data, respectively.
26 */
27public class AES {
28
29 // AES specification - changing will break existing encrypted streams!
30 private static final String CIPHER_SPEC = "AES/CBC/PKCS5Padding";
31
32 // Key derivation specification - changing will break existing streams!
33 private static final String KEYGEN_SPEC = "PBKDF2WithHmacSHA1";
34 private static final int SALT_LENGTH = 16; // in bytes
35 private static final int AUTH_KEY_LENGTH = 8; // in bytes
36 private static final int ITERATIONS = 32768;
37
38 // Process input/output streams in chunks - arbitrary
39 private static final int BUFFER_SIZE = 1024;
40
41
42 /**
43 * @return a new pseudorandom salt of the specified length
44 */
45 private static byte[] generateSalt(int length) {
46 Random r = new SecureRandom();
47 byte[] salt = new byte[length];
48 r.nextBytes(salt);
49 return salt;
50 }
51
52 /**
53 * Derive an AES encryption key and authentication key from given password and salt,
54 * using PBKDF2 key stretching. The authentication key is 64 bits long.
55 * @param keyLength
56 * length of the AES key in bits (128, 192, or 256)
57 * @param password
58 * the password from which to derive the keys
59 * @param salt
60 * the salt from which to derive the keys
61 * @return a Keys object containing the two generated keys
62 */
63 private static Keys keygen(int keyLength, char[] password, byte[] salt) {
64 SecretKeyFactory factory;
65 try {
66 factory = SecretKeyFactory.getInstance(KEYGEN_SPEC);
67 } catch (NoSuchAlgorithmException impossible) { return null; }
68 // derive a longer key, then split into AES key and authentication key
69 KeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, keyLength + AUTH_KEY_LENGTH * 8);
70 SecretKey tmp = null;
71 try {
72 tmp = factory.generateSecret(spec);
73 } catch (InvalidKeySpecException impossible) { }
74 byte[] fullKey = tmp.getEncoded();
75 SecretKey authKey = new SecretKeySpec( // key for password authentication
76 Arrays.copyOfRange(fullKey, 0, AUTH_KEY_LENGTH), "AES");
77 SecretKey encKey = new SecretKeySpec( // key for AES encryption
78 Arrays.copyOfRange(fullKey, AUTH_KEY_LENGTH, fullKey.length), "AES");
79 return new Keys(encKey, authKey);
80 }
81
82 /**
83 * Encrypts a stream of data. The encrypted stream consists of a header
84 * followed by the raw AES data. The header is broken down as follows:<br/>
85 * <ul>
86 * <li><b>keyLength</b>: AES key length in bytes (valid for 16, 24, 32) (1 byte)</li>
87 * <li><b>salt</b>: pseudorandom salt used to derive keys from password (16 bytes)</li>
88 * <li><b>authentication key</b> (derived from password and salt, used to
89 * check validity of password upon decryption) (8 bytes)</li>
90 * <li><b>IV</b>: pseudorandom AES initialization vector (16 bytes)</li>
91 * </ul>
92 *
93 * @param keyLength
94 * key length to use for AES encryption (must be 128, 192, or 256)
95 * @param password
96 * password to use for encryption
97 * @param input
98 * an arbitrary byte stream to encrypt
99 * @param output
100 * stream to which encrypted data will be written
101 * @throws AES.InvalidKeyLengthException
102 * if keyLength is not 128, 192, or 256
103 * @throws AES.StrongEncryptionNotAvailableException
104 * if keyLength is 192 or 256, but the Java runtime's jurisdiction
105 * policy files do not allow 192- or 256-bit encryption
106 * @throws IOException
107 */
108 public static void encrypt(int keyLength, char[] password, InputStream input, OutputStream output)
109 throws InvalidKeyLengthException, StrongEncryptionNotAvailableException, IOException {
110 // Check validity of key length
111 if (keyLength != 128 && keyLength != 192 && keyLength != 256) {
112 throw new InvalidKeyLengthException(keyLength);
113 }
114
115 // generate salt and derive keys for authentication and encryption
116 byte[] salt = generateSalt(SALT_LENGTH);
117 Keys keys = keygen(keyLength, password, salt);
118
119 // initialize AES encryption
120 Cipher encrypt = null;
121 try {
122 encrypt = Cipher.getInstance(CIPHER_SPEC);
123 encrypt.init(Cipher.ENCRYPT_MODE, keys.encryption);
124 } catch (NoSuchAlgorithmException | NoSuchPaddingException impossible) { }
125 catch (InvalidKeyException e) { // 192 or 256-bit AES not available
126 throw new StrongEncryptionNotAvailableException(keyLength);
127 }
128
129 // get initialization vector
130 byte[] iv = null;
131 try {
132 iv = encrypt.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
133 } catch (InvalidParameterSpecException impossible) { }
134
135 // write authentication and AES initialization data
136 output.write(keyLength / 8);
137 output.write(salt);
138 output.write(keys.authentication.getEncoded());
139 output.write(iv);
140
141 // read data from input into buffer, encrypt and write to output
142 byte[] buffer = new byte[BUFFER_SIZE];
143 int numRead;
144 byte[] encrypted = null;
145 while ((numRead = input.read(buffer)) > 0) {
146 encrypted = encrypt.update(buffer, 0, numRead);
147 if (encrypted != null) {
148 output.write(encrypted);
149 }
150 }
151 try { // finish encryption - do final block
152 encrypted = encrypt.doFinal();
153 } catch (IllegalBlockSizeException | BadPaddingException impossible) { }
154 if (encrypted != null) {
155 output.write(encrypted);
156 }
157 }
158
159 /**
160 * Decrypts a stream of data that was encrypted by {@link #encrypt}.
161 * @param password
162 * the password used to encrypt/decrypt the stream
163 * @param input
164 * stream of encrypted data to be decrypted
165 * @param output
166 * stream to which decrypted data will be written
167 * @return the key length for the decrypted stream (128, 192, or 256)
168 * @throws AES.InvalidPasswordException
169 * if the given password was not used to encrypt the data
170 * @throws AES.InvalidAESStreamException
171 * if the given input stream is not a valid AES-encrypted stream
172 * @throws AES.StrongEncryptionNotAvailableException
173 * if the stream is 192 or 256-bit encrypted, and the Java runtime's
174 * jurisdiction policy files do not allow for AES-192 or 256
175 * @throws IOException
176 */
177 public static int decrypt(char[] password, InputStream input, OutputStream output)
178 throws InvalidPasswordException, InvalidAESStreamException, IOException,
179 StrongEncryptionNotAvailableException {
180 int keyLength = input.read() * 8;
181 // Check validity of key length
182 if (keyLength != 128 && keyLength != 192 && keyLength != 256) {
183 throw new InvalidAESStreamException();
184 }
185
186 // read salt, generate keys, and authenticate password
187 byte[] salt = new byte[SALT_LENGTH];
188 input.read(salt);
189 Keys keys = keygen(keyLength, password, salt);
190 byte[] authRead = new byte[AUTH_KEY_LENGTH];
191 input.read(authRead);
192 if (!Arrays.equals(keys.authentication.getEncoded(), authRead)) {
193 throw new InvalidPasswordException();
194 }
195
196 // initialize AES decryption
197 byte[] iv = new byte[16]; // 16-byte I.V. regardless of key size
198 input.read(iv);
199 Cipher decrypt = null;
200 try {
201 decrypt = Cipher.getInstance(CIPHER_SPEC);
202 decrypt.init(Cipher.DECRYPT_MODE, keys.encryption, new IvParameterSpec(iv));
203 } catch (NoSuchAlgorithmException | NoSuchPaddingException
204 | InvalidAlgorithmParameterException impossible) { }
205 catch (InvalidKeyException e) { // 192 or 256-bit AES not available
206 throw new StrongEncryptionNotAvailableException(keyLength);
207 }
208
209 // read data from input into buffer, decrypt and write to output
210 byte[] buffer = new byte[BUFFER_SIZE];
211 int numRead;
212 byte[] decrypted;
213 while ((numRead = input.read(buffer)) > 0) {
214 decrypted = decrypt.update(buffer, 0, numRead);
215 if (decrypted != null) {
216 output.write(decrypted);
217 }
218 }
219 try { // finish decryption - do final block
220 decrypted = decrypt.doFinal();
221 } catch (IllegalBlockSizeException | BadPaddingException e) {
222 throw new InvalidAESStreamException(e);
223 }
224 if (decrypted != null) {
225 output.write(decrypted);
226 }
227
228 return keyLength;
229 }
230
231 /**
232 * A tuple of encryption and authentication keys returned by {@link #keygen}
233 */
234 private static class Keys {
235 public final SecretKey encryption, authentication;
236 public Keys(SecretKey encryption, SecretKey authentication) {
237 this.encryption = encryption;
238 this.authentication = authentication;
239 }
240 }
241
242
243 //******** EXCEPTIONS thrown by encrypt and decrypt ********
244
245 /**
246 * Thrown if an attempt is made to decrypt a stream with an incorrect password.
247 */
248 public static class InvalidPasswordException extends Exception { }
249
250 /**
251 * Thrown if an attempt is made to encrypt a stream with an invalid AES key length.
252 */
253 public static class InvalidKeyLengthException extends Exception {
254 InvalidKeyLengthException(int length) {
255 super("Invalid AES key length: " + length);
256 }
257 }
258
259 /**
260 * Thrown if 192- or 256-bit AES encryption or decryption is attempted,
261 * but not available on the particular Java platform.
262 */
263 public static class StrongEncryptionNotAvailableException extends Exception {
264 public StrongEncryptionNotAvailableException(int keySize) {
265 super(keySize + "-bit AES encryption is not available on this Java platform.");
266 }
267 }
268
269 /**
270 * Thrown if an attempt is made to decrypt an invalid AES stream.
271 */
272 public static class InvalidAESStreamException extends Exception {
273 public InvalidAESStreamException() { super(); };
274 public InvalidAESStreamException(Exception e) { super(e); }
275 }
276
277}