创建随机哈希/字符串的最佳方法是什么?

73

如何生成用于存储会话的哈希值?我正在寻找一种轻巧、可移植的解决方案。


如果您想要安全的随机字符串,请使用类似 Random::alphanumericString($length) 的东西。这是一个随机字符串,而不是哈希值。如果您想要哈希值,PHP 包含了几种算法,但您没有指定要从哪里计算哈希值。如何将其集成到会话管理中是一个不同的问题。 - caw
9个回答

78
bin2hex(mcrypt_create_iv(22, MCRYPT_DEV_URANDOM));
mcrypt_create_iv会给你一个随机的字节序列。 bin2hex会将其转换为ASCII文本。
示例输出:
d2c63a605ae27c13e43e26fe2c97a36c4556846dd3ef

请记住,“最好”是一个相对的词。您需要在安全性、唯一性和速度之间做出权衡。上面的示例对于99%的情况都很好,但如果您处理的是特别敏感的数据,您可能需要阅读关于MCRYPT_DEV_URANDOM和MCRYPT_DEV_RANDOM之间的区别。
最后,有一个“RandomLib”用于生成各种强度的随机数和字符串。
请注意,到目前为止,我假设您想要生成一个随机字符串,这与从一个值中派生哈希值是不同的。对于后者,请参考password_hash。

27
一个小提示:mcrypt_create_iv在PHP 7.1.0中已经被弃用。请使用random_bytes代替。 - Super Cat
7
PHP 7.2.0 版本中移除了:https://secure.php.net/manual/zh/intro.mcrypt.php - 0b10011

47

random_bytes() 在 PHP 7.0 中可用(或使用 这个polyfill来支持5.2到5.6版本)。它是密码学安全的(与 rand() 相比,不是),并且可以与 bin2hex()base64_encode() 或任何其他将二进制转换为字符串的函数一起使用,以便使其更适合您的用例。

作为十六进制字符串

bin2hex()会将随机字节转换为十六进制字符串,其字符数是随机字节数的两倍(每个十六进制字符表示4个位,而一个字节包含8个位)。它只包括abcdef0123456789中的字符,并且长度总是增加2(正则表达式:/^([a-f0-9]{2})*$/)。

$random_hex = bin2hex(random_bytes(18));
echo serialize($random_hex);

s:36:"ee438d1d108bd818aa0d525602340e5d7036";

作为base64字符串

base64_encode()将生成一个字符串,长度比随机字节的数量多约33%(每个base64字符表示6位,而1个字节有8位)。它的长度总是4的倍数,使用=来填充字符串的末尾,并使用以下列表中的字符对数据进行编码(我添加了空格以便阅读):

abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
0123456789
/+

为了充分利用可用空间,最好将3作为增量提供给random_bytes()。生成的字符串将匹配/^([a-zA-Z\/+=]{4})*$/,尽管当提供给random_bytes()的数字不是3的倍数时,=只能作为结尾出现为===。请保留HTML标签。
$random_base64 = base64_encode(random_bytes(18));
echo serialize($random_base64);

这种方法是否比创建使用额外特殊字符的字符串有任何优势?我使用一种方法随机生成相同长度但具有更大字符组合的字符串,其中包括数字、大写字母、小写字母和特殊字符。从数学上看,这种方法似乎更安全。 - Zortext
3
random_bytes() 是 PHP 内置的函数(所以可能比您创建的任何内容都更快),并且可以信任其具有加密安全性(与 加密安全的 rand() 相比)。至于 bin2hex(),您可以将二进制转换为其他内容而不用太担心影响安全性(这只会更费时间,但可为最终用户节省几个字节)。最简单的方法可能是使用 base64_encode(),它只会增加大约 33% 的二进制输入大小。或者,您可以在循环中使用 random_int() 并映射到字符数组。 - 0b10011
2
@Zortext 如果您关心的是会话的安全性,可以将“16”增加到您感到满意的数字。16个字节是32个十六进制字符,这意味着有16^32种不同的可能性,或者3.4e38种可能性。因此,我建议确定您要防止的攻击面,并相应地调整字节数。如果您担心的是cookie/会话使用的存储空间,请使用base64_encode(),或者使用random_int()和一个超过64个字符的数组来自己创建。 - 0b10011
serialize 是什么? - Merc
1
@Merc serialize() 调用不是必需的,只是为了说明输出是一个字符串,并且每个字符串包含的字符数与生成的随机字节数量相比。两者都使用了 18 个随机字节,但 bin2hex() 生成了 32 个字符的字符串,而 base64_encode() 生成了 24 个字符的字符串。关于你应该使用什么:你只需要 bin2hex(random_bytes(18))base64_encode(random_bytes(18));其他所有内容都是额外的。 - 0b10011

