4

I am porting a web application from Ruby to Java and would like to allow the users to log in without resetting their passwords. Here is the Ruby code that generates the hash using the pbkdf2 gem:

PBKDF2.new { |p|
  p.password = password
  p.salt = salt
  p.iterations = 10000
}.hex_string

Reading the source for the Ruby gem, it is using OpenSSL::Digest.new("sha256") as the default hashing function and generates a value of 32 bytes, which converts to a 64 character string using 'unpack("H*")'.

So, in Java I tried the following:

public String generatePasswordHash(String password, String salt) throws NoSuchAlgorithmException, InvalidKeySpecException
{
    char[] chars = password.toCharArray();
    byte[] saltBytes =salt.getBytes();

    PBEKeySpec spec = new PBEKeySpec(chars, saltBytes, 1000, 256);
    SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
    byte[] hash = skf.generateSecret(spec).getEncoded();
    BigInteger bi = new BigInteger(1, hash);
    return bi.toString(16);
}

Testing both pieces of code with password = "apassword" and salt = "somesalt", I get the following results.

Ruby: 3fa1eb7544ca49b1f371eb17d24bf0010c433fa263a84aff7df446741371706b

Java: 77a7c0b1ea9760d0b1ef02e7a2633c40ccd7848ee4fa822ec71b5794e476f354

I tested the Ruby and Java hex string encoding and they work the same so the problem looks to be in the hashing algorithm or perhaps the way the Strings are converted to byte arrays.

2
  • 1
    Your Ruby code has p.iterations = 10000. I don't see anything in your Java with a 10000 in it. Commented May 28, 2015 at 19:38
  • Wow, I missed the extra '0' in the number. Thanks! Commented May 28, 2015 at 23:37

1 Answer 1

5

The problem is in the number of iterations. If you change it to 10,000 in Java instead of the 1,000 you are using, it will give you an identical result to the one you got in Ruby:

    PBEKeySpec spec = new PBEKeySpec(chars, saltBytes, 10000, 256);

Additional notes:

It's always best to ensure that bytes are taken from a string according to a known character set. Without the character set, it uses whatever the default character set is, and it may cause surprises.

Also, it's best not to rely on BigInteger.toString(16), because if the first few bytes are 0, it will return a string that is shorter than 64 characters. Use String.format() instead:

public static String generatePasswordHash(String password, String salt) throws NoSuchAlgorithmException, InvalidKeySpecException
{
    char[] chars = password.toCharArray();
    byte[] saltBytes =salt.getBytes(StandardCharsets.US_ASCII);

    PBEKeySpec spec = new PBEKeySpec(chars, saltBytes, 10000, 256);
    SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
    byte[] hash = skf.generateSecret(spec).getEncoded();
    BigInteger bi = new BigInteger(1, hash);
    return String.format("%064x", bi);
}

(I assumed the salt is in plain ASCII text. You can change the character set, but keep in mind to use the same character set Ruby uses).

Sign up to request clarification or add additional context in comments.

1 Comment

I can't believe I missed the extra '0' on iterations. Thanks for the additional notes as well!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.