生成加密安全令牌

Posted

技术标签:

【中文标题】生成加密安全令牌【英文标题】:Generating cryptographically secure tokens 【发布时间】:2013-09-20 18:28:07 【问题描述】:

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

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

我了解到这种方法在密码学上并不安全,因为它基于系统时钟,并且openssl_random_pseudo_bytes 将是一个更好的解决方案,因为它更难预测。

如果是这种情况,等效代码会是什么样子?

我推测是这样的,但我不知道这是否正确......

$token = md5(openssl_random_pseudo_bytes(32));

还有我应该传递给函数的长度是多少?

【问题讨论】:

为什么是 md5 呢?只需将字节流转换为十六进制:您将从 openssl_random_pseudo_bytes() 返回 32 个字节,使用 bin2hex() 将这些字节中的每一个渲染为十六进制值,如 php docs 示例中所示 我只想要 32 个字符?我该怎么做? md5() 生成一个 32 个字符的字符串,但其中只有 128 位的数据。 openssl_random_pseudo_bytes() 返回真正的二进制数据,因此具有 32*8 = 256 位的随机性。通过将 32 字节的随机字符串填充到 md5 中,您有效地大量削减了它的唯一性。 那么$token = bin2hex(openssl_random_pseudo_bytes(16)); 足够了吗,还是我需要一个16次迭代的循环,将1作为长度并以十六进制附加到字符串? 16字节转十六进制的最终解法是正确的。但是你不应该在这里真正依赖 OpenSSL #1 #2 #3。一旦您使用 PHP 7.0+,就真的没有理由再使用它了。尝试使用 Random::alphanumericString($length),而不是 OpenSSL 和十六进制编码,它可以将大约 20 亿倍的“熵”放入这 16 个字符中。 【参考方案1】:

这是正确的解决方案:

$token = bin2hex(openssl_random_pseudo_bytes(16));

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

【讨论】:

我添加了另一个关于如何使用带有 CryptoLib 的 openssl_random_pseudo_bytes 生成唯一令牌的答案。这允许您生成包含所有字符的令牌,而不仅仅是 0-9、A-F。 PHP 5.x 支持 random_bytes() 和 random_int() github.com/paragonie/random_compat 您可能还需要bin2hex(random_bytes($length/2)),因为每个字节可以有2个十六进制字符,因此字符数将始终是双倍$length没有它 @AFriend 同意。如果你想创建一个产生$length字符串的函数(而不是关心字节大小),你需要传递random_bytes($length/2)【参考方案2】:

如果你想使用 openssl_random_pseudo_bytes 最好使用 CrytoLib 的实现,这将允许你生成所有字母数字字符,在 openssl_random_pseudo_bytes 周围粘贴 bin2hex 只会导致你得到 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) 每个字节只能有 az Az 0-9,即每个字节 62 个组合.是的 randomString 更好每个字符串长度,因为 bin2hex 编码效率低(50%);最好使用 base64_encode(openssl_random_pseudo_bytes(16)) 来获得更短的字符串和确切已知的安全字节数(在这种情况下为 16,但根据需要进行更改) @Rodney 请记住,base 64 也会生成一个包含 +/= 字符的字符串,因此如果您尝试生成用户友好的令牌(例如API 密钥)并不理想。【参考方案3】:

如果您有加密安全的随机数生成器,则无需对其输出进行哈希处理。事实上你不想。 只需使用

$token  = openssl_random_pseudo_bytes($BYTES,true)

其中 $BYTES 是您想要的许多字节数据。 MD5 有一个 128 位的哈希,所以 16 个字节就可以了。

附带说明,您在原始代码中调用的所有函数都不是密码安全的,大多数都足够有害,即使与安全的其他函数结合使用,仅使用一个也会破坏是不安全的。 MD5 存在安全问题(尽管对于此应用程序,它们可能不相关)。 Uniqid 不仅默认不生成加密随机字节(因为它使用系统时钟),您传入的附加熵使用线性全等生成器组合,这在加密上不安全。实际上,这可能意味着即使他们不知道您的服务器时钟的值,也可以猜测您的所有 API 密钥,即使他们可以访问其中的一些。最后,用作额外熵的 mt_rand() 也不是安全的随机数生成器。

【讨论】:

openssl_random_pseudo_bytes() 的第二个参数应该是一个通过引用传递的变量。在 openssl_random_pseudo_bytes 之后,如果 openssl 能够使用加密强算法,该变量将为真或假。它不用于告诉 openssl 使用加密强算法,无论如何它都会尝试这样做。【参考方案4】:

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

安装:composer require ircmaxell/random-lib

中等强度示例

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

【讨论】:

【参考方案5】:

可靠的密码 您只能使用 ascii 字符 a-zA-Z数字 0-9。要做到这一点,最好的方法是仅使用加密安全方法,例如 PHP7 中的 random_int()random_bytes()。其余函数为 base64_encode() 您只能将其用作支持函数以使字符串可靠并将其更改为 ASCII 字符。

mt_rand() 不安全且非常旧。 从任何字符串您必须使用 random_int()。从二进制字符串你应该使用base64_encode() 使二进制字符串可靠或bin2hex,但是你只会将字节切割到16个位置(值)。 See my implementation of this functions. 它使用所有语言。

【讨论】:

以上是关于生成加密安全令牌的主要内容,如果未能解决你的问题,请参考以下文章

azure nodejs 网站/webapp 支持用于 JWT 令牌生成和验证的加密算法

从 JWT 生成访问令牌

为 api 访问生成安全令牌

如何为组织帐户生成 GitHub OAuth 令牌?

刷新令牌如何为 jwt 添加安全性?

如何使用 javaScript 或 Angular 安全地存储 J.W.T 令牌?