并不是所有的随机值都是相等的 - 对于与安全相关的代码,您需要一种特定的随机值。

这篇文章的摘要,如果你不想阅读整个事情:

  • 不要使用Math.random()Math.random()是正确答案的情况极少。不要使用它,除非您已阅读整篇文章,并确定您的案件是必要的。
  • 不要直接使用crypto.getRandomBytes。虽然这是一个CSPRNG,但是在“转换”它时会很容易偏离结果,从而使输出变得更加可预测。
  • 如果要生成随机令牌或API密钥:请使用uuid,特别是uuid.v4()方法。 注意node-uuid它不是一样的包,并且不会产生可靠的安全随机值。
  • 如果要在一个范围内生成随机数:使用random-number-csprng

你应该认真考虑阅读整篇文章,虽然不是那么久:)

“随机”的种类

大概有三种类型的“随机”:

  • 真随机:顾名思义。无随机性,无图案或算法适用。这是否真的存在是有争议的。
  • 不可预测的:不是真正随机的,但是攻击者不可能预测。这就是您所需的安全相关代码 - 只要数据不会被猜到,它怎么生成并不重要。
  • 不规则:这是大多数人认为“随机”的想法。一个例子是一个具有星型场景背景的游戏,其中每个星星被画在屏幕上的“随机”位置。这不是真正随机的,它甚至不是不可预知的 - 它看起来并不像是有视觉上的模式。

不规则的数据是快速生成的,但在安全上而言毫无价值 - 即使看起来似乎没有一个模式,攻击者几乎总是可以预测这些值将会是什么。不规则数据的唯一用处是在视觉上表示的东西,如游戏元素或者在笑话网站上随机产生的短语。

不可预测的数据生成速度有点慢,但对于大多数情况来说仍然足够快,并且很难猜到它将具有抗攻击性。不可预测的数据由所谓的CSPRNG提供。

RNG类型(随机数生成器)

  • CSPRNG: 密码安全伪随机数发生器。这是为了安全目的而产生不可预测的数据。
    PRNG:伪随机数发生器。这是一个更广泛的类别,包括刚刚返回不规则值的CSPRNG和生成器 - 换句话说,您不能依靠PRNG为您提供不可预测的值。

RNG: 随机数生成器。这个术语的含义取决于上下文。大多数人将其用作更广泛的类别,包括PRNG和真正的随机数字生成器。

应使用CSPRNG生成与安全相关的目的(即,存在“攻击者”可能性的任何事物)所需的每个随机值。这包括验证令牌,重置令牌,彩票号码,API密钥,生成的密码,加密密钥等等。

Bias

在Node.js中,最广泛可用的CSPRNG是crypto.randomBytes函数,但是您不应该直接使用它,因为很容易弄乱和“偏离”随机值 - 也就是说,使特定的选择值或值集合。

这个错误的一个常见的例子是当你有少于256种可能性(因为单个字节有256个可能的值)时使用%模运算符。这样做实际上使较低的值比较高的值更可能被挑选。

例如,假设您有36个可能的随机值 - 0-9加上a-z中的每个小写字母。一个天真的实现可能看起来像这样:

let randomCharacter = randomByte % 36;

该代码是破碎和不安全的。通过上面的代码,您基本上创建了以下范围(包括):

0-35保持0-35。
36-71变为0-35。
72-107变成0-35。
108-143成为0-35。
144-179成为0-35。
180-215变成0-35。
216-251变为0-35。
252-255变成0-3。

如果您查看上述范围列表,您会注意到,对于4到35(含)之间的每个randomCharacter有7个可能的值,每个随机字符在0和3(含)之间有8个可能的值。这意味着虽然有一个2.64%的机会获得4到35(含)之间的值,但有一个3.02%的机会获得0到3(含)之间的值。

这种差异可能看起来很小,但是攻击者可以轻松有效地减少暴力事件时所需要的猜测量。而这只是一种方法,您可以使您的随机值不安全,尽管它们最初来自安全的随机源。

那么,如何安全地获取随机值?

在Node.js中:

  • 如果您需要在一定范围内的个别随机数:使用random-number-csprng。
  • 如果您需要API键或令牌:使用uuid(而不是node-uuid!),尤其是uuid.v4()方法。

这两者都使用CSPRNG,并以无偏差(即安全)方式“转换”字节。