当前位置: 首页 > 面试题库 >

为什么土窖/河豚用两种不同的盐生成相同的哈希值?

扈翰
2023-03-14
问题内容

这个问题与PHP的实现有关crypt()。对于此问题,不计算盐的前7个字符,因此盐’
$2a$07$a‘的长度为1,因为它仅是盐的1个字符和元数据的7个字符。

当使用长度超过22个字符的盐字符串时,生成的哈希值不会发生变化(即,截断),而当使用长度小于21个字符的字符串时,盐将被自动填充($显然带有’
‘字符);这非常简单。但是,如果给定一个Salt 20个字符和Salt
21个字符,并且除了21个长度的salt的最后一个字符外,这两个字符相同,则两个哈希字符串都是相同的。一个长22个字符的盐,除了最后一个字符外,它与长21个盐的盐相同,哈希值将再次不同。

代码示例:

$foo = 'bar';
$salt_xx = '$2a$07$';
$salt_19 = $salt_xx . 'b1b2ee48991281a439d';
$salt_20 = $salt_19 . 'a';
$salt_21 = $salt_20 . '2';
$salt_22 = $salt_21 . 'b';

var_dump(
    crypt($foo, $salt_19), 
    crypt($foo, $salt_20), 
    crypt($foo, $salt_21), 
    crypt($foo, $salt_22)
);

将产生:

string(60) "$2a$07$b1b2ee48991281a439d$$.dEUdhUoQXVqUieLTCp0cFVolhFcbuNi"
string(60) "$2a$07$b1b2ee48991281a439da$.UxGYN739wLkV5PGoR1XA4EvNVPjwylG"
string(60) "$2a$07$b1b2ee48991281a439da2.UxGYN739wLkV5PGoR1XA4EvNVPjwylG"
string(60) "$2a$07$b1b2ee48991281a439da2O4AH0.y/AsOuzMpI.f4sBs8E2hQjPUQq"

为什么是这样?

编辑:

一些用户注意到整个字符串存在差异,这是事实。在中salt_20,偏移量(28,4)为da$.;而在中salt_21,偏移量(28,4)为da2.;
但是,必须注意,所生成的字符串包括哈希,盐以及生成盐的指令(即$2a$07$);实际上,发生差异的部分仍然是盐。实际的哈希值不变为UxGYN739wLkV5PGoR1XA4EvNVPjwylG

因此,实际上这并不是所产生的哈希值的差异,而是用于存储哈希值的盐的差异,而这恰恰是当前的问题:两种盐正在生成相同的哈希值。

Rembmer:输出将采用以下格式:

"$2a$##$saltsaltsaltsaltsaltsaHASHhashHASHhashHASHhashHASHhash"
//                            ^ Hash Starts Here, offset 28,32

其中##是对数为2的对数,确定算法运行的迭代次数

编辑2:

在评论中,要求我发布一些附加信息,因为用户无法复制我的输出。执行以下代码:

var_dump(
    PHP_VERSION, 
    PHP_OS, 
    CRYPT_SALT_LENGTH, 
    CRYPT_STD_DES, 
    CRYPT_EXT_DES, 
    CRYPT_MD5, 
    CRYPT_BLOWFISH
);

产生以下输出:

string(5) "5.3.0"
string(5) "WINNT"
int(60)
int(1)
int(1)
int(1)
int(1)

希望这可以帮助。


问题答案:

经过一些实验,我得出的结论是,这是由于盐的处理方式所致。盐不被认为是文字文本,而是被base64编码的字符串,这样22字节的salt数据实际上将代表16字节floor(22 * 24 / 32) == 16的salt 字符串()。“ Gotcha!” 但是,这种实现方式与Unix crypt一样,使用的是“非标准”
base64字母。确切地说,它使用以下字母:

./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789$

第65个字符“ $”是填充字符。

现在,该crypt()函数似乎能够提取小于或等于其最大值的任何长度的盐,并通过丢弃不组成另一个完整字节的任何数据来静默处理base64中的任何不一致之处。如果您在盐中传递不属于base64字母的字符,则crypt函数将完全失败,这恰恰证实了这一操作原理。

取一个假想的盐’ 1234‘。这是与base64完全一致的,因为它表示24位数据,即3个字节,并且不携带任何需要丢弃的数据。这Len Mod 4是零的盐。在该盐上附加任何字符,它将变成5个字符的盐,Len Mod 4现在为1。但是,此附加字符仅表示六位数据,因此不能转换为另一个完整字节,因此将其丢弃。

因此,对于任何两种盐A和B,其中

   Len A Mod 4 == 0 
&& Len B Mod 4 == 1  // these two lines mean the same thing
&& Len B = Len A + 1 // but are semantically important separately
&& A == substr B, 0, Len A

实际上,用于crypt()计算哈希的实际盐将是相同的。为了证明这一点,我提供了一些示例PHP代码,可以用来说明这一点。盐以
非随机的方式不断旋转(基于当前时间的漩涡哈希的随机段至微秒),要哈希的数据(此处称为$seed)只是当前的Unix-Epoch时间。

$salt = substr(hash('whirlpool',microtime()),rand(0,105),22);
$seed = time();
for ($i = 0, $j = strlen($salt); $i <= $j; ++$i) {
    printf('%02d = %s%s%c',
        $i,
        crypt($seed,'$2a$07$' . substr($salt, 0, $i)),
        $i%4 == 0 || $i % 4 == 1 ? ' <-' : '',
        0x0A
    );
}

这会产生类似于以下内容的输出

