在Python中复制Java密码哈希代码(PBKDF2WithHmacSHA1)

时间:2016-12-06 10:54:20

标签: java python encryption hash password-encryption

我一直在尝试将java密码身份验证复制到python,但结果哈希是不同的。

密码: abcd1234

密码令牌(java): $ 31 $ 16 $ sWy1dDEx52vwQUCswXDYMQMzTJC39g1_nmrK384T4-w

生成密码令牌(python): $ pbkdf2 $ 16 $ c1d5MWRERXg1MnZ3UVVDcw $ qPQvE4QbrnYJTmRXk0M7wlfhH5U

从Java代码中,Iteration是16,SALT应该是sWy1dDEx52vwQUCswXDYMQMzTJC39g1_nmrK384T4-w中的前16个字符,即sWy1dDEx52vwQUCs,哈希应该是 wXDYMQMzTJC39g1_nmrK384T4-w

然而,将变量应用于python给了我一个不同的哈希结果, qPQvE4QbrnYJTmRXk0M7wlfhH5U ,这与Java的哈希不同。

我错过了哪里?

爪哇:

private static final String ALGORITHM = "PBKDF2WithHmacSHA1";
private static final int SIZE = 128;
private static final Pattern layout = Pattern.compile("\\$31\\$(\\d\\d?)\\$(.{43})");

public boolean authenticate(char[] password, String token)
   {
       Matcher m = layout.matcher(token);
       if (!m.matches())
           throw new IllegalArgumentException("Invalid token format");
       int iterations = iterations(Integer.parseInt(m.group(1)));
       byte[] hash = Base64.getUrlDecoder().decode(m.group(2));
       byte[] salt = Arrays.copyOfRange(hash, 0, SIZE / 8);
       byte[] check = pbkdf2(password, salt, iterations);
       int zero = 0;
       for (int idx = 0; idx < check.length; ++idx)
           zero |= hash[salt.length + idx] ^ check[idx];
       return zero == 0;
   }

的Python:

from passlib.hash import pbkdf2_sha1

def hasher(password):
   size = 128

   key0 = "abcd1234"
   iter = int(password.split("$")[2])
   salt0 = password.split("$")[3][0: 16]

   hash = pbkdf2_sha1.using(rounds=iter, salt = salt0.encode()).hash(key0)
   print(hash.split('$')[4])

   return hash

Java代码的原始链接:How can I hash a password in Java?

1 个答案:

答案 0 :(得分:3)

java代码如何处理事务,以及passlib的pbkdf2_sha1 hasher如何处理事情之间存在许多不同之处。

  • java哈希字符串包含一个日志成本参数,需要通过1<<cost来获取轮次/迭代次数。

  • salt + digest需要被base64解码,然后将前16个字节作为salt(实际上对应于base64数据的前21个1/3字符)。

  • 类似地,由于摘要的位在base64字符的中间开始,当解码salt +摘要时,然后digest被单独编码,base64字符串将是 AzNMkLf2DX-easrfzhPj7A(与原始编码字符串明显不同)。

基于此,以下代码将java哈希转换为pbkdf1_sha1.verify使用的格式:

from passlib.utils.binary import b64s_decode, ab64_encode

def adapt_java_hash(jhash):
    _, ident, cost, data = jhash.split("$")
    assert ident == "31"
    data = b64s_decode(data.replace("_", ".").replace("-", "+"))
    return "$pbkdf2$%d$%s$%s" % (1<<int(cost), ab64_encode(data[:16]),
                                 ab64_encode(data[16:]))

>>> adapt_java_hash("$31$16$sWy1dDEx52vwQUCswXDYMQMzTJC39g1_nmrK384T4-w")
'$pbkdf2$65536$sWy1dDEx52vwQUCswXDYMQ$AzNMkLf2DX.easrfzhPj7A'

结果字符串应该适合传入pbkdf2_sha1.verify("abcd1234", hash); 之外的一个问题: java代码将sha1摘要截断为16个字节,而不是整个20个字节;并且对passlib的hasher进行编码,摘要必须是完整的20个字节。

如果你改变java代码使用SIZE = 160而不是SIZE = 128,那么通过上面的adapt()函数运行哈希应该在passlib中工作。