kensei62
kensei62

Reputation: 164

How can I generate the same secure hash in Java and Ruby using PBKDF2

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.

Upvotes: 4

Views: 1200

Answers (1)

RealSkeptic
RealSkeptic

Reputation: 34638

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).

Upvotes: 5

Related Questions