【问题标题】:Generating random characters for a URL in PHP在 PHP 中为 URL 生成随机字符
【发布时间】:2014-08-22 08:18:33
【问题描述】:

我已经回答了我自己的实现(如下),如果您可以检查数学和逻辑,我将不胜感激,但我意识到还有其他可能性。


我正在尝试生成 32 个随机字符以用于注册 URL。

新帐户的一部分由工作人员创建(设置姓名/电子邮件),并向新用户发送纯文本电子邮件,以便他们确认电子邮件地址并设置密码。

试图保留 [A-Za-z0-9] 字符,我相信这会创建一个基于 62 位的系统,只需不到 6 位来存储......这只是超过 190 位的熵?还是 190.53428193238?

由于这是一项安全功能,我不认为单独使用 uniqid() 是一个好主意,因为这是基于当前的微时间。

而且我不认为使用用户 ID 或电子邮件地址的加密或散列是一个好的解决方案(冲突、低熵,并且可能由单个密钥保护)。

【问题讨论】:

  • 如果你不喜欢“+”和“/”,你可以用“-”和“_”代替它们——这只是Base64的另一种风格,叫做“base64url”;更多信息请参见Wikipedia
  • @Anton-Samsonov,好点子,尽管我可能仍会删除下划线字符(或使用句号),因为我有几个用户将其视为空格的实例(大概链接也使用隐藏该字符的字体样式加了下划线)......但原则上,190(ish)位熵是否正确? (并不是说它真的很重要,更重要的是检查数学部分)。

标签: php security passwords cryptography


【解决方案1】:

这适用于 PHP 7.0 random_bytes() 函数:

<?php

function random_key($length, $safe = false) {

    if ($safe !== false) {
        $bad_words = array_map('trim', file('/path/to/bad-words.txt', FILE_IGNORE_NEW_LINES));
    } else {
        $bad_words = NULL;
    }

    $j = 0;

    do {

        $bytes = (ceil($length / 4) * 3); // Must be divisible by 3, otherwise base64 encoding introduces padding characters, and the last character biases towards "0 4 8 A E I M Q U Y c g k o s w".
        $bytes = ($bytes * 2); // Get even more, because some characters will be dropped.

        $key = random_bytes($bytes);
        $key = base64_encode($key);
        $key = str_replace(array('0', 'O', 'I', 'l', '/', '+'), '', $key); // Make URL safe (base58), and drop similar looking characters (no substitutions, as we don't want to bias certain characters)
        $key = substr($key, 0, $length);

        if (preg_match('/[^a-zA-Z0-9]/', $key)) {
            exit_with_error('Invalid characters detected in key "' . $key . '"');
        }

        $valid = (strlen($key) == $length);

        if ($bad_words) {
            foreach ($bad_words as $bad_word) {
                if (stripos($key, $bad_word) !== false) {
                    $valid = false;
                    break;
                }
            }
        }

        if ($valid) {
            return $key;
        }

    } while ($j++ < 10);

    exit_with_error('Cannot generate a safe key after 10 attempts.');

}

?>

这段代码显示了base64_encode() 函数如何偏向某些字符:

<?php

$characters = [];

for ($k = 0; $k < 500000; $k++) {

    $key = base64_encode(random_bytes(32)); // 32 bytes results in "=" padding; try changing to 30 to fix.

    foreach (str_split($key) as $c) {
        if (!isset($characters[$c])) {
            $characters[$c] = 0;
        }
        $characters[$c]++;
    }

}

$characters = array_filter($characters, function($value) {
        return ($value > 343750); // ((((33/3)*4)*500000)/64) = 343750, everything else is about ~327000
    });

ksort($characters, SORT_STRING);

print_r($characters);

?>

【讨论】:

  • 不要费心从现有解决方案中发明自己的 PRNG,因为与普通的 mcrypt_create_ivopenssl_random_pseudo_bytes 相比,您收到的结果很可能会差一些。此外,许多加密级 PRNG 已经使用强散列来提高随机性;你确定你不是通过一个劣质的散列函数来传递他们的结果吗?
  • 我必须承认我从 Steve Gibson 和他的 SQRL 实现中获得了灵感......他还假设单一来源可能是坏的,但 sha256 是一个很好的散列算法......但可能是错误的......这些随机数生成器的二进制输出需要转换为 URL 安全的东西(这是这个问题的第二部分)。
  • 只是补充一点,openssl_random_pseudo_bytes() 的第二个参数会告诉你它是否是一个好的来源,旧系统可能是错误的......并且 mcrypt_create_iv() 也可以返回不那么随机数据,例如PHP 5.3 之前的 Windows 系统。
  • 这段代码有点烦人的是,如果少于$length 个字符是字母数字,它可能会随机失败。这不是 32 个字符输出的问题,但当您接近 40 个字符时会成为问题。如果输出太短,最好重复这个过程(也许增加计数器作为哈希的输入),追加新数据。
  • @CodesInChaos,同意...我应该注意 exit() 用于测试目的,通常我将此附加到一个导致各种警报响起的函数...和是的,如果我需要超过 32 个字符,我可能会循环播放,或者考虑其他选项。
猜你喜欢
  • 1970-01-01
  • 2011-05-20
  • 1970-01-01
  • 2013-10-05
  • 2021-11-29
  • 1970-01-01
  • 2023-04-06
  • 2015-08-16
相关资源
最近更新 更多