1. 什么是 CSPRNG?
CSPRNG(Cryptographically Secure Pseudo-Random Number Generator),中文通常译为密码学安全伪随机数产生器,是在一般 PRNG(Pseudo-Random Number Generator)的基础上,额外满足密码学安全要求的乱数产生器。
一个 CSPRNG 至少需要具备两项核心特性:
- Next-bit Test(下一位不可预测性)
即使已知前 n 个输出位元,也无法在多项式时间(Polynomial Time)内,以高于 50% 的成功率预测第 n+1 个位元。
- State Compromise Resilience(状态泄漏韧性)
即使攻击者取得目前的内部状态,也无法回推出先前已产生的乱数。
简单来说,
CSPRNG 所产生的输出,在计算上无法与真正的随机数区分。
2. PRNG 与 CSPRNG 比较
| 特性 | PRNG(例如 Xorshift128+) | CSPRNG(例如 ChaCha20) | |------|-------------------------|--------------------------| | 速度 | 极快(约 2–3 ns/数值) | 很快(约 10–20 ns/数值) | | 是否可预测 | 可以,取得部分输出即可推回内部状态 | 几乎不可行 | | 周期 | 2¹²⁸ − 1 | 约 2²⁵⁶(ChaCha20) | | 可用于密码学 | ❌ 不可 | ✅ 可以 | | 常见 API | Math.random() | crypto.getRandomValues()、/dev/urandom |
3. 发展历史
3.1 Blum Blum Shub(1986)
1986 年,
Lenore Blum、Manuel Blum 与 Michael Shub 提出了 Blum Blum Shub(BBS),
它是第一个具有严格数学安全证明的 CSPRNG。
其核心公式为:
# BBS
# x(n+1) = x^2 mod M
# M = p × q(两个大型质数乘积)
def bbs(seed, p, q, n):
M = p * q
x = seed
bits = []
for _ in range:
x = (x * x) % M
bits.append(x & 1)
return bits
由于需要大量模平方运算,
BBS 非常缓慢,
因此主要具有理论研究价值,
却奠定了现代 CSPRNG 的基础。
3.2 Yarrow 与 Fortuna(1999/2003)
Bruce Schneier 与 Niels Ferguson 先后提出:
- Yarrow(1999)
- Fortuna(2003)
其中:
- Yarrow 曾被 macOS、FreeBSD 的
/dev/random采用。 - Fortuna 引入多个 Entropy Pool,自动管理熵来源,避免单点失效。
- 两者皆以 AES-256 作为核心区块加密演算法。
自 macOS 10.12(Sierra)起,
Apple 已改采 Fortuna。
3.3 ChaCha20(2008 至今)
Daniel J. Bernstein 于 2008 年设计 ChaCha20,
它是 Salsa20 的改良版本,
也是目前最广泛采用的 CSPRNG 之一。
例如:
- Linux 4.8(2016)起,
/dev/urandom采用 ChaCha20。 - OpenBSD 自 2013 年起将
arc4random()改为 ChaCha20。 - Chrome、Node.js 的
crypto.getRandomValues()建立于作业系统提供的安全乱数来源。 - Rust 的
rand_chacha预设使用 ChaCha 演算法。
4. 现代 CSPRNG 演算法
4.1 ChaCha20
ChaCha20 是一种 Stream Cipher,
内部使用:
- 4 × 4 Matrix
- 共 16 个 32-bit Word
- 总共 512 位元状态
核心 Quarter Round:
function quarterRound(state, a, b, c, d) {
state[a] += state[b];
state[d] ^= state[a];
state[d] = rotl32(state[d], 16);
state[c] += state[d];
state[b] ^= state[c];
state[b] = rotl32(state[b], 12);
state[a] += state[b];
state[d] ^= state[a];
state[d] = rotl32(state[d], 8);
state[c] += state[d];
state[b] ^= state[c];
state[b] = rotl32(state[b], 7);
}
ChaCha20 每个 Block:
- Key:256 bit
- Nonce:96 bit
- Counter:32 bit
- Output:512 bit
经过 20 轮(Rounds) 运算产生乱数。
4.2 AES-CTR-DRBG(NIST SP 800-90A)
AES-CTR-DRBG 是 NIST 标准,
广泛应用于:
- 美国政府
- 金融系统
- FIPS 认证产品
简化结构如下:
class CTR_DRBG:
def __init__(self, entropy, nonce):
self.key = AES_key_from(entropy, nonce)
self.counter = 0
def generate(self, n):
...
特色:
- 使用 AES-CTR 模式。
- 定期 Reseed。
- 符合 NIST SP 800-90A。
4.3 HMAC-DRBG(RFC 6979)
HMAC-DRBG 以 HMAC-SHA256 为核心。
RFC 6979 使用它来产生:
Deterministic ECDSA Nonce
可避免重复使用 Nonce 而泄漏私钥。
基本流程:
Seed
│
▼
K
V
│
▼
HMAC 更新
│
▼
Random Output
5. 实际应用
CSPRNG 几乎存在于所有现代资讯安全系统中,包括:
- TLS/HTTPS:Session Key、IV、Nonce。
- SSH:金钥交换。
- OAuth、JWT:Access Token、Refresh Token。
- 密码系统:Salt、随机密码。
- 区块链:Bitcoin、Ethereum 私钥。
- 线上博弈:Provably Fair 随机种子。
6. 各程式语言 API
JavaScript(Browser / Node.js)
// Browser
const buf = new Uint8Array(32);
crypto.getRandomValues;
// Node.js
import { randomBytes, randomUUID } from "node:crypto";
const key = randomBytes(32);
const uuid = randomUUID();
Python
import secrets
import os
token = secrets.token_urlsafe(32)
pin = secrets.randbelow
raw = os.urandom(32)
# 不可用于安全用途
import random
⚠️
random模组使用 Mersenne Twister,并非 CSPRNG。
Rust
use rand::rngs::OsRng;
use rand::RngCore;
let mut key = [0u8; 32];
OsRng.fill_bytes(&mut key);
Java
SecureRandom rng = SecureRandom.getInstanceStrong();
byte[] key = new byte[32];
rng.nextBytes;
int pin = rng.nextInt;
C(Linux)
#include <sys/random.h>
unsigned char key[32];
getrandom(key, sizeof(key), 0);
7. 著名安全事件
Debian OpenSSL(2008)
- CVE-2008-0166
- 误删两行程式码。
- Entropy 仅剩约三万种可能。
- Debian 约两年间产生的 SSL 金钥几乎都可被暴力破解。
Sony PS3(2010)
Sony 在 ECDSA 中重复使用固定 Nonce(k)。
结果:
- 私钥被推算。
- PS3 遭全面破解(Jailbreak)。
Android SecureRandom(2013)
Android 初始化乱数时熵不足,
导致大量 Bitcoin 钱包私钥遭窃。
DualECDRBG(2013)
DualECDRBG 是 NIST 标准中的一种 DRBG。
2013 年曝光其可能遭 NSA 植入后门,
成为密码学史上最具争议的事件之一,也使业界重新检视随机数产生器的设计与标准制定流程。