一文详解安全随机数
本文分享自华为云社区《【安全攻防】深入浅出实战系列专题-安全随机数》,作者: MDKing 。
随机数的使用场景
使用随机数可分类安全场景跟非安全场景。非安全场景需要生成的越快越好。安全场景使用的随机数必须足够安全,保证不能被预测到。
常见的非安全场景:
数据的索引号、标识;
文件的名称或目录;
UUID、用户 ID、随机填充字节;
常见安全场景包括但不限于以下场景:
用于密码算法用途,如生成 IV、盐值、密钥等;
会话标识(sessionId)的生成;
挑战算法中的随机数生成;
验证码的随机数生成;
密码学意义上的安全随机数
安全场景下使用的随机数必须是密码学意义上的安全随机数。
密码学意义上的安全随机数分为两类:
真随机数产生器产生的随机数;
以真随机数产生器产生的少量随机数作为种子的密码学安全的伪随机数产生器产生的大量随机数。
已知的可供产品使用的密码学安全的非物理真随机数产生器有:
Linux 操作系统的/dev/random 设备接口(存在阻塞问题)
Windows 操作系统的 CryptGenRandom() 接口
我们可以看到,密码学意义上的安全随机数非指必须都为真随机数生成器生成,因为真随机数生成器产生随机数是需要资源(熵)消耗的,当资源不足时会阻塞。所以在业务使用随机数频次较低的时候可以考虑全部使用真随机数。在业务使用随机数的频次高、数量大时就必须使用兼顾安全跟效率的方式:使用伪随机数生成器作为主体生成器进行随机数生成,使用真随机数周期为其设置种子(seed)。
SecureRandom 对象的生成随机数有两类方法:生成下一个随机数的方法,比如 nextInt、nextBytes 等;生成种子的方法,如 generateSeed。
使用 new SecureRandom()创建的对象的 nextXXX、generateSeed 方法在 linux 系统下随机数来源均为/dev/urandom,生成的随机数都是不安全随机数。
使用 SecureRandom.getInstance("SHA1PRNG", "SUN")创建的对象 nextXXX 生成的为不安全的随机数,generateSeed 生成的为安全随机数(linux 下来源为/dev/random),所以使用 SHA1PRNG 算法合理设置好周期补种的逻辑理论上是可行的。但是根据公司密码算法相关要求,从 2023 年开始将禁止使用 SHA1PRNG 算法生成安全随机数。
常用推荐的安全随机数用法
大的原则上不建议自己实现补种、刷新熵源等逻辑,最好直接使用 JDK 封装好的方法。
密码学安全的随机数用法主要有 3 种:
1. SecureRandom.getInstance("NativePRNGBlocking")
参数要配置 NativePRNGBlocking,好处是非常安全,因为 nextXXX、generateSeed 方法都是生成的真随机数,缺点是因为每次都是真随机数,性能较低,会有阻塞性问题。常见几个参数的对比如下:
2. SecureRandom.getInstanceStrong()
在 JDK 8 中引入,主要解决 getInstance 在应用某些参数时不能保证取到的随机数质量足够安全的问题,简化编程。它返回每个平台上可用的最强 SecureRandom 实现的实例。它会调用系统上可用的最强算法,该算法由 $JAVA_HOME/jre/lib/security/java.security 中的 securerandom.strongAlgorithms 来指定。
securerandom.strongAlgorithms 的默认配置一般为 NativePRNGBlocking:SUN,在这种设定下 getInstanceStrong()与 getInstance("NativePRNGBlocking")等效。
3. SecureRandom.getInstance("DRBG",...)
从 JDK 9 开始,新增了由 Sun 提供的符合 NIST SP 800 90-A 标准的 DRBG 伪随机数产生器,包括 HASH-DRBG、HMAC-DRBG、CTR-DRBG 三种,且适用于各种 OS,符合公司密码算法相关要求,在使用 JDK 9 及以上版本时,推荐优先使用该方式生成安全随机数。该方式生成随机数是兼顾安全跟效率周期补种真随机数方式的封装,所以在高频生成安全随机数的场景中可以使用该方式。
推荐写法可参考编程规范:
小结一下:
低频使用安全随机数的业务场景(对性能没有要求)可以使用 SecureRandom.getInstance("NativePRNGBlocking")或者 SecureRandom.getInstanceStrong()方式;
高频使用安全随机数的业务场景(对性能有要求)只能使用 SecureRandom.getInstance("DRBG",...)方式;
上述方式都不能满足业务场景需求时,再考虑自己实现补种、补熵等逻辑(不推荐、一般也不会有)。
实战验证
我们使用 SHA1PRNG 类型的随机数生成器,验证设置同样的种子 seed 后,是否能产生同样的随机数序列。
执行结果如下:可以看到,不管重复执行多少次,只要初始设置的种子相同,后面的序列一定是相同、且固定的。
所以如果使用了不安全的随机数生成器实现的代码逻辑,一旦攻击者掌握了一定数量的随机数序列,就有可能推测出初始种子,从而完全预测到后续生成的随机数序列的具体内容。
如果使用真随机数设置种子(当前写法仅为示例,并非推荐写法)
可以看到,每次执行都有随机性,由于设置的 seed 是通过真随机数生成器生成的,所以不可预测。
有同学肯定好奇,真随机数生成器的速度真的有那么慢吗?我们实际验证下,使用真随机数生成器、伪随机数生成器的性能对比如下:
版权声明: 本文为 InfoQ 作者【华为云开发者联盟】的原创文章。
原文链接:【http://xie.infoq.cn/article/ea0bed687c4e1bc487940c0cf】。文章转载请联系作者。
评论