Password-based encryption in Java: key derivation
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:
- we append the password to the salt, and also append a counter,
which will start at 1;
- we calculate a secure hash of the sequence
we just created;
- we then repeat the process for some number of iterations, each
time forming the new sequence to be hashed from the output of the previous hash,
and appending the salt and incremented counter.
Using PBE in Java "out of the box"
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:
- PBEWithSHA1AndRC2_40
- This mode encrypts with 40-bit RC2. As an encryption scheme, this has pretty much nothing
going for it by today's standards. It was once a useful option when the US
had a ludicrous export policy that made it difficult or illegal
to export cryptography software with stronger than 40-bit encryption. By today's
standards (in fact, by pretty much any standards), 40-bit encryption is a joke.
- PBEWithMD5AndDES
- This option combines all the benefits of slow, insecure 56-bit encryption with
an insecure hash function (MD5). Don't use it, except to interface with legacy code.
- PBEWithMD5AndTripleDES
- Overall, this option is probably more-or-less secure. It uses the
triple DES algorithm, which gives up to 112-bit security. However, this is a
very slow algorithm for that level of security and the key is generated using
the MD5 hash algorithm, now considered insecure1. If you must use one of the
built-in PBE schemes, use this. But if possible, don't.
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.
Editorial page content written by Neil Coffey. Copyright © Javamex UK 2021. All rights reserved.