如何使用PBKDF2在Java和Ruby中生成相同的安全哈希

时间:2015-05-28 19:36:09

标签: java ruby hash pbkdf2

我正在将一个Web应用程序从Ruby移植到Java,并且希望允许用户在不重置密码的情况下登录。以下是使用pbkdf2 gem生成哈希的Ruby代码:

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

读取Ruby gem的源代码,它使用OpenSSL :: Digest.new(" sha256")作为默认的散列函数,并生成一个32字节的值,转换为64个字符的字符串使用'解包(" H *")'。

所以,在Java中我尝试了以下内容:

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);
}

使用密码=" apassword"测试两段代码和盐=" somesalt",我得到以下结果。

Ruby: 3fa1eb7544ca49b1f371eb17d24bf0010c433fa263a84aff7df446741371706b

Java: 77a7c0b1ea9760d0b1ef02e7a2633c40ccd7848ee4fa822ec71b5794e476f354

我测试了Ruby和Java十六进制字符串编码,它们的工作方式相同,所以问题看起来是在散列算法中,或者可能是字符串转换为字节数组的方式。

1 个答案:

答案 0 :(得分:5)

问题在于迭代次数。如果你用Java改为10,000而不是你正在使用的1000,它会给你一个与你在Ruby中得到的结果相同的结果:

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

附加说明:

始终最好确保根据已知字符集从字符串中获取字节。如果没有字符集,它将使用默认字符集,并且可能会导致意外。

此外,最好不要依赖BigInteger.toString(16),因为如果前几个字节为0,它将返回一个短于64个字符的字符串。请改用String.format()

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);
}

(我假设salt是纯ASCII文本。您可以更改字符集,但请记住使用Ruby使用的相同字符集。