所有加密用途的随机输出必须来自nextBytes(byte[]),禁用nextInt()等整数方法;应显式指定SHA1PRNG或DRBG算法初始化SecureRandom,避免new SecureRandom()默认构造和setSeed();禁止使用Math.random()等非加密随机源。

关键只有一条:所有加密用途的随机输出,必须来自 nextBytes(byte[]) 或其直接封装(比如填充密钥数组、生成 token 字节数组后再编码),不能走 nextInt()、nextLong() 或 Stream 流式调用等中间转换路径。
选对初始化方式,避免默认 fallback
不推荐直接使用 new SecureRandom()。它在不同 JDK 版本或运行环境(如某些 Android 系统、精简容器)中可能退化为弱实现,且无法控制底层熵源行为。
- 明确指定算法更可靠:Java 8+ 推荐
SecureRandom.getInstance("SHA1PRNG")(Oracle/OpenJDK 默认强实现);JDK 9+ 可用"DRBG"(NIST 标准确定性随机比特生成器,安全性更高) - 不要手动调用
setSeed(),除非你清楚自己在做可重现测试——传入时间戳、进程 ID 或固定字节数组都会严重削弱熵值,导致输出可预测 - 避免
getInstanceStrong()在生产服务中滥用:它会等待系统积累足够熵,容易在低负载虚拟机或容器中阻塞数秒甚至更久
生成字节序列是唯一安全起点
nextBytes(byte[]) 是 SecureRandom 唯一真正对接操作系统熵源(如 /dev/urandom 或 Windows BCryptGenRandom)的出口。所有密码学用途都应从这里开始。
- 生成 AES-256 密钥:
byte[] key = new byte[32]; sr.nextBytes(key); - 生成 session ID 或重置令牌:
byte[] token = new byte[48]; sr.nextBytes(token); Base64.getEncoder().encodeToString(token); - 生成 salt:
byte[] salt = new byte[16]; sr.nextBytes(salt);(注意:salt 长度建议 ≥128 位,即至少 16 字节)
绕开整数方法的隐蔽陷阱
nextInt(n) 和 nextLong() 表面方便,但存在三类风险:
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
立即学习“Java免费学习笔记(深入)”;
- 分布偏差:当
n不是 2 的幂时(如nextInt(1000000)),内部采用模运算 + 拒绝采样不足,会导致低位字节偏差,不符合密码学均匀性要求 - 类型溢出:用
Math.abs(sr.nextLong())获取正 long 会因Long.MIN_VALUE绝对值仍为负而漏出负数 - 语义错位:这些方法返回的是“伪随机整数”,不是“加密安全的任意长度随机字节流”,本质已脱离 CSPRNG 原始输出链
若必须输出整数范围,应基于 nextBytes 自行实现拒绝采样(rejection sampling),或使用 Apache Commons 的 RandomStringUtils.randomAlphanumeric(n) 等经审计的封装。
杜绝任何非 SecureRandom 的替代方案
以下写法一律禁止用于安全场景:
-
Math.random():底层是线性同余生成器,完全可预测 -
new Random().nextInt():种子默认为System.currentTimeMillis(),毫秒级精度极易被爆破 -
Random.doubles().limit(n).toArray():Stream API 与 SecureRandom 无关,实际调用的是非加密 Random 实例 - 任何用当前时间、线程 ID、简单哈希拼接作为“种子”的 DIY 方案:熵源不可靠,不满足 RFC 1750 要求

















