To perform password-based encryption, we've seen that we need a random salt sequence to prevent dictionary attacks, and that we need to use a deliberately slow function to generate a key (such as an AES or DES key) from a given password and salt. So what function do we use?
There are several possible variations, but a common scheme is as follows:
Out of the box, Sun's JDK supports password-based encryption ciphers, though sadly not in a terribly useful way. Apart from the clumsiness of the API, the three schemes supported are to varying degrees obsolete by today's standards. At least the PBEWithMD5AndTripleDES scheme may be a suitable choice (but a slow one) if you resign yourself to the fact that the passwords used by your users aren't very secure anyway. The others are a bit pointless except for interfacing with legacy systems. Anyway, here are the three algorithms:
Note that the DES-based algorithms restrict the salt to 8 bytes.
If you must use one of the above schemes, then the code to do so looks as follows. We create a PBEKeySpec object that encapsulates the password, then a PBEParameterSpec object that encapsulates the salt and iteration count (the number of times the hash function will be applied to derive the symmetric key from the salt and password: typical values would be between 1000 and 2000):
public static byte[] encrypt(byte[] data, char[] password, byte[] salt, int noIterations) { try { String method = "PBEWithMD5AndTripleDES"; SecretKeyFactory kf = SecretKeyFactory.getInstance(method); PBEKeySpec keySpec = new PBEKeySpec(password); SecretKey key = kf.generateSecret(keySpec); Cipher ciph = Cipher.getInstance(method); PBEParameterSpec params = new PBEParameterSpec(salt, noIterations); return ciph.doFinal(data); } catch (Exception e) { throw new RuntimeException("Spurious encryption error"); } }
Notice that it's common to handle passwords as char arrays, not as String objects. The rationale is that with a char array, we can blank the array (fill it with zeroes) once we've finished with it, and thus reduce the risk of the password hanging about in memory and ending up in a swap/page file. (It's a fairly shaky guarantee— in reality, the machine could be put into hybernation at any moment— but it's the best we have.)
1. There's not necessarily a known practical attack against iterated MD5 operations, as used in password-based encryption. But why take the risk?
If you enjoy this Java programming article, please share with friends and colleagues. Follow the author on Twitter for the latest news and rants. Follow @BitterCoffey
Editorial page content written by Neil Coffey. Copyright © Javamex UK 2021. All rights reserved.