使用 java (SHA-512) 创建与 symfony2 相同的哈希值
Creating same hashes like symfony2 does with java (SHA-512)
我这里有一个旧的基于 Symfony2 的应用程序,我正在 Java 中用 Dropwizard 开发一个替代品。
我将所有用户记录从旧数据库迁移到我的新数据模型中。
我还添加了新的密码字段,并导入了旧密码和盐字段。
现在我想制作众所周知的程序。让用户登录,尝试对新的密码字段。如果失败尝试迁移的那些,如果它们有效,则使用新算法对明文密码进行编码并将新哈希存储在新密码字段中。这样用户就可以将密码哈希值从旧程序移植到新程序。
听起来很简单,正常情况下它像往常一样工作,但是这个 Symfony 和 PHP 让我发疯。
我坚持的地方是使用 java 创建与 symfony 相同的散列。
旧应用程序使用具有 "sha512"、base64 编码和 5000 次迭代的 MessageDigestPasswordEncoder,所有默认值 ;)
重要的方法有:
MessageDigestPasswordEncoder:
public function encodePassword($raw, $salt) {
if ($this->isPasswordTooLong($raw)) {
throw new BadCredentialsException('Invalid password.');
}
if (!in_array($this->algorithm, hash_algos(), true)) {
throw new \LogicException(sprintf('The algorithm "%s" is not supported.', $this->algorithm));
}
$salted = $this->mergePasswordAndSalt($raw, $salt);
$digest = hash($this->algorithm, $salted, true);
// "stretch" hash
for ($i = 1; $i < $this->iterations; ++$i) {
$digest = hash($this->algorithm, $digest.$salted, true);
}
return $this->encodeHashAsBase64 ? base64_encode($digest) : bin2hex($digest);
}
和 BasePasswordEncoder:
protected function mergePasswordAndSalt($password, $salt) {
if (empty($salt)) {
return $password;
}
if (false !== strrpos($salt, '{') || false !== strrpos($salt, '}')) {
throw new \InvalidArgumentException('Cannot use { or } in salt.');
}
return $password.'{'.$salt.'}';
}
这看起来很简单,但我坚持了下来。
当我读到这篇文章时:
- 将 salt 和明文密码合并到:"password{salt}"
- 使用 SHA-512 和 return 将此字符串散列为摘要变量
- 迭代 5k 次并使用与合并的明文密码连接的摘要重新散列为摘要
- 将摘要编码为 base64
所以这是我在 Java 中的尝试:
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public void legacyEncryption(String salt, String clearPassword) throws UnsupportedEncodingException, NoSuchAlgorithmException {
// Get digester instance for algorithm "SHA-512" using BounceCastle
MessageDigest digester = MessageDigest.getInstance("SHA-512", new BouncyCastleProvider());
// Create salted password string
String mergedPasswordAndSalt = clearPassword + "{" + salt + "}";
// First time hash the input string by using UTF-8 encoded bytes.
byte[] hash = digester.digest(mergedPasswordAndSalt.getBytes("UTF-8"));
// Loop 5k times
for (int i = 0; i < 5000; i++) {
// Concatenate the hash bytes with the clearPassword bytes and rehash
hash = digester.digest(ArrayUtils.addAll(hash, mergedPasswordAndSalt.getBytes("UTF-8")));
}
// Log the resulting hash as base64 String
logger.info("Legace password digest: salt=" + salt + " hash=" + Base64.getEncoder().encodeToString(hash));
}
有人看到问题了吗?我认为不同之处在于:
PHP: binary.binary
和
JAVA: addAll(字节[],字节[])
提前致谢
php 端的实现通过进行第一轮散列然后循环 4999 次正确地进行了 5k 次迭代。
$digest = hash($this->algorithm, $salted, true);
for ($i = 1; $i < $this->iterations; ++$i) {
$digest = hash($this->algorithm, $digest.$salted, true);
}
在 java 实现中,for 循环从 0 开始,导致 5k + 1 次迭代。
通过在 java 中的 1 处开始 for 循环,生成的密码哈希值也等于。
byte[] hash = digester.digest(mergedPasswordAndSalt.getBytes("UTF-8"));
for (int i = 0; i < 5000; i++) {
hash = digester.digest(ArrayUtils.addAll(hash, mergedPasswordAndSalt.getBytes("UTF-8")));
}
我这里有一个旧的基于 Symfony2 的应用程序,我正在 Java 中用 Dropwizard 开发一个替代品。 我将所有用户记录从旧数据库迁移到我的新数据模型中。 我还添加了新的密码字段,并导入了旧密码和盐字段。
现在我想制作众所周知的程序。让用户登录,尝试对新的密码字段。如果失败尝试迁移的那些,如果它们有效,则使用新算法对明文密码进行编码并将新哈希存储在新密码字段中。这样用户就可以将密码哈希值从旧程序移植到新程序。
听起来很简单,正常情况下它像往常一样工作,但是这个 Symfony 和 PHP 让我发疯。
我坚持的地方是使用 java 创建与 symfony 相同的散列。 旧应用程序使用具有 "sha512"、base64 编码和 5000 次迭代的 MessageDigestPasswordEncoder,所有默认值 ;)
重要的方法有:
MessageDigestPasswordEncoder:
public function encodePassword($raw, $salt) {
if ($this->isPasswordTooLong($raw)) {
throw new BadCredentialsException('Invalid password.');
}
if (!in_array($this->algorithm, hash_algos(), true)) {
throw new \LogicException(sprintf('The algorithm "%s" is not supported.', $this->algorithm));
}
$salted = $this->mergePasswordAndSalt($raw, $salt);
$digest = hash($this->algorithm, $salted, true);
// "stretch" hash
for ($i = 1; $i < $this->iterations; ++$i) {
$digest = hash($this->algorithm, $digest.$salted, true);
}
return $this->encodeHashAsBase64 ? base64_encode($digest) : bin2hex($digest);
}
和 BasePasswordEncoder:
protected function mergePasswordAndSalt($password, $salt) {
if (empty($salt)) {
return $password;
}
if (false !== strrpos($salt, '{') || false !== strrpos($salt, '}')) {
throw new \InvalidArgumentException('Cannot use { or } in salt.');
}
return $password.'{'.$salt.'}';
}
这看起来很简单,但我坚持了下来。 当我读到这篇文章时:
- 将 salt 和明文密码合并到:"password{salt}"
- 使用 SHA-512 和 return 将此字符串散列为摘要变量
- 迭代 5k 次并使用与合并的明文密码连接的摘要重新散列为摘要
- 将摘要编码为 base64
所以这是我在 Java 中的尝试:
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public void legacyEncryption(String salt, String clearPassword) throws UnsupportedEncodingException, NoSuchAlgorithmException {
// Get digester instance for algorithm "SHA-512" using BounceCastle
MessageDigest digester = MessageDigest.getInstance("SHA-512", new BouncyCastleProvider());
// Create salted password string
String mergedPasswordAndSalt = clearPassword + "{" + salt + "}";
// First time hash the input string by using UTF-8 encoded bytes.
byte[] hash = digester.digest(mergedPasswordAndSalt.getBytes("UTF-8"));
// Loop 5k times
for (int i = 0; i < 5000; i++) {
// Concatenate the hash bytes with the clearPassword bytes and rehash
hash = digester.digest(ArrayUtils.addAll(hash, mergedPasswordAndSalt.getBytes("UTF-8")));
}
// Log the resulting hash as base64 String
logger.info("Legace password digest: salt=" + salt + " hash=" + Base64.getEncoder().encodeToString(hash));
}
有人看到问题了吗?我认为不同之处在于: PHP: binary.binary 和 JAVA: addAll(字节[],字节[])
提前致谢
php 端的实现通过进行第一轮散列然后循环 4999 次正确地进行了 5k 次迭代。
$digest = hash($this->algorithm, $salted, true);
for ($i = 1; $i < $this->iterations; ++$i) {
$digest = hash($this->algorithm, $digest.$salted, true);
}
在 java 实现中,for 循环从 0 开始,导致 5k + 1 次迭代。
通过在 java 中的 1 处开始 for 循环,生成的密码哈希值也等于。
byte[] hash = digester.digest(mergedPasswordAndSalt.getBytes("UTF-8"));
for (int i = 0; i < 5000; i++) {
hash = digester.digest(ArrayUtils.addAll(hash, mergedPasswordAndSalt.getBytes("UTF-8")));
}