为什么crypt / blowfish与两种不同的盐产生相同的散列? [英] Why does crypt/blowfish generate the same hash with two different salts?

查看:242
本文介绍了为什么crypt / blowfish与两种不同的盐产生相同的散列?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这个问题与PHP的 crypt()的实现有关。 。对于这个问题,盐的前7个字符不计算在内,所以盐' $ 2a $ 07 $ a '将被认为长度为1,因为它只有盐的1个字符和元数据的7个字符。

当使用长度超过22个字符的salt字符串时,生成的哈希值没有变化(即截断),当使用少于21个字符的字符串时,盐会自动填充(显然,' $ '字符);这相当简单。但是,如果给定一个盐20个字符和一个盐21个字符,其中两个是相同的,除了21长度的盐的最终字符以外,两个哈希字符串将是相同的。一个长22个字符的盐,除了最后一个字符外,它与21长度的盐相同,哈希将会不同。



代码示例:

  $ foo ='bar'; 
$ salt_xx ='$ 2a $ 07 $';
$ salt_19 = $ salt_xx。 b1b2ee48991281a439d;
$ salt_20 = $ salt_19。 '一个';
$ 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 ,offset(28,4)是 da2。;然而,重要的是要注意,生成的字符串包括散列,salt,以及生成盐的指令(即 $ 2a $ 07 $ );事实上,发生差异的部分仍然是盐。实际的散列值不变,因为 UxGYN739wLkV5PGoR1XA4EvNVPjwylG



因此,这实际上并不是产生的散列值的差异,但用于存储散列的盐的差别正是这个问题:两种盐会产生相同的散列。

Rembmer:输出将在以下格式:

 $ 2a $ ## $ saltaltsaltsaltsaltsaHASHhashHASHhashHASHhashHASHhash
// ^哈希值开始这里,偏移量28 ,32

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

编辑2:



在评论中,要求我发布一些额外的信息,因为用户无法重现我的输出。执行以下代码:

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

产生以下输出:

<$ p $ (5)5.3.0
字符串(5)WINNT
int(60)
int(1)
int(5) 1)
int(1)
int(1)


解决方案

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

  ./ ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 $ 

第65个字符' $ '是填充字符。



现在, crypt()函数似乎可以将任何长度小于或等于它的最大值的salt,并静静地处理任何通过丢弃任何不构成另一个完整字节的数据,在base64中产生不一致。如果你将盐中的字符传递给它不是其base64字母表的一部分,那么crypt函数就会彻底失败,这只是证实了它的操作理论。



假设盐' 1234 '。这是完美的base64一致性,因为它表示24位数据,所以3个字节,并且不携带任何需要丢弃的数据。这是一个盐,它的 Len Mod 4 是零。将任何字符追加到该盐中,并且它变成一个5字符的盐,并且 Len Mod 4 现在为1.但是,这个附加字符仅表示6位数据,因此因此,对于任何两种盐A和B,其中
$ b $都不能被转换成另一个完整字节,所以它被丢弃。

  Len A Mod 4 == 0 
&& Len B Mod 4 == 1 //这两行意味着相同的东西
&& Len B = Len A + 1 //但在语义上分别是重要的
&& A == substr B,0,Len A

crypt()来计算散列实际上是相同的。作为证明,我将包含一些可用于显示此内容的示例PHP代码。盐不断地以非随机的方式(基于当前时间的漩涡散列的随机段到微秒)以及要被散列的数据(在此称为<$ c $ <$ p
$ b

  $ salt = substr(c> $ seed )只是当前的Unix-Epoch时间。散列( '漩涡',microtime中()),兰特(0105),22); 
$ seed = time();
($ 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 = $ 2一个$ $ 07 e8955be6162468deb0da $ .0xhS8VgxJOn4skeI02VNI6jI6324EPe< -
21 = $ 2A $ $ 07 e8955be6162468deb0da3.0xhS8VgxJOn4skeI02VNI6jI6324EPe< -
22 = $ 2A $ $ 07 e8955be6162468deb0da3ucYVpET7X / 5YddEeJxVqqUIxs3COrdym

结论?双重。首先,它是按照预期工作的,第二,知道你自己的盐,或者不要推出自己的盐。


This question has to do with PHP's implementation of crypt(). For this question, the first 7 characters of the salt are not counted, so a salt '$2a$07$a' would be said to have a length of 1, as it is only 1 character of salt and seven characters of meta-data.

When using salt strings longer than 22 characters, there is no change in the hash generated (i.e., truncation), and when using strings shorter than 21 characters the salt will automatically be padded (with '$' characters, apparently); this is fairly straightforward. However, if given a salt 20 characters and a salt 21 characters, where the two are identical except for the final character of the 21-length salt, both hashed strings will be identical. A salt 22 characters long, which is identical to the 21 length salt except for the final character, the hash will be different again.

Example In Code:

$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)
);

Will produce:

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"

Why is this?

EDIT:

Some users are noting that there is a difference in the overall string, which is true. In salt_20, offset (28, 4) is da$., while in salt_21, offset (28, 4) is da2.; however, it is important to note that the string generated includes the hash, the salt, as well as instructions to generate the salt (i.e. $2a$07$); the part in which the difference occurs is, in fact, still the salt. The actual hash is unchanged as UxGYN739wLkV5PGoR1XA4EvNVPjwylG.

Thus, this is in fact not a difference in the hash produced, but a difference in the salt used to store the hash, which is precisely the problem at hand: two salts are generating the same hash.

Rembmer: the output will be in the following format:

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

where ## is the log-base-2 determining the number of iterations the algorithm runs for

Edit 2:

In the comments, it was requested that I post some additional info, as the user could not reproduce my output. Execution of the following code:

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

Produces the following output:

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

Hope this helps.

解决方案

After some experimentation, I have come to the conclusion that this is due to the way the salt is treated. The salt is not considered to be literal text, but rather to be a base64 encoded string, such that 22 bytes of salt data would actually represent a 16 byte string (floor(22 * 24 / 32) == 16) of salt. The "Gotcha!" with this implementation, though, is that, like Unix crypt, it uses a "non-standard" base64 alphabet. To be exact, it uses this alphabet:

./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789$

The 65th character, '$', is the padding character.

Now, the crypt() function appears to be capable of taking a salt of any length less than or equal to its maximum, and silently handling any inconsistencies in the base64 by discarding any data that doesn't make up another full byte. The crypt function will fail completely if you pass it characters in the salt that are not part of its base64 alphabet, which just confirms this theory of its operation.

Take an imaginary salt '1234'. This is perfectly base64 consistent in that it represents 24 bits of data, so 3 bytes, and does not carry any data that needs to be discarded. This is a salt whose Len Mod 4 is zero. Append any character to that salt, and it becomes a 5 character salt, and Len Mod 4 is now 1. However, this additional character represents only six bits of data, and therefore cannot be transformed into another full byte, so it is discarded.

Thus, for any two salts A and B, where

   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

The actual salt used by crypt() to calculate the hash will, in fact, be identical. As proof, I'm including some example PHP code that can be used to show this. The salt constantly rotates in a seminon-random way (based on a randomish segment of the whirlpool hash of the current time to the microsecond), and the data to be hashed (herein called $seed) is simply the current Unix-Epoch time.

$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
    );
}

And this produces output similar to the following

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

The conclusion? Twofold. First, it's working as intended, and second, know your own salt or don't roll your own salt.

这篇关于为什么crypt / blowfish与两种不同的盐产生相同的散列?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