在 Python 中复制 Java 密码哈希代码 (PBKDF2WithHmacSHA1)
Replicating Java password hashing code in Python (PBKDF2WithHmacSHA1)
我一直在尝试将 java 密码验证复制到 python,但结果哈希不同。
密码:abcd1234
密码令牌 (java): $31$16$sWy1dDEx52vwQUCswXDYMQMzTJC39g1_nmrK384T4-w
生成的密码令牌(python):$pbkdf2$16$c1d5MWRERXg1MnZ3UVVDcw$qPQvE4QbrnYJTmRXk0M7wlfhH5U
根据Java代码,Iteration是16,SALT应该是sWy1dDEx52vwQUCswXDYMQMzTJC39g1_nmrK384T4-w中的前16个字符,也就是sWy1dDEx52vwQUCs,hash应该是wXDYMQMzTJC39g1_nmrK384T4-w
然而,将变量应用于 python 给了我一个不同的哈希结果,qPQvE4QbrnYJTmRXk0M7wlfhH5U 与 Java 的哈希不同。
我哪里漏掉了?
Java:
private static final String ALGORITHM = "PBKDF2WithHmacSHA1";
private static final int SIZE = 128;
private static final Pattern layout = Pattern.compile("\\$(\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
原始 Link Java 代码:How can I hash a password in Java?
java 代码的工作方式与 passlib 的 pbkdf2_sha1 哈希器的工作方式有很多不同之处。
java散列字符串中包含一个log cost参数,需要通过1<<cost
得到轮数/迭代次数。
salt+digest需要进行base64解码,然后取前16个字节作为salt(实际上对应前21 1/3个字符base64 数据)。
同样,由于摘要的位从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("$sWy1dDEx52vwQUCswXDYMQMzTJC39g1_nmrK384T4-w")
'$pbkdf2536$sWy1dDEx52vwQUCswXDYMQ$AzNMkLf2DX.easrfzhPj7A'
生成的字符串应该适合传入 pbkdf2_sha1.verify("abcd1234", hash)
; 除一期:
java 代码将 sha1 摘要截断为 16 个字节,而不是完整的 20 个字节;并且 passlib 的散列器的编码方式,摘要必须是完整的 20 个字节。
如果您更改 java 代码以使用 SIZE=160 而不是 SIZE=128,运行 通过上述 adapt() 函数的散列应该可以在 passlib 中工作。
我一直在尝试将 java 密码验证复制到 python,但结果哈希不同。
密码:abcd1234
密码令牌 (java): $31$16$sWy1dDEx52vwQUCswXDYMQMzTJC39g1_nmrK384T4-w
生成的密码令牌(python):$pbkdf2$16$c1d5MWRERXg1MnZ3UVVDcw$qPQvE4QbrnYJTmRXk0M7wlfhH5U
根据Java代码,Iteration是16,SALT应该是sWy1dDEx52vwQUCswXDYMQMzTJC39g1_nmrK384T4-w中的前16个字符,也就是sWy1dDEx52vwQUCs,hash应该是wXDYMQMzTJC39g1_nmrK384T4-w
然而,将变量应用于 python 给了我一个不同的哈希结果,qPQvE4QbrnYJTmRXk0M7wlfhH5U 与 Java 的哈希不同。
我哪里漏掉了?
Java:
private static final String ALGORITHM = "PBKDF2WithHmacSHA1";
private static final int SIZE = 128;
private static final Pattern layout = Pattern.compile("\\$(\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
原始 Link Java 代码:How can I hash a password in Java?
java 代码的工作方式与 passlib 的 pbkdf2_sha1 哈希器的工作方式有很多不同之处。
java散列字符串中包含一个log cost参数,需要通过
1<<cost
得到轮数/迭代次数。salt+digest需要进行base64解码,然后取前16个字节作为salt(实际上对应前21 1/3个字符base64 数据)。
同样,由于摘要的位从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("$sWy1dDEx52vwQUCswXDYMQMzTJC39g1_nmrK384T4-w")
'$pbkdf2536$sWy1dDEx52vwQUCswXDYMQ$AzNMkLf2DX.easrfzhPj7A'
生成的字符串应该适合传入 pbkdf2_sha1.verify("abcd1234", hash)
; 除一期:
java 代码将 sha1 摘要截断为 16 个字节,而不是完整的 20 个字节;并且 passlib 的散列器的编码方式,摘要必须是完整的 20 个字节。
如果您更改 java 代码以使用 SIZE=160 而不是 SIZE=128,运行 通过上述 adapt() 函数的散列应该可以在 passlib 中工作。