为什么不使用时间戳来做随机数,要用更复杂的方式去生成呢?

84 阅读5分钟

前言

在我们的开发过程中我们往往需要创建随机数来避免重复,很多时候我们都是使用工具来生成的。但是,其实有很多场景下我们其实只需要一个简单的随机数而已,并不需要它很复杂,那么你有没有好奇过,为什么我们不使用它呢?

时间戳的古今

什么是时间戳

简单来说,时间戳是一个用于表示某一特定时间点的数字或字符序列。它就像时间的“指纹”或“序列号”,为某个事件打上一个唯一的、可追溯的时间标记。

时间戳的核心思想是:用一个相对于某个“纪元”经过的秒数(或毫秒数等)来表示时间。在计算机世界中,最著名的纪元是 Unix 纪元,即 1970年1月1日 00:00:00 UTC。也就是说,我们现在的时间戳是1970年1月1日零点开始的秒数,或者是毫秒数。

举个例子

假设一个时间戳是 1622500000(以秒为单位)。 它的计算方式是:从 1970年1月1日 00:00:00 UTC 开始,经过了 1622500000 秒。换算成人类可读的日期时间大约是 2021年6月1日 12:26:40 UTC。

做随机数的问题

一句话:时间戳作为随机数种子,产生的随机数序列是高度可预测和不安全的,因此它们只能用于对安全性要求极低的场景。

时间戳作为随机数源的主要问题:

  1. 可预测性

    • 时间戳是一个持续、线性增长的数值。攻击者很容易就能猜到你的程序在某个大致时间范围内运行时所用的时间戳。
    • 例如,如果你在2024年1月1日中午12点运行程序,攻击者可以合理地猜测你使用的种子在 1704096000 附近。他们只需要在一个很小的范围内(比如前后几秒或几分钟)进行尝试,就能复现你生成的整个“随机数”序列。
    • 安全后果: 在需要安全性的场景下(如生成加密密钥、会话Token、彩票抽奖),如果使用时间戳,攻击者可以预测出你的“随机”结果,从而轻易破解系统。
  2. 熵不足

    • “熵”在这里可以理解为“不确定性”或“混乱程度”。一个高质量的随机数种子需要尽可能高的熵。
    • 时间戳的熵非常低。它的变化是规律的、有限的。在同一秒内运行程序的多个实例,它们可能会得到完全相同的时间戳种子,从而产生完全相同的随机数序列,这显然是灾难性的。
  3. 粒度问题

    • 尽管时间戳的精度可能达到毫秒甚至微秒,但在现代计算机上,一个程序可能在同一次“时钟滴答”内被多次调用。如果快速连续地生成多个随机数,它们可能会基于相同的时间戳种子,导致随机数重复。

如何解决?

根据上述时间戳的缺陷,从物理世界中收集高熵的、不可预测的数据作为随机数种子

  1. 伪随机数生成器的优质种子源

    • 编程语言中的 Math.random() 或 rand() 函数通常是伪随机数生成器。它们本身是确定性算法,需要一个高质量的种子来产生看似随机的序列。
    • 现代操作系统提供了专门用于收集熵的机制。如:键盘敲击的时间间隔、鼠标移动的轨迹、磁盘I/O的精确时间、硬件中断的时序、网络数据包到达的时间
  2. 密码学安全的伪随机数生成器

    • 对于安全至关重要的应用(如生成加密密钥),直接使用上述操作系统提供的接口是最佳实践。在编程中,你应该使用明确标记为“密码学安全”的函数:如:Python: os.urandom() 或 secrets 模块、Java: java.security.SecureRandom、Node.js: crypto.randomBytes()
    • 这些伪随机数生成器的内部实现非常复杂,它们不仅使用高熵种子,其生成算法也经过特殊设计,使得即使部分内部状态被泄露,也难以推测出之前的随机数序列

何时能用时间戳?

场景类型描述例子是否可以使用时间戳?
非关键性模拟/游戏结果不需要安全,只需要“看起来随机”即可。游戏中的怪物随机移动、随机生成地图、洗牌动画效果。可以。例如 Math.random() 在很多实现中默认会使用时间戳相关的种子,这完全够用。
统计模拟需要高质量的随机性以保证结果的统计有效性,但不需要安全。蒙特卡洛模拟、科学计算。不建议。应使用高质量的PRNG并明确指定一个可复现的种子。时间戳的熵和质量不够高。
安全敏感应用随机数的不可预测性是安全的核心。生成加密密钥、会话ID、密码重置Token、彩票中奖号码、在线扑克发牌。绝对禁止。必须使用密码学安全的伪随机数生成器。

结论

时间戳之所以被“更麻烦和复杂”的方式取代,是因为在绝大多数需要真随机性(尤其是安全性)的场景下,时间戳是一个脆弱且危险的选择。那些复杂的方法是为了从物理世界的混沌中汲取真正的“随机性”,从而确保产生的数字既是随机的,又是不可预测的。在编程中,养成习惯:只要是和安全性沾边,就毫不犹豫地使用伪随机数生成器。对应到前端开发,可以使用uuid、nanoid等工具库可以自行使用。