PHP的password_verify()与Python的bcrypt.hashpw()比较

11

好的,开始翻译吧。

我已经建立了一个[简单的]PHP REST API,在这里我通过X-API-KEY头键接收哈希密码。当与另一个PHP脚本进行接口操作并且使用PHP的password_hash()方法对该短语进行哈希处理时,这种方式非常有效。然而,当我尝试通过Python和Requests库与API进行接口操作时,该键被拒绝。以下是一些示例:

PHP:

<?php
$usrid = '123456';
$dt     = new DateTime();
$secret = "secret{$usrid}{$dt->format('Ymd')}";
$hashed = password_hash($secret, PASSWORD_BCRYPT);
echo $secret."\n";
echo $hashed."\n";
echo(phpversion());
?>

Python:

#!/usr/bin/python
import bcrypt, datetime, sys
usrid = '123456' # user id
t = datetime.datetime.now().strftime('%Y%m%d')
secret = "secret{usrid}{t}".format(usrid=usrid,t=t)
hashed = bcrypt.hashpw(secret, bcrypt.gensalt())
print secret
print hashed
print '%d.%d.%d' % (sys.version_info[:3])

每个命令的输出如下所示:
PHP:
    secret12345620161116
    $2y$10$/WUBS2RkTlfcgPxvmqYRI.EkBD/CPgnpE9rYvOqweERgSwFeENUDO
    5.6.24

Python: 
    secret12345620161116
    $2b$11$9v/l6KglHiNgOybw1Y8jWeCFHiAfv.cguO1Qmc7Noe4azSluoBeHO
    2.7.11

显然,它们是不同的,这就是关键所在,但当您将Python输出传递给PHP password_verify()函数时,它返回False。 PHP输出可以正常验证。
我肯定会漏掉一些东西,但是我无论如何都找不到它。 我已经尝试使用不同的salt选项,但没有成功。 我错过了什么? 它们两个不兼容吗? 如果是真的,那看起来很傻。
非常感谢您聪明的网络人士。
更新:
[我已经使用以下2行代码更新了脚本]
PHP: $hashed = password_hash($secret, PASSWORD_BCRYPT, ['cost'=>11]);
Python: hashed = bcrypt.hashpw(secret, bcrypt.gensalt(11))

我已经使用PHP代码验证了上述内容:

<?php
$secret = 'secret12345620161116';

$php    = '$2y$11$rMqK7PhWtYd3E6yqqor0K.p2XEOJqbxJSrknLLWfhqZKsbYRa1YRa'; // output from php script
$python = '$2b$11$yWzCNB4dfIIVH2FLWWEQ/efSmN/KlVmLq.MGJ54plgedE1OSQgvPu'; // putput from python script

$php_needs_rehash    = password_needs_rehash($php, PASSWORD_BCRYPT);
$python_needs_rehash = password_needs_rehash($python, PASSWORD_BCRYPT);

echo 'php_needs_rehash: '.$php_needs_rehash."\n";
echo 'python_needs_rehash: '.$python_needs_rehash."\n";
echo "\n";

echo "php_info:\n";
print_r(password_get_info($php));
echo "\n";

echo "python_info:\n";
print_r(password_get_info($python));
echo "\n";

echo "php_verified: ".password_verify($secret, $php)."\n";
echo "python_verified: ".password_verify($secret, $python)."\n";
echo "\n";
?>

以下为输出结果:

php_needs_rehash: 1
python_needs_rehash: 1

php_info:
Array
(
    [algo] => 1
    [algoName] => bcrypt
    [options] => Array
        (
            [cost] => 11
        )

)

python_info:
Array
(
    [algo] => 0
    [algoName] => unknown
    [options] => Array
        (
        )

)

php_verified: 1
python_verified: 1

所以,现在我真的很困惑,因为服务器仍然无法识别我的Python哈希密钥,如果我不按照richardhsu在评论中建议的那样用"$2y"替换"$2b"。


3
PHP使用$2y$作为“bcrypt”算法标识符。显然,Python使用不同的标识符。此外,下一个值是成本。不同的成本意味着哈希将重复的循环次数不同。尝试将PHP成本更改为像Python哈希一样的11(可以设置为password_hash的选项之一),看看它是否与Python哈希减去算法相匹配。 - Jonathan Kuhn
2
看起来你可以互换它们。https://dev59.com/zHvaa4cB1Zd3GeqPGbGw - richardhsu
1
不同的成本不应该有影响,对吧?password_verify()函数会查看哈希值中的成本并相应地进行验证。你到底在比较什么? - M. Eriksson
4
但是这些哈希值永远不会相等,对吧?我的意思是,你不能比较两个哈希值是否相等(因为同一个字符串每次产生的哈希值都不同)。你只能将原始值与哈希值进行比较,然后再查看哈希值并从中获取成本。 - M. Eriksson
1
@MagnusEriksson,我在上面发布了一个示例链接(https://3v4l.org/JijAe),内容可以说明这一点。 - Jonathan Kuhn
显示剩余10条评论
2个回答

2

从技术上讲,它们都是bcrypt或crypt-blowfish的不同版本。

在php中,前缀是$2y$10$,在python中,前缀是$2b$11$。

这意味着成本因素略有不同,分别为10和11。在您的更新中,您已经将成本因素修正为11。

前缀的另一部分表明php正在使用CRYPT_BLOWFISH哈希,而python正在使用基于Blowfish密码的bcrypt。

由于这些差异,两个密码不能互换使用。


1
BCrypt算法基于Blowfish密码,尽管两个平台都使用相同的BCrypt算法。不同的成本因素并不会使哈希不兼容,BCrypt的设计允许使用不同的成本因素进行验证。 - martinstoeckli
这个答案表明,目前可以互换密码。 - Fabien Snauwaert

2
我发现了passlib,它完美地运行。
你需要安装。
pip install passlib
pip install bcrypt

一段示例代码

from passlib.hash import bcrypt

php_hashed='$2y$10$/WUBS2RkTlfcgPxvmqYRI.EkBD/CPgnpE9rYvOqweERgSwFeENUDO'
plain_secret='secret12345620161116'

bcrypt.verify(plain_secret,php_hashed)
#output True

1
我发现这个非常有用,因为我正在探索将登录功能从旧的PHP代码迁移到新的Python微服务环境,而不需要用户重置密码。这个方法非常成功...谢谢。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接