【问题标题】:Create cryptographically secure passwords. And verify that it works创建加密安全密码。并验证它是否有效
【发布时间】:2020-03-28 03:41:58
【问题描述】:

我正在开发一个也可以生成密码的软件。它基本上是这样的:

static char AllowedChars[] = {...}
static int passwordLength = 123;

std::string password;

std::random_device rd;
std::mt19937 mt(rd());
std::uniform_int_distribution<int> dist(0, sizeof(AllowedChars) - 1);
for (int i = 0; i < passwordLength; ++i) {
    int index = dist(mt);
    password.push_back(AllowedChars[index]);
}

我怎么知道这个算法会创建加密安全密码?

我知道random_device 依赖于编译器、目标平台、它自己的版本等。我也不能依赖像std::random_device::entropy 这样的方法的输出,因为它可能只是一个固定值。见:http://www.pcg-random.org/posts/cpps-random_device.html

我也无法对其输出进行黑盒测试:https://security.stackexchange.com/questions/83254/how-to-check-randomness-of-random-number-generators

我如何知道使用了哪个“随机性来源”(即哪个 RNG)?如果种子有足够的熵?(显然,这必须在编译后的二进制文件上完成,因为静态源代码无法揭示这些细节。) 通过这个输出,我可以验证是否使用了 CSPRNG。

【问题讨论】:

  • 如果你想创建像 this 这样的密码,为什么不直接使用散列摘要呢? SHA-3 可以根据需要输出摘要。
  • 由于 mt19937 在密码学上是安全的,我会自动怀疑使用它。
  • 另请注意MT isn't even a very good PRNG,有些算法具有更小的状态空间和代码大小,初始化和采样速度更快,并产生统计上“更好”的输出
  • 在 C++ 中至少有 std::mt19937 is well-defined。但是 Mersenne Twister 在密码学上并不安全(正如其中一个答案所说),无论random_device 最终如何在“编译二进制”(无论是作为密码 RNG 还是其他)或什么“熵源”中实现,这都是正确的" 它最终会使用。

标签: c++ security random cryptography passwords


【解决方案1】:

基于Mersenne Twisterpseudorandom 数字生成器加密安全。

MT 不适合加密用途的原因是,给定一组输出,您可以开始对下一个输出做出准确的预测。

加密安全要求所有原语和底层协议在不牺牲熵或一致性的情况下在加密上是可靠的。

统一性非常重要,不仅密码是“随机的”,而且是统一的。

例如,ECDHE 之后的共享秘密(在散列之前商定的椭圆曲线点)很可能是随机秘密,但它肯定不是统一的!

我见过的最好的方法是来自this 答案:

“DH 密钥交换的结果是一个组元素,它与组中随机/均匀分布的元素在计算上无法区分.然而,需要注意的是一个均匀分布的组元素不是(读:必然)一个均匀分布的字符串(后者必须有每个位等于 0 的概率为 1/2,等于 1 的概率为 1/2)"

因此,为了更直接地回答您的问题,如果您想构建一个密码生成器,我只需将一个字节数组从 CSPRNG(如 /dev/(u)random 或板载 TRNG)编码为十六进制,而不是构建一个特殊的密码细绳。如果您这样做,请记住十六进制的精确格式(分隔符、大写或小写)。

如果您使用C,为什么不直接使用libsodiumgenerate 随机字节,它既优雅又安全。

【讨论】:

  • “例如,ECDHE 之后的共享密钥(商定的椭圆曲线点,在散列之前)很可能是随机密钥,但它肯定不是统一的!”真的吗?也许在理论上,但read this answer 看到你在实践中是错误的。
  • @MaartenBodewes,我建议采用二进制数据的十六进制表示,因此它是一个可打印的可用密码回复:./dev/urandom 生成二进制数据,为什么要转储“十六进制”数据? – Maarten Bodewes 3 分钟前
【解决方案2】:

可能不会,因为 Mersenne twister 算法是不安全的,并且可以确定算法的状态,尤其是在有足够的输出可用的情况下。不幸的是,passwordLength 整数设置为 123 - 太高了。所以这可能会泄露信息。相反,您可以尝试使用std:random_device 直接 而不是mt19937。在我看来,没有特别需要特殊库。但是,您必须确保您的 std:random_device 版本是安全的(通常作为强化 std: 库的一部分)。

使用统一范围从字母表中选择密码字符的原则(如代码所示)应该被认为是正确的。要了解密码的可能强度,您可以获取有效字符数量的 2-log,然后将其与密码中的字符数量相乘。好的密码应该至少有 48 到 64 位的强度,好的密码应该在 64 位以上。当然,上面的算法无法检查你是否偶然生成了一个弱密码,所以你不能完全确定。不过,拥有足够大的密码应该可以解决这个问题。通常,如果您允许 all ASCII 可打印字符,您可以假设每个字符最多 6.6 位,因此 10-12 个字符应该可以。对于十六进制,您当然希望每个字符 4 位,对于基数 64,每个字符 6 位。

请注意,许多系统对密码有愚蠢的要求,这使得该方案过于简单。系统可能需要特定数量的特殊字符或数字。密码长度的限制通常也是一个问题。


我认为使用保证使用系统的RNG的随机数设备对于安全性至关重要。您可能想尝试另一个执行此操作的随机数生成器,但不幸的是,在许多库中有许多编译选项,最后您必须确保自己是我想的。

创建您自己的uniform_random_bit_generator(替换mt19937random_device 是这样做的一种方法,因此您可以使用依赖于它的其他std: 功能。该接口仅在C++ 20 中指定不过,是的,到了那里。

【讨论】:

  • 好答案。我有一个事后的想法,两个!鉴于我们不知道字符串的熵(因为它可能会在其他地方发布以减少熵或是弱密码的哈希等),用 log2 计算“理论”熵真的有价值吗?
  • 其次,我们之前谈到过,我完全接受字符编码只是位的表示,而不是位的实际值。熵存在于位的值中,但是如果从 /dev/random 读取熵为 128 位的数据,用十六进制编码,然后在具有 Unicode 密码的系统中使用,这不会因为受到攻击而拉伸熵不知道您使用了十六进制,并且无论如何都必须为每个十六进制字符尝试所有 ascii 字符。 (即使我接受理论熵是一样的)
  • @Woodstock:Maarten 在这里讨论熵的地方,他说的是生成这些字符串的过程,而不是单个字符串的熵。这是一项非常重要的措施,因为它为攻击者猜测密码所必须做的工作提供了一个上限。
  • @Woodstock:您关于编码的观点有一定的道理,但从安全分析的角度来看,在攻击者可以发现大部分或所有“恒定”非秘密信息的假设下评估安全性很重要。由于编码方法必须存在于加密和解密软件中,因此可以“免费”发现。
  • @Woodstock:欢迎您。在测量熵时考虑攻击者的类型很有趣。大多数密码“攻击者”只是使用您的编码方法会失败的密码模型的机器人。所以在那种情况下(最常见的),你的编码到十六进制方法实际上会显着增加熵,因为机器人不知道你已经这样做了。但是对于专门针对您的资源丰富的攻击者,他们会努力工作并对您的软件进行逆向工程等,以获取尽可能多的信息。
猜你喜欢
  • 2017-11-05
  • 2014-04-29
  • 1970-01-01
  • 1970-01-01
  • 2021-09-25
  • 2014-02-24
  • 2017-01-17
  • 2017-12-16
  • 2012-05-21
相关资源
最近更新 更多