一尘不染

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

php

这个问题与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)

希望这可以帮助。


阅读 309

收藏
2020-05-29

共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

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

2020-05-29