Miller-Rabin 素性检验本身是一种概率性的素性检验方式,作为一种随机化算法,它能够在短时间内判断一个数是否为素数(一定概率下还是会出错的)。

# Fermat 素性检验

一个简单的素性判定思路是使用 Fermat 小定理:

对于整数aa 和素数pp,有

ap11(mod p)a^{p-1}\equiv 1 (mod\ p)

对于我们任意选取的整数aa,如果pp 是一个质数,那么肯定是满足ap11(mod p)a^{p-1}\equiv 1 (mod\ p) 的。如果我们选取了足够多的aa 并且都经过了验证,那我们可以认为pp 非常有可能是一个素数。

def fermat_primality_test(p, k = 40):
    for _ in range(k):
        a = random.randint(2, p)
        if pow(a, p - 1, p) != 1:
            return False
    return True

然而问题在于,Fermat 小定理的逆命题不一定成立,也就是说 Fermat 小定理是pp 为素数的必要不充分条件。关于aa Fermat 伪素数 (Fermat Pseudoprime) 指的是满足an11(mod n)a^{n-1}\equiv 1(mod\ n) 的合数nn。例如23401(mod 341)2^340\equiv 1(mod\ 341),也就是说 341 是以 2 为底的伪素数。

然而,334056(mod 341)3^340\equiv 56(mod\ 341),经过多轮aa 的选择,我们似乎可以回避掉pp 为伪素数的情况,但是实际上并不是。

Carmichael 数是一类特殊的伪素数:一个合数nn 如果对于所有和nn 互素的数aa,都满足an11(mod n)a^{n-1}\equiv 1(mod\ n),那么这个数就是 Carmichael 数。可以理解为 Carmichael 数是一类特殊的伪素数,这类数能够有比较大的可能性逃过 Fermat 素性检验。最小的 Carmichael 数是 561。

# Miller-Rabin 素性检验

前面的 Fermat 素性检验对于比较大的数有一定的错误概率,因此需要有一些办法来改进素性检验。

我们可以注意到有下面情况:

二次探测定理: 对于一个素数pp,整数xx 满足x21(mod p)x^2\equiv 1(mod\ p),那么xx 可能的解只有11p1p-1

可以这样证明:x21(mod p)x^2\equiv 1(mod\ p),则有x2(x+1)(x1)0(mod p)x^2\equiv(x+1)(x-1)\equiv 0(mod\ p),那么很明显解只能是x1(mod p)x\equiv 1(mod\ p) 或者xp1(mod p)x\equiv p-1(mod\ p)

而前面的 Fermat 素性检验判定的是ap11(mod p)a^{p-1}\equiv 1(mod\ p)。我们注意到p1p-1 明显应该是一个素数,因此ap12a^{\frac{p-1}{2}} 在模pp 意义下只能是11 或者p1p-1。如果结果为11,那么可以继续拆分p14,p18,...\cfrac{p-1}{4},\cfrac{p-1}{8},...,直到结果为一个奇数。

这也就是 Miller-Rabin 素性检验的基本思想。对于待检验的数pp,我们将p1p-1 分解为2rt2^r\cdot t 的形式,其中tt 为一个奇数。

接下来,我们选择一个数aa,验证atmod p,a2tmod p,a4tmod p,...,a2rtmod pa^{t}\mod\ p,a^{2t}\mod\ p,a^{4t}\mod\ p,...,a^{2^rt}\mod\ p 的值。如果这一系列数都为 1,那么pp 是满足素数的条件的。如果其中出现了一个p1p-1,在这之后的数都是 1,那么pp 同样是满足素数的条件。但是如果一开始不为 1,后面出现了一个 1,但是在这之前的值不为p1p-1,也就是不满足二次探测定理,那么pp 不是素数。如果a2rtmod pa^{2^rt}\mod\ p 不为 1,也就是不满足 Fermat 小定理,pp 必定不是素数。

有了上面的思想,我们可以去实现 Miller-Rabin 素性检验。

def miller_rabin_test(p, k = 64):
    # 特判一些特殊情况
    if p == 2 or p == 3:
        return True
    if p % 2 == 0:
        return False
    # p-1=t*2^r
    t, r = p - 1, 0
    while t % 2 == 0:
        t //= 2
        r += 1
    for _ in range(k):
        # 重复 k 次判定
        a = random.randint(2, p)
        v = pow(a, t, p)
        if v == 1 or v == p - 1:
            # 后面肯定都是 1,通过
            continue
        for i in range(r):
            # 验证 2t,2^2t,... 是否满足
            v = pow(v, 2, p)
            if v == p - 1 and i != r - 1:
                # 不是在结尾的时候出现了 p-1,说明满足定理
                v = 1
                break
            if v == 1:
                # 中途直接出现了 1,不满足二次探测定理,不是素数
                return False
        # 用 Fermat 小定理进行判断
        if v != 1:
            return False
    return True

可以看出,Miller-Rabin 素性检验算法的时间复杂度为O(klogn3)O(k\log{n}^3),而每一次选取随机数aa 之后,如果通过了检验,那么数pp1/41/4 的概率不是素数。重复kk 次之后可以有14k1-4^{-k} 的概率确信pp 是一个素数。

Miller-Rabin 算法基本上就能够确定一个数是素数,但是毕竟还是有一定的出错概率的。我们实际上可以将这个算法进行确定化。

最早 Miller 提出的素性检验算法实际上是一个确定性算法,然而这个算法依赖了目前未被证明的广义 Riemann 猜想 (GRH),因而 Rabin 在此基础上进行了改进,提出了 Miller-Rabin 算法,也就是不依赖于 GRH 的概率性素性检验算法。

在 GRH 成立的情况下,如果pp 可以通过以2,3,...2log2n2,3,...\lfloor 2\log^2n\rfloor 为底数的 Miller-Rabin 素性检验,那么pp 一定是素数。

这个数据量对于较大的素数pp 明显并不现实,一个改进是采取前若干个素数进行检验。对于2782^{78} 以内的数,采用前 12 个素数作为底数就可以验证。不过对于密码学常用的数据量来说,还是并不够用。

此文章已被阅读次数:正在加载...更新于