00 = $2a$07$$$$$$$$$$$$$$$$$$$$$$.rBxL4x0LvuUp8rhGfnEKSOevBKB5V2. <-
01 = $2a$07$e$$$$$$$$$$$$$$$$$$$$.rBxL4x0LvuUp8rhGfnEKSOevBKB5V2. <-
02 = $2a$07$e8$$$$$$$$$$$$$$$$$$$.WEimjvvOvQ.lGh/V6HFkts7Rq5rpXZG
03 = $2a$07$e89$$$$$$$$$$$$$$$$$$.Ww5p352lsfQCWarRIWWGGbKa074K4/.
04 = $2a$07$e895$$$$$$$$$$$$$$$$$.ZGSPawtL.pOeNI74nhhnHowYrJBrLuW <-
05 = $2a$07$e8955$$$$$$$$$$$$$$$$.ZGSPawtL.pOeNI74nhhnHowYrJBrLuW <-
06 = $2a$07$e8955b$$$$$$$$$$$$$$$.2UumGVfyc4SgAZBs5P6IKlUYma7sxqa
07 = $2a$07$e8955be$$$$$$$$$$$$$$.gb6deOAckxHP/WIZOGPZ6/P3oUSQkPm
08 = $2a$07$e8955be6$$$$$$$$$$$$$.5gox0YOqQMfF6FBU9weAz5RmcIKZoki <-
09 = $2a$07$e8955be61$$$$$$$$$$$$.5gox0YOqQMfF6FBU9weAz5RmcIKZoki <-
10 = $2a$07$e8955be616$$$$$$$$$$$.hWHhdkS9Z3m7/PMKn1Ko7Qf2S7H4ttK
11 = $2a$07$e8955be6162$$$$$$$$$$.meHPOa25CYG2G8JrbC8dPQuWf9yw0Iy
12 = $2a$07$e8955be61624$$$$$$$$$.vcp/UGtAwLJWvtKTndM7w1/30NuYdYa <-
13 = $2a$07$e8955be616246$$$$$$$$.vcp/UGtAwLJWvtKTndM7w1/30NuYdYa <-
14 = $2a$07$e8955be6162468$$$$$$$.OTzcPMwrtXxx6YHKtaX0mypWvqJK5Ye
15 = $2a$07$e8955be6162468d$$$$$$.pDcOFp68WnHqU8tZJxuf2V0nqUqwc0W
16 = $2a$07$e8955be6162468de$$$$$.YDv5tkOeXkOECJmjl1R8zXVRMlU0rJi <-
17 = $2a$07$e8955be6162468deb$$$$.YDv5tkOeXkOECJmjl1R8zXVRMlU0rJi <-
18 = $2a$07$e8955be6162468deb0$$$.aNZIHogUlCn8H7W3naR50pzEsQgnakq
19 = $2a$07$e8955be6162468deb0d$$.ytfAwRL.czZr/K3hGPmbgJlheoZUyL2
20 = $2a$07$e8955be6162468deb0da$.0xhS8VgxJOn4skeI02VNI6jI6324EPe <-
21 = $2a$07$e8955be6162468deb0da3.0xhS8VgxJOn4skeI02VNI6jI6324EPe <-
22 = $2a$07$e8955be6162468deb0da3ucYVpET7X/5YddEeJxVqqUIxs3COrdym

结论?双重。首先,它按预期工作,其次,知道您自己的盐或不撒盐。



 类似资料:
  • 我正在创建使用Facebook登录SDK的Android应用程序。 我想生成调试密钥哈希。在Facebook网站上我发现了这样的命令: keytool-exportcert-alias androiddebugkey-keystore c:\users\redio\.android\debug.keystore“c:\openssl\bin\openssl”sha1-binary“c:\opens

  • 问题内容: 我可以跑: 而事实证明,并且有不同的MD5哈希值。的确,它们是不同的,这证实了这一点。 我还需要传递其他哪些标志,以便其输出随时间推移与相同输入保持一致? 问题答案: 相当于 文件不同的原因是因为将其输入文件名和修改时间放入了压缩文件中。当输入是管道时,它将使用空字符串作为文件名,并使用当前时间作为修改时间。 但是它也有一个选项,告诉它不要将名称和时间戳记放入文件中。因此,如果您显式编

  • 据我所知,两个不相等的对象可以具有相同的哈希代码。当添加或从HashMap java中检索时,将如何处理这个问题?

  • 所以我想通过添加随机盐来散列我的密码。网。我为此使用的内置类是RNGCryptoServiceProvider-生成随机盐和Rfc2898DeriveBytes-散列实际密码。 但是当我为相同的passwordString组合调用Rfc2898DeriveBytes的GetBytes()函数时,SaltBytes 在我的测试课上,我有这个函数 测试失败了 但是如果在上面的类PBKDF2实现中,如果

  • 这是代码: 如果我在我的机器()或这里()上尝试: 相反,这里(): 这是不同的。这是由于机器厄普西隆?还是编译器精度标志?还是不同的评估? 造成这种漂移的原因是什么?问题似乎出现在函数中(因为其他值似乎相同)。

  • 我正在开发一款Android应用程序。在我的应用程序中,我集成了Facebook登录。我的facebook登录工作正常。但当我制作release apk并运行该应用程序并尝试登录Facebook时,它就不工作了。请看下面我的场景。 我生成如下的发布apk 然后我使用jks文件路径生成keyhash。 我得到了一个散列键,然后将其添加到开发人员配置文件设置中。 当我在我的设备上安装并运行apk并使用