我如何在php中获得与Spring安全框架中的encodePassword相同的值?

时间:2019-05-12 15:18:39

标签: java php encryption spring-security

我需要从php获取与使用Spring Security ShaPasswordEncoder对象的encodePassword方法获得的结果相同的值。

我无权修改相应的Java代码,因此仅php应该可以解决此问题。

Java代码:

ShaPasswordEncoder shaPasswordEncoder = new ShaPasswordEncoder(256);
shaPasswordEncoder.setIterations(1000);
String password = "123456789";
String salt = "123456";
String hash2 = shaPasswordEncoder.encodePassword(password, salt);
System.out.println(hash2);

我尝试了以下方法:

$password = "123456789"; 
$salt = "123456";

$hash = hash_pbkdf2('sha256', $password, $salt, 1000);
echo 'hash_pbkdf2 result : '.$hash.'<br>';

$out = $salt.$password;
for ($i = 0; $i < 1000; $i++) $out = hash('sha256', $out);
echo 'hash result : '.$out.'<br>';

Java结果:

9379d5a5fdd899cdf588b075988b9e94d2b3fe5cb44e593dd4a241f4757c8eec

php结果:

hash_pbkdf2 result : 2794f28c44aa958663637ebeed391b533854fb2f4b7b7d09f521de8c1fe5c6bb
hash result : b0782b680af7a13aeffc61006d2799d69ddc0465c33f376b40d946dd232f62ed

我想使php的结果值等于Java的结果值。我该怎么办?

1 个答案:

答案 0 :(得分:0)

这是我解决这个问题的过程;我在这里将其完整写出来,以帮助您学习该过程,而不仅仅是这个解决方案。

来自ShaPasswordEncoder的来源(为简洁起见删除了评论):

public class ShaPasswordEncoder extends MessageDigestPasswordEncoder {
    public ShaPasswordEncoder() {
        this(1);
    }

    public ShaPasswordEncoder(int strength) {
        super("SHA-" + strength);
    }
}

在我看来,这是一个非常瘦的包装器类。调用new ShaPasswordEncoder(256)与调用new MessageDigestPasswordEncoder("SHA-256")实际上是相同的,所以让我们看一下that的来源:

public class MessageDigestPasswordEncoder extends BaseDigestPasswordEncoder {
    private final String algorithm;
    private int iterations = 1;

    [...]

    public String encodePassword(String rawPass, Object salt) {
        String saltedPass = mergePasswordAndSalt(rawPass, salt, false);   /* 1 */

        MessageDigest messageDigest = getMessageDigest();

        byte[] digest = messageDigest.digest(Utf8.encode(saltedPass));    /* 2 */

        // "stretch" the encoded value if configured to do so
        for (int i = 1; i < this.iterations; i++) {                       /* 3 */
            digest = messageDigest.digest(digest);
        }

        if (getEncodeHashAsBase64()) {
            return Utf8.decode(Base64.encode(digest));
        } else {
            return new String(Hex.encode(digest));                        /* 4 */
        }
    }

    [...]
}

啊,现在我们到了某个地方。那里还有一些其他方法,但是我们现在只真正关心encodePassword。它可以按您期望的那样工作。从编号的行:

  1. 给密码加盐
    • 这使用了看起来很纯真的mergePasswordAndSalt方法,我们将在接下来的内容中进行探讨
  2. 哈希密码
    • 请注意此处的UTF-8编码-并非总是默认的
  3. 重复哈希输出以进行其他迭代
    • 请注意,messageDigest.digest方法的输入和输出都是原始字节数组,而不是十六进制编码的字符串
  4. 返回最终输出的十六进制编码字符串

如所承诺的,这是mergePasswordAndSalt方法的来源:

protected String mergePasswordAndSalt(String password, Object salt, boolean strict) {
    if (password == null) {                                                              /* 1 */
        password = "";
    }

    if (strict && (salt != null)) {                                                      /* 2 */
        if ((salt.toString().lastIndexOf("{") != -1)
                || (salt.toString().lastIndexOf("}") != -1)) {
            throw new IllegalArgumentException("Cannot use { or } in salt.toString()");
        }
    }

    if ((salt == null) || "".equals(salt)) {
        return password;
    } else {
        return password + "{" + salt.toString() + "}";                                   /* 3 */
    }
}

再次从编号部分开始:

  1. 检查空密码
  2. 检查是否存在{}个字符;由于下一点,这些是不允许的
  3. password{salt}的形式输出加盐的密码

我们在这里看到了为什么您在PHP中重现该算法的尝试失败了。

根据此信息,查看是否可以构建行为相同的PHP函数。今晚晚些时候可以访问环境进行测试时,我将编辑完整的PHP解决方案。