【缺陷周话】第27期:不安全的随机数
字数 1944 2025-08-18 11:38:21
不安全的随机数缺陷分析与防范指南
1. 不安全的随机数概述
随机数在计算机安全领域有着广泛的应用,特别是在密码学中。然而,不正确的随机数生成方式会导致严重的安全漏洞。
1.1 常见的不安全随机数实现
在Java中,常见的错误做法包括:
- 使用
java.util.Random类生成随机数 - 使用可预测的种子值
- 依赖伪随机数生成器(Pseudo-Random Number Generator, PRNG)
1.2 安全关键场景
以下场景必须使用安全的随机数:
- UUID生成
- Token生成
- 密钥生成
- 密文加盐处理
- 密码重置链接
- 验证码生成
2. 不安全随机数的危害
2.1 直接风险
使用可预测的随机数会导致:
- 加密密钥可被预测
- 会话令牌可被猜测
- 验证码可被暴力破解
- 系统访问控制被绕过
2.2 实际漏洞案例
| CVE编号 | 影响范围 | 漏洞描述 |
|---|---|---|
| CVE-2018-1474 | Android 7.0-9.0 | random.c中的random_get_bytes使用不安全默认值,导致随机性降低 |
| CVE-2018-18531 | kaptcha 2.3.2 | 使用Random而非SecureRandom生成验证码值 |
| CVE-2018-16031 | Socket.io ≤0.9.6 | 使用Math.random()生成可预测的套接字ID |
3. 代码示例分析
3.1 缺陷代码示例
// 不安全随机数使用示例
import java.util.Random;
public class PasswordResetLink {
public String resetPassword(String username) {
Random random = new Random(); // 缺陷点:使用不安全的Random类
if("admin".equals(username)) {
random.setSeed(123456L); // 缺陷点:使用固定种子
return scramble(random, username);
}
return scramble(random, username);
}
private String scramble(Random random, String username) {
// 使用随机数处理用户名
// ...
}
}
缺陷分析:
- 使用
java.util.Random类生成随机数 - 为"admin"账户设置了固定种子(123456L)
- 随机数生成器可被预测
3.2 修复代码示例
// 安全随机数使用示例
import java.security.SecureRandom;
public class PasswordResetLink {
public String resetPassword(String username) {
SecureRandom random = new SecureRandom(); // 修复:使用安全的SecureRandom
if("admin".equals(username)) {
// 不再设置固定种子
return scramble(random, username);
}
return scramble(random, username);
}
private String scramble(SecureRandom random, String username) {
// 使用安全随机数处理用户名
// ...
}
}
修复要点:
- 使用
java.security.SecureRandom替代java.util.Random - 不再设置固定种子
- SecureRandom默认使用系统提供的强随机种子
4. 安全实践指南
4.1 Java中的安全随机数
在Java中,应始终使用SecureRandom类:
import java.security.SecureRandom;
public class SecureRandomExample {
public static void main(String[] args) {
// 创建安全随机数生成器
SecureRandom secureRandom = new SecureRandom();
// 生成随机字节数组
byte[] randomBytes = new byte[32];
secureRandom.nextBytes(randomBytes);
// 生成随机整数
int randomInt = secureRandom.nextInt();
}
}
4.2 种子管理最佳实践
- 不要设置固定种子:让系统自动选择强随机种子
- 避免种子泄露:确保种子生成过程安全
- 定期重新播种:对于长时间运行的应用程序
4.3 跨平台安全建议
| 语言/平台 | 安全随机数实现 | 不安全实现 |
|---|---|---|
| Java | SecureRandom | Random, Math.random() |
| C/C++ | CryptGenRandom (Windows), /dev/random (Unix) | rand(), random() |
| Python | os.urandom(), secrets模块 | random模块 |
| JavaScript | crypto.getRandomValues() | Math.random() |
5. 检测与验证
5.1 静态检测方法
使用代码安全检测工具(如360代码卫士)可以检测以下模式:
- 使用
java.util.Random类 - 调用
Math.random() - 设置固定种子
- 使用可预测的种子源
5.2 动态验证方法
- 随机性测试:使用统计测试套件(如Diehard测试、NIST测试)验证随机数质量
- 熵源检查:确保系统有足够的熵源
- 预测测试:尝试基于部分输出预测后续随机数
6. 高级主题
6.1 密码学安全伪随机数生成器(CSPRNG)
SecureRandom默认使用SHA1PRNG算法,这是符合CSPRNG要求的实现。其他安全算法包括:
- HMAC_DRBG
- Hash_DRBG
- CTR_DRBG
6.2 系统熵源
安全随机数生成依赖于系统熵源:
- Linux:
/dev/random(阻塞型),/dev/urandom(非阻塞型) - Windows: CryptGenRandom API
- 硬件随机数生成器(HRNG)
6.3 性能考量
虽然SecureRandom比Random慢,但在安全关键场景中不应妥协。对于高吞吐量场景:
- 可以预生成一批随机数
- 使用线程安全的实现
- 考虑硬件加速方案
7. 总结
在安全开发中,随机数的正确使用至关重要。遵循以下原则:
- 在安全关键场景始终使用SecureRandom等安全实现
- 避免使用固定或可预测的种子
- 定期进行代码审计和安全测试
- 了解不同平台的推荐实现方式
- 在性能与安全之间做出明智权衡
通过遵循这些准则,可以有效防止因随机数不安全导致的各类安全漏洞。