Caloogy 文章
English

素数从哪来:费马检验、Carmichael 数与 Miller–Rabin

日期
2025 年 11 月 18 日
系列
密码学
阅读
4 分钟
主题
PrimalityFermatCarmichaelMiller–Rabin

一句话版本

生成 RSA 密钥要找两个上千位的素数。办法是"随机挑一个奇数,测一测,不是就换一个"。 费马小定理给出最简单的测试:若 pp 是素数,则 ap−1≡1a^{p-1} \equiv 1。但有一类合数(Carmichael 数,最小的是 561561)能骗过所有底数。Miller–Rabin 补上了这个漏洞,它利用的是:模素数时,x2=1x^2 = 1 只有 ±1\pm 1 两个根。

全文分三层:

  1. 通俗理解:为什么不分解也能判断素数,"随机抽查"的思路
  2. 实际用处:RSA 密钥生成、OpenSSL,以及素数生成出错的事故
  3. 数学原理:费马检验、Carmichael 数、Miller–Rabin 的正确性,附 561561 的手算例子

第一层:通俗理解

1.1 判断素数比分解容易得多

"NN 是不是素数"和"NN 等于哪两个数相乘"听起来像同一个问题,其实难度天差地别:

神奇之处在于,素性检验可以不找出任何因子就断定一个数是合数。就像验钞机不用知道假钞是谁印的,只要发现某个特征不对就行。

1.2 素数够多吗

素数定理说,NN 附近大约每 ln⁡N\ln N 个数里有一个素数。对 1024 位的数,ln⁡21024≈710\ln 2^{1024} \approx 710;只挑奇数的话,平均试约 355 个就能碰到一个素数。每次测试都很快,所以生成素数并不慢。

1.3 "抽查"的思路

素数一定满足某些性质(比如费马小定理)。我们随机挑一些底数 aa 去检查:

关键是设计一种检查,让合数"蒙混过关"的概率足够低。Miller–Rabin 每轮至多 1/41/4 的概率被骗,做 40 轮,被骗概率不超过 4−40=2−804^{-40} = 2^{-80},比硬件出错的概率还低。


第二层:实际用处

素数生成出错的后果

  • 随机数不够随机:2012 年对全网 RSA 公钥的扫描发现,大量路由器、防火墙在开机熵不足时生成了共用素因子的密钥,求个 gcd⁡\gcd 就能分解(见《RSA与欧拉定理》)。
  • 素数有结构:ROCA 漏洞(2017)中,某芯片为了加速而用了特殊形式的素数,结果可以被分解。
  • 对手构造的"伪素数":2018 年的研究 Prime and Prejudice 发现,部分加密库对外部传入的数做素性检验时轮数太少或底数固定,攻击者可以专门构造能骗过它的合数,从而注入不安全的 DH 参数。

第三层:数学原理

3.1 费马检验

费马小定理:若 pp 是素数,gcd⁡(a,p)=1\gcd(a, p) = 1,则 ap−1≡1(modp)a^{p-1} \equiv 1 \pmod p。(这是 Zp×\mathbb{Z}_p^\times 阶为 p−1p-1 时的拉格朗日定理。)

检验:随机选 aa,算 aN−1 mod Na^{N-1} \bmod N。不等于 11,NN 一定是合数。

大多数合数都过不了这一关。例如 N=15N = 15,214=16384≡4(mod15)2^{14} = 16384 \equiv 4 \pmod{15},立刻露馅。

3.2 Carmichael 数

有一类合数,对所有与它互素的 aa 都满足 aN−1≡1a^{N-1} \equiv 1,叫 Carmichael 数。

Korselt 判据:合数 NN 是 Carmichael 数,当且仅当 NN 无平方因子,并且对每个素因子 p∣Np \mid N 都有 (p−1)∣(N−1)(p - 1) \mid (N - 1)。

最小的例子 561=3×11×17561 = 3 \times 11 \times 17:560560 能被 22、1010、1616 整除。由中国剩余定理,a560a^{560} 在模 33、1111、1717 下都是 11,所以模 561561 也是 11。

1994 年 Alford、Granville、Pomerance 证明了 Carmichael 数有无穷多个。所以单靠费马检验是不行的。

3.3 关键引理:模素数时 11 只有两个平方根

若 pp 是奇素数,x2≡1(modp)x^2 \equiv 1 \pmod p,则 p∣(x−1)(x+1)p \mid (x-1)(x+1)。因为 Zp\mathbb{Z}_p 是域(没有零因子),只能 x≡1x \equiv 1 或 x≡−1x \equiv -1。

反过来,如果找到 x≢±1x \not\equiv \pm 1 但 x2≡1(modN)x^2 \equiv 1 \pmod N,那么 ZN\mathbb{Z}_N 有零因子,NN 一定是合数(而且 gcd⁡(x−1,N)\gcd(x - 1, N) 就是一个非平凡因子)。

3.4 Miller–Rabin

把 N−1N - 1 写成 2s⋅d2^s \cdot d,dd 为奇数。随机选 aa,看这串数:

ad, a2d, a4d, …, a2sd=aN−1(modN).a^d,\ a^{2d},\ a^{4d},\ \dots,\ a^{2^s d} = a^{N-1} \pmod N.

每一项是前一项的平方。如果 NN 是素数,由费马小定理最后一项是 11;再由 3.3 的引理,倒着往回开平方,第一次离开 11 时必须是 −1-1。所以素数一定满足:

要么 ad≡1a^d \equiv 1,要么这串数里某一项等于 −1-1。

不满足就判定合数。Rabin 证明了:对合数 NN,至多 1/41/4 的底数 aa 能骗过这个检验。

3.5 手算例子:揭穿 561561

N=561N = 561,底数 a=2a = 2

费马检验:2560 mod 561=12^{560} \bmod 561 = 1。通过了,被骗。

Miller–Rabin:560=24×35560 = 2^4 \times 35,所以 s=4s = 4,d=35d = 35。

项 值  mod 561\bmod 561
2352^{35} 263263
270=26322^{70} = 263^2 166166
2140=16622^{140} = 166^2 6767
2280=6722^{280} = 67^2 11
25602^{560} 11

序列从 6767 直接跳到 11,中间没有经过 −1=560-1 = 560。也就是说 672≡167^2 \equiv 1 但 67≢±167 \not\equiv \pm 1,这在素数模下不可能发生,所以 561 是合数。

顺带还能拿到因子:gcd⁡(67−1,561)=gcd⁡(66,561)=33=3×11\gcd(67 - 1, 561) = \gcd(66, 561) = 33 = 3 \times 11,gcd⁡(67+1,561)=17\gcd(67 + 1, 561) = 17。(已用 Python 验算。)

3.6 确定性算法

2002 年,Agrawal、Kayal、Saxena 给出了 AKS 算法,第一次证明了素性检验可以在确定性多项式时间内完成(论文题目就叫 PRIMES is in P)。不过它比 Miller–Rabin 慢得多,实际中仍用概率算法。


总结

层次 要点
直觉 判断素数比分解容易得多;随机抽查,错一次就是合数
代数 费马小定理来自拉格朗日定理;Carmichael 数能骗过它
关键 域里 x2=1x^2 = 1 只有 ±1\pm 1;找到别的平方根就证明是合数
工程 试除 + 多轮 Miller–Rabin;随机数质量和轮数都不能省