09.3 數論專題#
本節目標#
- 學會用
isprime()判質數、factorint()做質因數分解、primepi()數質數個數 - 掌握模算術工具:
mod_inverse、冪次取模 \(a^b \bmod m\) - 建立 RSA 加密的數學直覺,並用小質數完成一次完整的加解密實驗
- 實驗:測量 SymPy 判斷大數是否為質數的速度
質數:isprime、factorint、primepi#
質數是數論的基石。SymPy 提供三個常用函數:
isprime(n):判斷 \(n\) 是否為質數(先做小質數試除,再用 Miller–Rabin 等機率/確定性測試)factorint(n):回傳質因數分解的字典{質因數: 次方}primepi(n):計算不超過 \(n\) 的質數個數,即質數計數函數 \(\pi(n)\)
from sympy import isprime, factorint, primepi
print(isprime(97)) # True
print(isprime(1)) # False(1 不是質數)
print(factorint(2024)) # {2: 3, 11: 1, 23: 1},即 2^3 * 11 * 23
print(primepi(100)) # 25:100 以內有 25 個質數
其他順手的小工具:
from sympy import primerange, prime, nextprime
print(list(primerange(10, 30))) # [11, 13, 17, 19, 23, 29]
print(prime(10)) # 29:第 10 個質數
print(nextprime(100)) # 101
模算術:mod_inverse 與冪次取模#
模算術就是「只看餘數」的算術。記號 \(a \equiv b \pmod m\) 表示 \(a\) 與 \(b\) 除以 \(m\) 的餘數相同。
乘法反元素:在模 \(m\) 之下, \(a\) 的反元素 \(a^{-1}\) 滿足 \(a \cdot a^{-1} \equiv 1 \pmod m\) ,存在條件是 \(\gcd(a, m) = 1\) :
from sympy import mod_inverse
print(mod_inverse(3, 11)) # 4,因為 3*4 = 12 ≡ 1 (mod 11)
print((3 * 4) % 11) # 1,驗證
print(mod_inverse(6, 15)) # ValueError:gcd(6, 15) = 3 ≠ 1
冪次取模 \(a^b \bmod m\) 是密碼學的核心運算。Python 內建的 pow(a, b, m) 用「平方-乘」快速演算法,即使指數大如天文數字也瞬間完成:
print(pow(7, 1000, 13)) # 9:7^1000 mod 13
print(pow(2, 10**100, 97)) # 大指數也瞬間算完
# 費馬小定理:p 質數時 a^(p-1) ≡ 1 (mod p)
print(pow(7, 96, 97)) # 1
離散對數的直覺:冪次運算 \(a^b \bmod m\) 很容易(快速冪),但反過來「已知 \(a^b \bmod m\) 和 \(a\) ,求 \(b\) 」卻極難——這就是離散對數問題。這種「正向易、反向難」的不對稱性正是公鑰密碼學的基礎:
from sympy.ntheory.residue_ntheory import discrete_log
# 3^x ≡ 13 (mod 17),x 是多少?(答案:x = 4,因為 3^4 = 81 ≡ 13)
print(discrete_log(17, 13, 3)) # 4
# 數字小時可以暴力找,數字一大就不可行了
RSA 的數學直覺#
RSA 建立在這個事實上:把兩個大質數相乘很容易,但把乘積分解回兩個質數極難。流程如下:
- 選兩個質數 \(p, q\) ,算 \(n = pq\) 與 \(\varphi(n) = (p-1)(q-1)\)
- 選 \(e\) 與 \(\varphi(n)\) 互質,公鑰是 \((e, n)\)
- 私鑰 \(d\) 是 \(e\) 在模 \(\varphi(n)\) 下的反元素: \(ed \equiv 1 \pmod{\varphi(n)}\)
- 加密: \(c = m^e \bmod n\) ;解密: \(m = c^d \bmod n\)
其正確性由尤拉定理 \(m^{\varphi(n)} \equiv 1 \pmod n\) 保證。動手做一個小例子:
from sympy import isprime, mod_inverse, randprime
# 1. 選兩個小質數
p, q = 61, 53
assert isprime(p) and isprime(q)
n = p * q
phi = (p - 1) * (q - 1)
print(f"n = {n}, phi(n) = {phi}") # n = 3233, phi = 3120
# 2. 選公鑰指數 e(與 phi 互質)
e = 17
from sympy import gcd
print(gcd(e, phi)) # 1
# 3. 算私鑰 d:e 在模 phi 下的反元素
d = mod_inverse(e, phi)
print(f"d = {d}") # 2753,因為 17*2753 = 46801 ≡ 1 (mod 3120)
# 4. 加密與解密:訊息 m 必須小於 n
m = 65 # 想傳的數字
c = pow(m, e, n)
print(f"密文 c = {c}") # 2790
print(f"解密回 m = {pow(c, d, n)}") # 65,正確還原
再驗證一次完整往返,並用隨機大質數產生鑰匙:
for m in range(2, 10):
c = pow(m, e, n)
assert pow(c, d, n) == m
print("所有往返測試通過")
# 隨機產生兩個 100 位元左右的大質數
p2 = randprime(10**99, 10**100)
q2 = randprime(10**99, 10**100)
print(isprime(p2), isprime(q2)) # True True
注意:真的 RSA 會用數百位元的質數。攻擊者只知道 \(n\) 與 \(e\) ,要算 \(d\) 就必須先對 \(n\) 做質因數分解——目前沒有效率的演算法能對大數做到。
動手實驗:判斷大數是否質數的速度#
isprime 對多大的數還能瞬間回答?用 timeit 實測:
import time
from sympy import isprime, factorint, nextprime
# 先做出幾個大數:質數與質數乘積(半質數)
p = nextprime(10**20)
q = nextprime(10**21)
semiprime = p * q
for n in [97, 10**12 + 39, p, semiprime]:
t0 = time.perf_counter()
result = isprime(n)
dt = time.perf_counter() - t0
print(f"isprime({n}): {result},耗時 {dt*1000:.3f} ms")
典型的輸出顯示:即使數字有 40 多位元數,isprime 也在幾毫秒內回答。接著對照組——質因數分解就慢多了:
t0 = time.perf_counter()
f = factorint(semiprime)
dt = time.perf_counter() - t0
print(f"factorint({semiprime}) = {f},耗時 {dt:.4f} 秒")
實驗延伸:把 \(p, q\) 的位數從 20 位逐步加倍到 40、60 位,觀察 isprime 幾乎不變慢,但 factorint 的時間急速成長。這條「判質數容易、分解難」的鴻溝,就是 RSA 安全性的實證直覺。
小結與練習#
本節重點
isprime(n)判質數、factorint(n)質因數分解、primepi(n)質數計數mod_inverse(a, m)求模反元素(存在條件 \(\gcd(a, m) = 1\) )pow(a, b, m)快速計算 \(a^b \bmod m\) ;離散對數是其反問題,且極難- RSA:公鑰 \((e, n)\) 加密 \(m^e \bmod n\) ,私鑰 \(d = e^{-1} \bmod \varphi(n)\) 解密
練習
- 求 \(\pi(1000)\) ,並估計 \(\pi(n) \approx n / \ln n\) 在 \(n = 1000\) 時的誤差。
- 解同餘方程 \(7x \equiv 3 \pmod{26}\) (提示:用
mod_inverse)。 - 用質數 \(p = 101, q = 113\) 建 RSA 鑰匙對(自己選 \(e\) ),加密 \(m = 42\) 再解密驗證。
- 實驗:用
discrete_log猜 \(2^x \bmod 101 = 1\) 的最小正整數 \(x\) ,並與費馬小定理的理論值 100 對照(提示:檢查所有因數)。