这种用于随机字符串的算法在密码学上是否安全?

信息安全 密码学 Web应用程序 php 随机的
2021-09-02 12:47:48

我已经从我在网上看到的各种代码中拼凑出这个算法(如果它可以被称为),我想知道它的密码安全性如何。它用于生成密码:

function seed_random() {
    $seed = crc32(uniqid(sha1(microtime(true) . getmypid()), true));
    mt_srand($seed);
    $n = mt_rand(1, 200);
    for ($i = 0; $i < $n; $i++) {
        mt_rand();
    }
}

function mt_rand_custom($min, $max) {
    seed_random();
    return mt_rand($min, $max);
}

function random_password($length) {
    $legal_characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'_!@#$^*?<>[]{}~(),";
    seed_random();
    $command = 'head -c 500 /dev/urandom | tr -dc %s | head -c %s; echo';
    return shell_exec(sprintf($command, $legal_characters, $length));
}

function generate_password() {
    return random_password(10);
}

我知道安全性,尤其是在 Web 环境中,取决于许多其他事情(例如通过 SSL 发送的密码),但我对这组特定的功能感到好奇。感谢您的反馈。

编辑:即使示例不再使用 mt_random_custom 函数,任何对此的反馈也将不胜感激,所以我把它留在了。

2个回答

在您的代码中,您必须填写$legal_characters您接受作为密码一部分的字符列表。例如:

$legal_characters = "abcdefghijklmnopqrstuvwxyz";

如果您想要由小写字母组成的密码(所有小写拉丁字母,但没有其他字符且没有重音符号)。

这段代码有点奇怪;它使用mt_rand()以当前时间和进程 ID 为种子的(内部 PRNG)来获取密码长度,介于 15 到 60 个字符之间。然后它/dev/urandom用于密码本身,这很聪明,因为mt_rand()它不是加密安全的(特别是因为进程 ID 不是非常机密的东西,当前时间也不是)。

实际的密码生成是这样工作的:它产生 500 个随机字节(来自/dev/urandom),然后删除所有不在接受字符集中的字节(即“ tr”),最后再次将剩余的字符序列截断为所需的长度。这个过程会生成均匀的随机序列,这很好,并且/dev/urandom是合适的 PRNG。但请注意,有几点需要注意:

  • 如果“合法字符”集很小,您可以使用较短的密码结束。例如,如果您想要仅包含 1 到 6 位数字的密码,则在 500 个随机字节中平均只有 12 个左右的匹配字节。该代码没有“太短”的故障保护。如果您设置$legal_characters为全部 26 个小写字母,那么 10 中的大约 1 个字节将是合法的(字节值范围从 0 到 255,而 26/256 接近 1/10),平均而言,“ tr”部分将产生大约50 个字符,它产生少于 20 个字符的可能性极小。不过,这值得注意,应该有故障保护。

  • 密码长度可变是没有意义的。如果最小长度的密码在安全方面是可以接受的,那么所有密码都可以具有该长度。如果它是不可接受的,那你为什么要使用最小长度呢?您最好使用单个固定长度,而不是范围。这将允许您删除seed_random()mt_rand_custom(),大大简化代码。

  • “密码”是人类可以输入记忆的东西——因此是“单词”。他会记住一个 60 个字符的序列吗?我确信这种情况有一个医学名称。

  • 您应该将密码的长度设置为“适当”的值,以使熵足够高。如果有x 个合法字符且密码长度为n,则熵将为x n需要什么熵取决于预期用途。对于通过适当的 SSL 隧道对网站上的用户进行身份验证,2 40(“40 位”熵)就足够了,它转换为 9 个小写字母(26 9大于 2 40)。

  • 该代码使用unixisms ( /dev/urandom, head, tr...),并且很难在Windows 服务器上运行(PHP 也可以在Windows 上运行)。


摘要:密码会很强大,但代码很奇怪。您应该放弃可变长度,如果您的用户可以吞下 60 个字符的密码,那么请替我祝贺他们。或者把它们卖给动物园。这是一个应该没问题的简化代码(警告:我不使用 PHP,所以我在这里即兴创作):

function generate_password() {
    return shell_exec('head -c 500 /dev/urandom | tr -dc abcdefghijklmnopqrstuvwxyz | head -c 9; echo');
}

分析。您的代码有一些缺点:

  1. 错误地使用 system()。我怀疑你的 shell 命令不会做你想做的事,因为你没有引用各种 shell 元字符。而不是tr -dc %s,您需要tr -dc '%s',然后您需要转义 $legal_characters 中的单引号。

  2. 字符集太宽泛。这不会生成可以在所有站点上可靠使用的密码。许多站点对密码中允许使用的字符施加限制(例如,它们可能禁止单引号)。因此,如果您在实践中使用您的脚本,您会发现它经常会生成一些网站会拒绝的密码。我建议将字符范围限制为 a-zA-Z0-9,以获得最大的可移植性。这不会显着降低熵或密码安全性,但会使您的脚本更有用。

  3. 死代码。您有无用的死代码 ( mt_rand_custom())。该代码也是糟糕的东西,对于加密目的来说并不安全,但这是一个切线:如果您想询问该代码,请将其放在一个单独的问题中。不要将两个问题塞进一篇文章中。

解决方案。@Thomas Pornin 的单行 shell 命令要好得多。就个人而言,我会通过允许大写字母和数字来稍微改进它:

head -c 500 /dev/urandom | tr -dc a-zA-Z0-9 | head -c 12; echo

这足以让您获得 71 位熵,这对于网站密码来说应该绰绰有余,并且几乎每个网站都应该接受由此产生的密码。

(如果你想要一个更短的密码,用125910位熵替换 10 个字符的密码,这对于所有合理的目的可能仍然足够。一个 8 个字符的密码将具有 47 位熵,这也可能是足够了,只要网站的密码哈希数据库不被泄露——对于大多数用户来说,这不是他们需要担心的主要问题。8 个字符的网站特定密码足以防止在线密码猜测攻击,这可能是大多数用户面临的最重要的威胁。)