14
您可以使用PHP内置的哈希函数,sha1md5。选择一个,不要同时使用两个。
有人可能认为同时使用sha1(md5($pass))是一个解决方案。但同时使用并不能让您的密码更安全,反而会导致数据冗余,也没有多大意义。
请参考PHP Security Consortium: Password Hashing,了解哈希的弱点和提高安全性的方法。
Nonce代表“numbers used once”(一次性数字)。它们用于请求中,防止未经授权的访问,发送一个秘密键,并在每次使用代码时检查该键。
您可以查看FullThrottle Development的PHP NONCE库,了解更多信息。

谢谢。我会研究一下的。 - Eric Gates
5
SHA1也可以被破解(至少在理论上是这样)。有关详细信息,请参见“哈希函数安全摘要”。 我会选择SHA256或SHA512。 - colan
8
非常古老的帖子,但现在SHA1已经被正式攻破了!http://www.theverge.com/2017/2/23/14712118/google-sha1-collision-broken-web-encryption-shattered - SuperNOVA

12

1
现在,我正在使用uniqid(),但我读到说在uniqid()上使用md5不好。 - Eric Gates

8

您可以使用php5.3.0中的openssl_random_pseudo_bytes函数生成一个伪随机字节字符串,您可以使用以下方法之一将其转换为字符串:

$bytes = openssl_random_pseudo_bytes(32);
$hash = base64_encode($bytes);

或者

$bytes = openssl_random_pseudo_bytes(32);
$hash = bin2hex($bytes);

第一个选项将生成包含数字、小写字母、大写字母和一些特殊字符(=,+,/)的最短字符串。第二个选项将生成十六进制数字(0-9,a-f)。

5

如果可用,使用random_bytes()

$length = 32;

if (function_exists("random_bytes")) {
    $bytes = random_bytes(ceil($length / 2));
    $token = substr(bin2hex($bytes), 0, $length)
}

在php.net上查看


0

通常我不手动管理会话ID。我曾经看到过类似的建议,以此来混淆一下,但我自己从未使用过,因此无法证明它比默认设置更好或更差(请注意,这是用于自动生成而非手动管理)。

//md5 "emulation" using sha1
ini_set('session.hash_function', 1);
ini_set('session.hash_bits_per_character', 5);

我不使用内置的PHP会话函数,因为我对它们没有太多控制权,而且可能不安全。 - Eric Gates
3
我可以向您保证,它们比您认为的任何安全函数都更加安全 :) - Tomáš Fejfar

0

-3

不同的人会有不同的“最佳”方法。但这是我的方法:

  1. 下载此rand-hash.php文件: http://bit.ly/random-string-generator
  2. include()它在你正在使用的php脚本中。然后,简单地调用 cc_rand()函数。默认情况下,它将返回一个包含a-z,A-Z0-9的6个字符长的随机字符串。您可以传递length来指定cc_rand()应该返回多少个字符。

例子:

  1. cc_rand()将返回类似于:4M8iro

  2. cc_rand(15)将返回类似于:S4cDK0L34hRIqAS

干杯!


这不会生成加密安全的值,因为它使用了 rand() - 0b10011
但是,如果您不将其用于加密安全值,则此方法效果很好。我只是想在我的程序中为某些内容创建一个标识符。+1。 - Jaden Baptista
文章中的链接已经失效,无法使用。 - Bombelman

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