生成安全的加密令牌

92

为了生成一个32个字符的API访问令牌,我们目前使用:

$token = md5(uniqid(mt_rand(), true));

我读到这种方法并不具备密码学安全性,因为它是基于系统时钟的,openssl_random_pseudo_bytes 将是更好的解决方案,因为它更难以预测。

如果是这样的话,那等效的代码会是什么样子呢?

我猜大概是这样,但我不知道是否正确...

$token = md5(openssl_random_pseudo_bytes(32));

还有什么长度是我应该传递给函数的有意义的呢?


5
为什么要进行MD5处理呢?只需将字节流转换为十六进制:从openssl_random_pseudo_bytes()函数返回32个字节,使用bin2hex()函数将这些字节渲染成十六进制值,如PHP文档中所示的例子。 - Mark Baker
我只想要32个字符?我该怎么做? - fire
11
md5() 生成32个字符的字符串,但其中只包含128位的数据。openssl_random_pseudo_bytes() 返回真正的二进制数据,因此具有32x8 = 256位的随机性。将32字节的随机字符串通过md5()处理,会使其独特性大大降低。 - Marc B
1
那么 $token = bin2hex(openssl_random_pseudo_bytes(16)); 足够了吗?还是我需要一个循环,进行 16 次迭代,将长度传递为 1 并以十六进制附加到字符串中? - fire
16字节转换为十六进制的最终解决方案是正确的。但是在这里你真的不应该依赖OpenSSL #1 #2 #3。一旦你使用PHP 7.0或更高版本,就没有借口再使用它了。尝试使用Random::alphanumericString($length),它可以将约20亿倍的“熵”装进这16个字符中。 - caw
5个回答

201

以下是正确的解决方案:

$token = bin2hex(openssl_random_pseudo_bytes(16));

# or in php7
$token = bin2hex(random_bytes(16));

1
我已经添加了另一个答案,介绍如何使用CryptoLib和openssl_random_pseudo_bytes生成唯一令牌。这样可以生成包含所有字符而不仅仅是0-9、A-F的令牌。 - mjsa
4
PHP 5.x 支持 random_bytes() 和 random_int() 函数的 random_compat 库 https://github.com/paragonie/random_compat - MTK
6
你可能还想使用 bin2hex(random_bytes($length/2)),因为每个字节可以有2个十六进制字符,所以字符数始终是 $length 的两倍,如果不使用此方法则不会达到这个效果。 - A Friend
4
@AFriend 同意。如果你想创建一个产生指定长度字符(而不是关心字节大小)的函数,你需要传递 random_bytes($length/2) - BadHorsie

10

如果您想使用openssl_random_pseudo_bytes函数,最好使用CrytoLib的实现,这将使您能够生成所有字母数字字符,将bin2hex应用于openssl_random_pseudo_bytes函数只会得到A-F(大写)和数字。

将path/to/替换为您放置cryptolib.php文件的位置(您可以在GitHub上找到它: https://github.com/IcyApril/CryptoLib

<?php
  require_once('path/to/cryptolib.php');
  $token = IcyApril\CryptoLib::randomString(16);
?>

完整的CryptoLib文档可在以下网址获取:https://cryptolib.ju.je/。它包含许多其他随机方法,级联加密和哈希生成和验证; 但如果需要,它就在那里。


对于相同数量的字节:openssl_random_pseudo_bytes(16) 每个字节可以有任何值(0-255),而 randomString(16) 每个字节只能是 a-z A-Z 0-9,即每个字节只有 62 种组合。是的,就字符串长度而言,randomString 更好,因为 bin2hex 是低效的编码方式(50%);最好使用 base64_encode(openssl_random_pseudo_bytes(16)) 来获得更短的字符串和确切的已知字节数的安全性(在这种情况下为 16,但根据需要进行修改)。 - Rodney
1
请注意,Base64编码还会产生包含“+”、“/”和“=”字符的字符串,因此如果您想生成用户友好的令牌(例如API密钥),它并不理想。@Rodney - BadHorsie

9
如果你拥有一个具有密码学安全性的随机数生成器,你就不需要对其输出进行哈希。事实上,你不应该这样做。 只需使用

$token  = openssl_random_pseudo_bytes($BYTES,true)

其中$BYTES表示想要处理的字节数。MD5哈希长度为128位,所以只需要16个字节。

顺便说一句,在您原始代码中调用的所有函数都不是密码学安全的,大多数甚至足够有害,即使与其他安全函数组合使用也会存在不安全性。MD5存在安全问题(虽然对于此应用程序可能不相关)。默认情况下,uniqid不能生成密码学随机字节(因为它使用系统时钟),您传递的附加熵使用线性同余生成器进行组合,这不是密码学安全的。实际上,即使他们不知道服务器时钟的值,他们也可能猜测出您所有API密钥的值,只需访问其中几个即可。最后,mt_rand(),即您用作额外熵的功能,也不是安全的随机数生成器。


27
openssl_random_pseudo_bytes() 的第二个参数应该是一个通过引用传递的变量。在 openssl_random_pseudo_bytes() 执行后,如果 OpenSSL 能够使用加密强度足够的算法,则该变量的值为 true 或 false。它不是用来告诉 OpenSSL 使用加密强度足够的算法的,因为 OpenSSL 在任何情况下都会尝试这样做。 - Jonathan Amend

0

另一种选择是使用来自 ircmaxell 的 RandomLib(https://github.com/ircmaxell/RandomLib

安装:composer require ircmaxell/random-lib

中等强度示例

$factory = new Factory();
$factory->getMediumStrengthGenerator()->generateString(32);

-1

可靠的密码只能由ASCII字符a-zA-Z数字0-9组成。最好的方法是仅使用加密安全的方法,如PHP7中的random_int()random_bytes()。其余函数如base64_encode()仅用作支持函数,以提高字符串的可靠性并将其更改为ASCII字符。

mt_rand()不安全且非常陈旧。 从任何字符串中,您必须使用random_int()。从二进制字符串中,您应该使用base64_encode()使二进制字符串可靠或bin2hex,但然后您只能将字节剪切到16个位置(值)。 查看我的这些函数的实现。它使用所有语言。


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