Reputation: 164
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
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