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:

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.