javapbkdf2java-securityibm-jdk

SecretKeyFactory.generateSecret dies with InvalidKeySpecException on IBM Java


We are hashing a password using the PBKDF2 algorithm, using the SecretKeyFactory.generateSecret function, like this:

final SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm);
final PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iterations, hashLength);
final SecretKey secretKey = secretKeyFactory.generateSecret(keySpec);
return secretKey.getEncoded();

Everything seems to work fine, however, on the production server, when it is running in IBM Java, it dies with java.security.spec.InvalidKeySpecException: Could not generate secret key:

Caused by: java.security.spec.InvalidKeySpecException: Could not generate secret key
    at javax.crypto.SecretKeyFactory.generateSecret(Unknown Source)
    at our.Implementation.doHash(Hasher.java:71)
    ... 48 more
Caused by: java.lang.RuntimeException: Error deriving PBKDF2 keys
    at com.ibm.crypto.provider.PBKDF2KeyImpl.a(Unknown Source)
    at com.ibm.crypto.provider.PBKDF2KeyImpl.<init>(Unknown Source)
    at com.ibm.crypto.provider.PBKDF2HmacSHA1Factory.engineGenerateSecret(Unknown Source)
    ... 50 more

We tried to change the iteration count, generated hash size, and the salt size, but nothing helped. What am I doing wrong?


Solution

  • Apparently, it seems the IBM Java implementation of PBKDF2 insists the password must not be empty. When it is, the implementation throws an exception.

    Empty passwords are arguably an edge case, however, the first test case was exactly that, so it seemed the code did not work at all. Also, while an edge case, you either need to ensure it never happens (validations requiring the user not to have empty password), or you should handle it gracefully.

    If you really need to support empty passwords, you either need to handle them as a special case, or just normalize all passwords so that the input to PBKDF2 is never empty. However, you have to ensure you won’t compromise security that way.

    In the end, we chose to prefix all passwords with their length, so that even the empty passwords ends being represented with a nonempty string. Like that:

    final String prefixedPassword = String.format(Locale.ROOT, "%08x%s", password.length(), password);