现代密码学的数学地基:从模运算到离散对数
-
- 前言
- [1. 模运算:密码学的运算基石](#1. 模运算:密码学的运算基石)
-
- [1.1 什么是模运算](#1.1 什么是模运算)
- [1.2 模运算的基本性质](#1.2 模运算的基本性质)
- [1.3 模幂运算与快速幂](#1.3 模幂运算与快速幂)
- [1.4 为什么密码学偏爱模运算](#1.4 为什么密码学偏爱模运算)
- [2. 欧几里得算法与逆元](#2. 欧几里得算法与逆元)
-
- [2.1 欧几里得算法求最大公约数](#2.1 欧几里得算法求最大公约数)
- [2.2 扩展欧几里得算法](#2.2 扩展欧几里得算法)
- [2.3 模逆元的存在条件与求解](#2.3 模逆元的存在条件与求解)
- [3. 三大数论定理](#3. 三大数论定理)
-
- [3.1 费马小定理](#3.1 费马小定理)
- [3.2 欧拉定理与欧拉函数](#3.2 欧拉定理与欧拉函数)
- [3.3 中国剩余定理(CRT)](#3.3 中国剩余定理(CRT))
- [4. 离散对数问题](#4. 离散对数问题)
-
- [4.1 什么是离散对数](#4.1 什么是离散对数)
- [4.2 离散对数的"难解性"](#4.2 离散对数的"难解性")
- [4.3 离散对数在密码学中的应用](#4.3 离散对数在密码学中的应用)
- [5. 抽象代数基础](#5. 抽象代数基础)
-
- [5.1 群、环、域的概念](#5.1 群、环、域的概念)
- [5.2 有限域 GF(p)](#5.2 有限域 GF(p))
- [5.3 有限域 GF(2ⁿ)](#5.3 有限域 GF(2ⁿ))
- [5.4 有限域在密码学中的作用](#5.4 有限域在密码学中的作用)
前言
很多开发者熟悉密码学库的API调用,却对底层原理感到陌生------模运算为何能构建加密体系?离散对数为什么是安全性的基石?
本文作为系列开篇,将从程序员的视角出发,用实例而非纯公式,拆解现代密码学的核心数学工具:模运算与欧几里得算法、费马小定理与中国剩余定理、离散对数问题,以及群环域的基本概念。
1. 模运算:密码学的运算基石
1.1 什么是模运算
模运算(Modular Arithmetic),简单说就是**"取余数"的运算**。
你小学就学过:7 除以 3,商是 2,余数是 1。用模运算的写法就是:
7 mod 3 = 1
翻译成程序员的话,就是 7 % 3 == 1。
但模运算远不止"求余"这么简单------它本质上是把无限的整数映射到一个有限的集合里。比如模 12 的世界里,所有整数都会被压缩到 0~11 这 12 个数里:
base
13 mod 12 = 1
25 mod 12 = 1
-1 mod 12 = 11
你可以把它想象成一个钟表:时针从 12 点走到 13 点,表盘上显示的还是 1 点。这就是模 12 的直观理解。
💡 核心直觉:模运算 = 有限范围内的算术。无论数字多大,做完 mod 之后都会被"折叠"回一个固定大小的空间里。
1.2 模运算的基本性质
模运算之所以能构建加密体系,关键在于它的代数性质非常好------加法、减法、乘法都可以"先算再取模",也可以"取模再算",结果一样。
加法性质:
( a + b ) m o d m = ( a m o d m ) + ( b m o d m ) m o d m (a + b) \bmod m = (a \\bmod m) + (b \\bmod m) \bmod m (a+b)modm=(amodm)+(bmodm)modm
base
(17 + 23) mod 7 = 40 mod 7 = 5
// 等价于
(17 mod 7 + 23 mod 7) mod 7 = (3 + 2) mod 7 = 5
乘法性质:
( a × b ) m o d m = ( a m o d m ) × ( b m o d m ) m o d m (a \times b) \bmod m = (a \\bmod m) \\times (b \\bmod m) \bmod m (a×b)modm=(amodm)×(bmodm)modm
base
(17 × 23) mod 7 = 391 mod 7 = 6
// 等价于
(17 mod 7 × 23 mod 7) mod 7 = (3 × 2) mod 7 = 6
减法性质:
( a − b ) m o d m = ( a m o d m ) − ( b m o d m ) + m m o d m (a - b) \bmod m = (a \\bmod m) - (b \\bmod m) + m \bmod m (a−b)modm=(amodm)−(bmodm)+mmodm
⚠️ 注意减法可能出现负数,所以要加 m 再取模,保证结果非负。
这些性质意味着什么?数字再大,你都可以随时取模缩小,不影响最终结果。 这对密码学至关重要------因为加密算法要处理的数字动辄几百上千位,不靠模运算随时"瘦身",计算机根本算不动。
1.3 模幂运算与快速幂
模幂运算就是 "先求幂,再取模":
a b m o d m a^b \bmod m abmodm
比如 3 5 m o d 7 3^5 \bmod 7 35mod7:
base
3^5 = 243
243 mod 7 = 5
但如果指数很大呢?比如 3 1000000 m o d 7 3^{1000000} \bmod 7 31000000mod7,直接算 3 1000000 3^{1000000} 31000000 会得到一个几十万位的数,根本存不下。
这时候就要用快速幂算法(Fast Exponentiation)------利用指数的二进制分解,把时间复杂度从 O(n) 降到 O(log n)。
核心思想:指数折半,底数平方。
比如计算 3 13 m o d 7 3^{13} \bmod 7 313mod7:
- 13 的二进制是
1101,即 13 = 8 + 4 + 1 13 = 8 + 4 + 1 13=8+4+1 - 所以 3 13 = 3 8 × 3 4 × 3 1 3^{13} = 3^8 \times 3^4 \times 3^1 313=38×34×31
- 每一步都可以通过前一步平方得到: 3 2 , 3 4 , 3 8 3^2, 3^4, 3^8 32,34,38...
python
def fast_pow(base, exponent, mod):
result = 1
base = base % mod
while exponent > 0:
if exponent & 1:
result = (result * base) % mod
base = (base * base) % mod
exponent = exponent >> 1
return result
print(fast_pow(3, 13, 7)) # 输出 5
print(fast_pow(3, 1000000, 7)) # 瞬间出结果
💡 快速幂是密码学的"基础设施"。RSA、Diffie-Hellman 等算法的核心运算都是模幂,没有快速幂,这些算法根本跑不起来。
1.4 为什么密码学偏爱模运算
讲到这里,你可能会问:不就是取个余吗,凭什么能撑起整个密码学?
答案在于三个关键词:有限性、单向性、代数结构。
① 有限性:把无限压缩进有限空间
普通的整数加法/乘法,数字会越算越大。但在模运算的世界里,无论怎么算,结果永远落在 0 到 m-1 之间。存储空间可控、运算效率稳定、还能定义"逆运算"。
② 单向性:正向容易反向难
来看一个问题:已知 3 x m o d 7 = 5 3^x \bmod 7 = 5 3xmod7=5,求 x 是多少?
你得一个个试: 3 1 = 3 3^1=3 31=3、 3 2 = 2 3^2=2 32=2、 3 3 = 6 3^3=6 33=6、 3 4 = 4 3^4=4 34=4、 3 5 = 5 3^5=5 35=5... 直到试出 x = 5。
数字小的时候还好,但如果模数是一个 2048 位的大质数呢?
- 正向计算 :给你 a 和 x,算 a x m o d p a^x \bmod p axmodp → 用快速幂,毫秒级出结果
- 反向求解 :给你 a 和 a x m o d p a^x \bmod p axmodp,求 x → 目前没有高效算法,暴力破解要算到宇宙毁灭
这就是离散对数问题(下一章会详细讲),也是现代公钥密码学的安全性根基。
③ 代数结构:模运算构成"群"
模 m 的整数集合,配上加法运算,构成一个数学上的"群";模 m 的乘法(只看与 m 互质的数),也构成一个"群"。
群是什么?简单说就是满足封闭性、结合律、有单位元、有逆元的代数结构。这些性质是构建加密算法的数学前提------没有逆元,解密就做不到;没有封闭性,运算就不完整。
🔑 一句话总结:模运算把"无限的整数"关进"有限的笼子"里,还保留了漂亮的代数性质,让"正向容易、反向困难"成为可能------这就是密码学需要的全部。
2. 欧几里得算法与逆元
2.1 欧几里得算法求最大公约数
最大公约数(Greatest Common Divisor,简称 GCD),指的是两个整数共有约数中最大的那个。比如 g c d ( 12 , 18 ) = 6 gcd(12, 18) = 6 gcd(12,18)=6,因为 12 和 18 的公约数有 1、2、3、6,最大的是 6。
求 GCD 是数论中最基础的问题之一,也是密码学中很多算法的基石------RSA 的密钥生成、模逆元求解、中国剩余定理,全都离不开它。
朴素做法:从大到小枚举
最容易想到的方法是:从两个数中较小的那个开始,往下一个个试,直到找到能同时整除两个数的数。
python
def gcd_naive(a, b):
n = min(a, b)
for i in range(n, 0, -1):
if a % i == 0 and b % i == 0:
return i
return 1
但这个方法的时间复杂度是 O ( n ) O(n) O(n),如果 a 和 b 是密码学中常用的几百位大数,这种方法根本跑不完。
欧几里得算法:辗转相除法
公元前 300 年,欧几里得在《几何原本》中提出了一个极其优雅的算法,核心只有一句话,
两个整数 a,b 的最大公约数,等于 b 与 a 模 b 的最大公约数,写成公式:
g c d ( a , b ) = g c d ( b , a m o d b ) gcd(a, b) = gcd(b, a \bmod b) gcd(a,b)=gcd(b,amodb)
反复用这个等式递推,直到 b 变成 0,此时 a 就是最大公约数。
完整手算示例
我们来求 g c d ( 1071 , 462 ) gcd(1071, 462) gcd(1071,462),一步步递推:
第 1 步:计算 gcd(1071, 462)
用 1071 除以 462,商 2,余 147
即 1071 mod 462 = 147
根据 gcd(a, b) = gcd(b, a mod b),递推为 gcd(462, 147)
第 2 步:计算 gcd(462, 147)
用 462 除以 147,商 3,余 21
即 462 mod 147 = 21
递推为 gcd(147, 21)
第 3 步:计算 gcd(147, 21)
用 147 除以 21,商 7,余 0
即 147 mod 21 = 0
递推为 gcd(21, 0)
第 4 步:计算 gcd(21, 0)
当第二个数为 0 时,第一个数就是最大公约数
所以 gcd(21, 0) = 21
最终答案:g c d ( 1071 , 462 ) = 21 gcd(1071, 462) = 21 gcd(1071,462)=21
四步就出结果了,比枚举法快得多。
为什么这个等式成立?
这是整个算法的核心,我们来推导一下。
要证明: g c d ( a , b ) = g c d ( b , a m o d b ) gcd(a, b) = gcd(b, a \bmod b) gcd(a,b)=gcd(b,amodb)
设 d = g c d ( a , b ) d = gcd(a, b) d=gcd(a,b),即 d d d 是 a a a 和 b b b 的最大公约数。这意味着 d d d 整除 a a a,也整除 b b b。
根据带余除法,对任意整数 a a a、正整数 b b b,一定存在唯一一对整数 q , r q, r q,r,使得:
a = q × b + r , 0 ≤ r < b a = q \times b + r,\quad 0 \le r < b a=q×b+r,0≤r<b
式中:
q q q 叫做商 ,代表 a a a 里最多能容纳多少个完整的 b b b;
r r r 就是余数,等价于 a m o d b a \bmod b amodb。
所以 r = a − q × b r = a - q \times b r=a−q×b。
现在看 d d d 和 r r r 的关系:
- d d d 整除 a a a, d d d 也整除 b b b,因此 d d d 一定整除 q × b q \times b q×b(整数倍不改变整除关系)
- 两个 d d d 的倍数相减,结果仍是 d d d 的倍数
- 因此 d d d 整除 r r r,即 d d d 也是 b b b 和 r r r 的公约数
反过来,设 d ′ = g c d ( b , r ) d' = gcd(b, r) d′=gcd(b,r):
- d ′ d' d′ 整除 b b b,也整除 r r r
- a = q × b + r a = q \times b + r a=q×b+r,两个 d ′ d' d′ 的倍数相加,结果仍是 d ′ d' d′ 的倍数
- 所以 d ′ d' d′ 整除 a a a,即 d ′ d' d′ 也是 a a a 和 b b b 的公约数
a , b a,b a,b 的全体公约数 和 b , r b,r b,r 的全体公约数完全一致,二者最大公约数必然相等。因此 g c d ( a , b ) = g c d ( b , r ) gcd(a, b) = gcd(b, r) gcd(a,b)=gcd(b,r)。
Python 实现
迭代版本,最常用也最稳健:
python
def gcd(a, b):
while b != 0:
a, b = b, a % b
return a
print(gcd(1071, 462)) # 输出 21
print(gcd(48, 18)) # 输出 6
递归版本,代码更简洁但大数下可能有栈溢出风险:
python
def gcd_recursive(a, b):
if b == 0:
return a
return gcd_recursive(b, a % b)
时间复杂度分析
欧几里得算法的时间复杂度是 O ( log min ( a , b ) ) O(\log \min(a, b)) O(logmin(a,b))。
为什么这么快?因为每一步之后,数字至少会缩小到原来的 2/3(实际上更快,接近黄金比例)。这意味着即使 a 和 b 是 1000 位的大数,也只需要几千次迭代------这在密码学中完全可以接受。
对比一下:
- 枚举法: O ( n ) O(n) O(n),1000 位数要算 10 1000 10^{1000} 101000 次,宇宙毁灭也算不完
- 欧几里得: O ( log n ) O(\log n) O(logn),1000 位数大约算几千次,毫秒级完成
💡 欧几里得算法是已知最古老的算法之一,距今已有 2300 多年历史。它的优雅和高效,让它成为了整个数论大厦的第一块砖。
2.2 扩展欧几里得算法
欧几里得算法只能求出 GCD,但在密码学中,我们经常需要更多的信息------比如找到两个整数 x 和 y,使得:
a × x + b × y = g c d ( a , b ) a \times x + b \times y = gcd(a, b) a×x+b×y=gcd(a,b)
这就是贝祖定理(Bézout's Identity) :对于任意整数 a 和 b,一定存在整数 x 和 y,使得 a x + b y = g c d ( a , b ) ax + by = gcd(a, b) ax+by=gcd(a,b)。
扩展欧几里得算法(Extended Euclidean Algorithm) 就是用来求这组 x 和 y 的。
算法原理
扩展欧几里得算法在普通欧几里得算法的基础上,多维护了两组系数。它的核心思路是"回代":
-
当 b = 0 b = 0 b=0 时, g c d ( a , 0 ) = a gcd(a, 0) = a gcd(a,0)=a,此时 x = 1 x = 1 x=1, y = 0 y = 0 y=0
- 验证: a × 1 + 0 × 0 = a a \times 1 + 0 \times 0 = a a×1+0×0=a ✓
-
当 b ≠ 0 b \neq 0 b=0 时,先递归求 g c d ( b , a m o d b ) gcd(b, a \bmod b) gcd(b,amodb),得到一组解 ( x ′ , y ′ ) (x', y') (x′,y′),满足:
- b × x ′ + ( a m o d b ) × y ′ = g c d ( a , b ) b \times x' + (a \bmod b) \times y' = gcd(a, b) b×x′+(amodb)×y′=gcd(a,b)
-
把 a m o d b a \bmod b amodb 展开: a m o d b = a − ⌊ a / b ⌋ × b a \bmod b = a - \lfloor a / b \rfloor \times b amodb=a−⌊a/b⌋×b
- 代入上式: b × x ′ + ( a − ⌊ a / b ⌋ × b ) × y ′ = g c d ( a , b ) b \times x' + (a - \lfloor a / b \rfloor \times b) \times y' = gcd(a, b) b×x′+(a−⌊a/b⌋×b)×y′=gcd(a,b)
-
整理一下,把 a 的项和 b 的项分开:
- a × y ′ + b × ( x ′ − ⌊ a / b ⌋ × y ′ ) = g c d ( a , b ) a \times y' + b \times (x' - \lfloor a / b \rfloor \times y') = gcd(a, b) a×y′+b×(x′−⌊a/b⌋×y′)=gcd(a,b)
-
对比 a x + b y = g c d ( a , b ) ax + by = gcd(a, b) ax+by=gcd(a,b),得到递推公式:
- x = y ′ x = y' x=y′
- y = x ′ − ⌊ a / b ⌋ × y ′ y = x' - \lfloor a / b \rfloor \times y' y=x′−⌊a/b⌋×y′
完整手算示例
我们来求 1071 x + 462 y = 21 1071x + 462y = 21 1071x+462y=21 的一组整数解。
第一步,先正向跑一遍普通欧几里得,把每一步的商记下来:
第 1 层:gcd(1071, 462)
1071 ÷ 462 = 2 余 147
商 q₁ = 2,余数 r₁ = 147
第 2 层:gcd(462, 147)
462 ÷ 147 = 3 余 21
商 q₂ = 3,余数 r₂ = 21
第 3 层:gcd(147, 21)
147 ÷ 21 = 7 余 0
商 q₃ = 7,余数 r₃ = 0
第 4 层:gcd(21, 0) = 21
到达最底层,此时 x = 1, y = 0
即 21 × 1 + 0 × 0 = 21
第二步,从最底层开始往回代,逐层推出 x 和 y:
回代第 3 层(对应 gcd(147, 21)):
上一层的结果:21 × 1 + 0 × 0 = 21
我们要的形式:147 × x + 21 × y = 21
从第 3 层的等式:147 = 7 × 21 + 0,即 0 = 147 - 7 × 21
把 0 代入上式:
21 × 1 + (147 - 7 × 21) × 0 = 21
147 × 0 + 21 × 1 = 21
得到 x = 0,y = 1
回代第 2 层(对应 gcd(462, 147)):
上一层的结果:147 × 0 + 21 × 1 = 21
我们要的形式:462 × x + 147 × y = 21
从第 2 层的等式:462 = 3 × 147 + 21,即 21 = 462 - 3 × 147
把 21 代入上式:
147 × 0 + (462 - 3 × 147) × 1 = 21
462 × 1 + 147 × (-3) = 21
得到 x = 1,y = -3
回代第 1 层(对应 gcd(1071, 462)):
上一层的结果:462 × 1 + 147 × (-3) = 21
我们要的形式:1071 × x + 462 × y = 21
从第 1 层的等式:1071 = 2 × 462 + 147,即 147 = 1071 - 2 × 462
把 147 代入上式:
462 × 1 + (1071 - 2 × 462) × (-3) = 21
462 × 1 + 1071 × (-3) + 462 × 6 = 21
1071 × (-3) + 462 × 7 = 21
得到 x = -3,y = 7
最终答案:x = − 3 x = -3 x=−3, y = 7 y = 7 y=7
验证: 1071 × ( − 3 ) + 462 × 7 = − 3213 + 3234 = 21 1071 \times (-3) + 462 \times 7 = -3213 + 3234 = 21 1071×(−3)+462×7=−3213+3234=21 ✓
这里x,y到底是怎么定下来(其实就是展开)我们拿 147 × 0 + ( 462 − 3 × 147 ) × 1 = 21 147×0+(462−3×147)×1=21 147×0+(462−3×147)×1=21举例
原式:
147 × 0 + ( 462 − 3 × 147 ) × 1 = 21 147 \times 0 + (462 - 3 \times 147) \times 1 = 21 147×0+(462−3×147)×1=21
- 把括号乘开
147 × 0 + 462 × 1 − 3 × 147 × 1 = 21 147 \times 0 + 462 \times 1 - 3 \times 147 \times 1 = 21 147×0+462×1−3×147×1=21- 化简每一项
147 × 0 = 0 147 \times 0 = 0 147×0=0, 3 × 147 × 1 = 3 × 147 3 \times 147 \times 1 = 3 \times 147 3×147×1=3×147,式子变为:
0 + 462 × 1 − 3 × 147 = 21 0 + 462 \times 1 - 3 \times 147 = 21 0+462×1−3×147=21- 调换两项顺序,写成标准 A x + B y Ax+By Ax+By 形式
462 × 1 + 147 × ( − 3 ) = 21 462 \times 1 + 147 \times (-3) = 21 462×1+147×(−3)=21
对比格式 462 ⋅ x + 147 ⋅ y = 21 462 \cdot x + 147 \cdot y = 21 462⋅x+147⋅y=21
对应得到:
x = 1 , y = − 3 x=1,\ y=-3 x=1, y=−3
递归实现
python
def ext_gcd(a, b):
if b == 0:
return a, 1, 0
g, x1, y1 = ext_gcd(b, a % b)
x = y1
y = x1 - (a // b) * y1
return g, x, y
g, x, y = ext_gcd(1071, 462)
print(f"gcd={g}, x={x}, y={y}") # 输出 gcd=21, x=-3, y=7
迭代实现
递归虽然简洁,但对于极大的数可能有栈溢出风险。迭代版本更稳健,也是实际库函数中常用的实现:
python
def ext_gcd_iter(a, b):
old_r, r = a, b
old_s, s = 1, 0
old_t, t = 0, 1
while r != 0:
quotient = old_r // r
# 更新余数
old_r, r = r, old_r - quotient * r
# 更新 a 的系数
old_s, s = s, old_s - quotient * s
# 更新 b 的系数
old_t, t = t, old_t - quotient * t
return old_r, old_s, old_t
g, x, y = ext_gcd_iter(1071, 462)
print(f"gcd={g}, x={x}, y={y}") # 同样输出 gcd=21, x=-3, y=7
迭代版本维护了三组变量,每一步都用商来更新。它的本质和递归是一样的,只是把递归的"栈"变成了显式的变量更新。
解的不唯一性
贝祖定理的解不是唯一的。如果 ( x 0 , y 0 ) (x_0, y_0) (x0,y0) 是一组解,那么所有解可以表示为:
x = x 0 + k × b g c d ( a , b ) x = x_0 + k \times \frac{b}{gcd(a, b)} x=x0+k×gcd(a,b)b
y = y 0 − k × a g c d ( a , b ) y = y_0 - k \times \frac{a}{gcd(a, b)} y=y0−k×gcd(a,b)a
其中 k 是任意整数。
验证一下,还是用刚才的例子: x 0 = − 3 , y 0 = 7 , g c d = 21 x_0 = -3, y_0 = 7, gcd = 21 x0=−3,y0=7,gcd=21
取 k = 1 k = 1 k=1:
- x = − 3 + 1 × ( 462 / 21 ) = − 3 + 22 = 19 x = -3 + 1 \times (462 / 21) = -3 + 22 = 19 x=−3+1×(462/21)=−3+22=19
- y = 7 − 1 × ( 1071 / 21 ) = 7 − 51 = − 44 y = 7 - 1 \times (1071 / 21) = 7 - 51 = -44 y=7−1×(1071/21)=7−51=−44
- 验证: 1071 × 19 + 462 × ( − 44 ) = 20349 − 20328 = 21 1071 \times 19 + 462 \times (-44) = 20349 - 20328 = 21 1071×19+462×(−44)=20349−20328=21 ✓
💡 扩展欧几里得算法是求模逆元的标准方法,而模逆元是 RSA、ElGamal 等公钥密码算法的核心运算。可以说,没有扩展欧几里得,就没有现代公钥密码学。
2.3 模逆元的存在条件与求解
什么是模逆元?
在普通算术中,一个数 a 的逆元就是它的倒数 1 / a 1/a 1/a,因为 a × ( 1 / a ) = 1 a \times (1/a) = 1 a×(1/a)=1。
在模运算中,逆元的概念类似:如果存在一个整数 x,使得
a × x ≡ 1 ( m o d m ) a \times x \equiv 1 \pmod{m} a×x≡1(modm)
那么 x 就叫做 a 在模 m 下的乘法逆元 ,记作 a − 1 m o d m a^{-1} \bmod m a−1modm。
举个例子: 3 × 5 = 15 3 \times 5 = 15 3×5=15, 15 m o d 7 = 1 15 \bmod 7 = 1 15mod7=1,所以 3 在模 7 下的逆元是 5。
补充说明:在区间 0 ≤ x < m 0 \le x < m 0≤x<m 内,满足同余式的解有且仅有一个;全体整数中存在无穷多等价解,但它们对 m m m 取模后结果完全相同,密码学运算统一使用区间内唯一的最小非负解。
逆元存在的条件
有了逆元,模运算中的"除法"就可以转化为"乘以逆元",就像普通算术中除以 a 等于乘以 1 / a 1/a 1/a 一样。
不是所有数在任意模数下都有逆元。
a 在模 m 下有逆元,当且仅当 g c d ( a , m ) = 1 gcd(a, m) = 1 gcd(a,m)=1,即 a 和 m 互质。
为什么?我们来证明一下。
必要性(有逆元 → 互质)
假设 a 在模 m 下有逆元 x,即 a x ≡ 1 ( m o d m ) ax \equiv 1 \pmod{m} ax≡1(modm)。
这意味着 a x − 1 = k m ax - 1 = km ax−1=km(k 是某个整数)。
整理一下: a x − k m = 1 ax - km = 1 ax−km=1。
根据贝祖定理,方程 a x + m y = 1 ax + my = 1 ax+my=1 有整数解的充要条件是 g c d ( a , m ) gcd(a, m) gcd(a,m) 整除 1。
而能整除 1 的正整数只有 1,所以 g c d ( a , m ) = 1 gcd(a, m) = 1 gcd(a,m)=1。
充分性(互质 → 有逆元)
反过来,如果 g c d ( a , m ) = 1 gcd(a, m) = 1 gcd(a,m)=1,根据贝祖定理,存在整数 x 和 y 使得 a x + m y = 1 ax + my = 1 ax+my=1。
两边 mod m,my 这一项就没了,得到 a x ≡ 1 ( m o d m ) ax \equiv 1 \pmod{m} ax≡1(modm)。
所以 x 就是 a 的逆元。
举几个例子:
- 3 和 7 互质( g c d = 1 gcd=1 gcd=1)→ 3 在模 7 下有逆元 ✓
- 6 和 9 不互质( g c d = 3 gcd=3 gcd=3)→ 6 在模 9 下没有逆元 ✗
- 验证一下:6×1=6, 6×2=12≡3, 6×3=18≡0, 6×4=24≡6... 确实永远得不到 1
方法一:扩展欧几里得算法求逆元
这是最通用的方法,适用于任何互质的 a 和 m。
思路:求 a 的逆元,就是找 x 使得 a x ≡ 1 ( m o d m ) ax \equiv 1 \pmod{m} ax≡1(modm),也就是找 x 和 y 使得 a x + m y = 1 ax + my = 1 ax+my=1。
这正好是扩展欧几里得算法能解决的问题------当 g c d ( a , m ) = 1 gcd(a, m) = 1 gcd(a,m)=1 时,扩展欧几里得返回的 x 就是逆元。
完整手算示例
求 7 在模 26 下的逆元(这是仿射密码中常用的)。
即解方程: 7 x ≡ 1 ( m o d 26 ) 7x \equiv 1 \pmod{26} 7x≡1(mod26),也就是找 x 使得 7 x + 26 y = 1 7x + 26y = 1 7x+26y=1。
第一步,正向跑欧几里得:
第 1 层:gcd(26, 7)
26 ÷ 7 = 3 余 5
商 q₁ = 3,余数 5
第 2 层:gcd(7, 5)
7 ÷ 5 = 1 余 2
商 q₂ = 1,余数 2
第 3 层:gcd(5, 2)
5 ÷ 2 = 2 余 1
商 q₃ = 2,余数 1
第 4 层:gcd(2, 1)
2 ÷ 1 = 2 余 0
商 q₄ = 2,余数 0
第 5 层:gcd(1, 0) = 1
到达最底层,x = 1, y = 0
即 1 × 1 + 0 × 0 = 1
第二步,往回代:
回代第 4 层(gcd(2, 1)):
上一层:1 × 1 + 0 × 0 = 1
由 2 = 2 × 1 + 0,得 0 = 2 - 2 × 1
代入:1 × 1 + (2 - 2 × 1) × 0 = 1
整理:2 × 0 + 1 × 1 = 1
x = 0, y = 1
回代第 3 层(gcd(5, 2)):
上一层:2 × 0 + 1 × 1 = 1
由 5 = 2 × 2 + 1,得 1 = 5 - 2 × 2
代入:2 × 0 + (5 - 2 × 2) × 1 = 1
整理:5 × 1 + 2 × (-2) = 1
x = 1, y = -2
回代第 2 层(gcd(7, 5)):
上一层:5 × 1 + 2 × (-2) = 1
由 7 = 1 × 5 + 2,得 2 = 7 - 1 × 5
代入:5 × 1 + (7 - 1 × 5) × (-2) = 1
展开:5 × 1 + 7 × (-2) + 5 × 2 = 1
整理:7 × (-2) + 5 × 3 = 1
x = -2, y = 3
回代第 1 层(gcd(26, 7)):
上一层:7 × (-2) + 5 × 3 = 1
由 26 = 3 × 7 + 5,得 5 = 26 - 3 × 7
代入:7 × (-2) + (26 - 3 × 7) × 3 = 1
展开:7 × (-2) + 26 × 3 - 7 × 9 = 1
整理:26 × 3 + 7 × (-11) = 1
x = 3, y = -11
所以 7 × ( − 11 ) ≡ 1 ( m o d 26 ) 7 \times (-11) \equiv 1 \pmod{26} 7×(−11)≡1(mod26)。
逆元通常取正的, − 11 m o d 26 = 15 -11 \bmod 26 = 15 −11mod26=15。
最终答案:7 在模 26 下的逆元是 15
验证: 7 × 15 = 105 7 \times 15 = 105 7×15=105, 105 m o d 26 = 105 − 4 × 26 = 105 − 104 = 1 105 \bmod 26 = 105 - 4 \times 26 = 105 - 104 = 1 105mod26=105−4×26=105−104=1 ✓
Python 实现
python
def mod_inverse(a, m):
g, x, _ = ext_gcd(a, m)
if g != 1:
return None # 逆元不存在
return x % m # 保证结果为正
print(mod_inverse(7, 26)) # 输出 15
print(mod_inverse(3, 7)) # 输出 5
print(mod_inverse(6, 9)) # 输出 None(不互质,无逆元)
Python 3.8 及以上版本还有更简便的写法:pow(a, -1, m),内部就是用扩展欧几里得实现的。
方法二:费马小定理求逆元(关于费马小定理后面会讲的到时候可以回过来看)
当模数 m 是质数的时候,有一个更简单的方法------费马小定理。
费马小定理说:如果 p 是质数,且 a 不是 p 的倍数,那么 a p − 1 ≡ 1 ( m o d p ) a^{p-1} \equiv 1 \pmod{p} ap−1≡1(modp)。
两边同时乘以 a − 1 a^{-1} a−1,得:
a p − 2 ≡ a − 1 ( m o d p ) a^{p-2} \equiv a^{-1} \pmod{p} ap−2≡a−1(modp)
所以 a 的逆元就是 a p − 2 m o d p a^{p-2} \bmod p ap−2modp,用快速幂就能算。
例子:求 3 在模 7 下的逆元
- 3 7 − 2 = 3 5 = 243 3^{7-2} = 3^5 = 243 37−2=35=243
- 243 m o d 7 = 243 − 34 × 7 = 243 − 238 = 5 243 \bmod 7 = 243 - 34 \times 7 = 243 - 238 = 5 243mod7=243−34×7=243−238=5
- 所以逆元是 5 ✓
python
def mod_inverse_fermat(a, p):
# p 必须是质数
return pow(a, p - 2, p)
print(mod_inverse_fermat(3, 7)) # 输出 5
两种方法的对比
| 方法 | 适用条件 | 时间复杂度 | 备注 |
|---|---|---|---|
| 扩展欧几里得 | a 和 m 互质即可 | O ( log m ) O(\log m) O(logm) | 通用,常数小 |
| 费马小定理 | m 必须是质数 | O ( log m ) O(\log m) O(logm) | 写起来简单,模数为质数时常用 |
虽然时间复杂度都是 O ( log m ) O(\log m) O(logm),但扩展欧几里得的常数更小,实际运行更快。不过费马小定理写起来更简洁,在模数确定是质数的时候(比如很多密码学协议中)很常用。
逆元在密码学中的应用
模逆元是密码学中无处不在的基础运算:
- RSA 算法 :私钥 d 就是公钥 e 在模 φ ( n ) \varphi(n) φ(n) 下的逆元,即 d ≡ e − 1 ( m o d φ ( n ) ) d \equiv e^{-1} \pmod{\varphi(n)} d≡e−1(modφ(n))
- 仿射密码 :加密用 a x + b ax + b ax+b,解密时需要乘以 a 的逆元才能还原
- 椭圆曲线密码:点的运算中需要求有限域元素的逆元
- 数字签名:DSA、ECDSA 等签名算法的签名和验证过程都用到逆元
- 中国剩余定理 :构造解的时候需要求 M i M_i Mi 的逆元
🔑 一句话总结:模逆元让模运算有了"除法",而扩展欧几里得算法是求逆元的标准工具------这对组合是整个公钥密码学的数学底座。
3. 三大数论定理
3.1 费马小定理
费马小定理是数论中最著名的定理之一,也是密码学中最常用的工具之一。它的表述极其简洁,但威力巨大。
定理内容
如果 p p p 是一个质数,且整数 a a a 不是 p p p 的倍数,那么:
a p − 1 ≡ 1 ( m o d p ) a^{p-1} \equiv 1 \pmod{p} ap−1≡1(modp)
换句话说, a a a 的 p − 1 p-1 p−1 次方除以 p p p,余数一定是 1 1 1。
先看几个例子,建立直觉
例 1: p = 5 p = 5 p=5(质数), a = 2 a = 2 a=2
- 2 4 = 16 2^4 = 16 24=16
- 16 m o d 5 = 1 16 \bmod 5 = 1 16mod5=1 ✓
例 2: p = 7 p = 7 p=7(质数), a = 3 a = 3 a=3
- 3 6 = 729 3^6 = 729 36=729
- 729 m o d 7 = 1 729 \bmod 7 = 1 729mod7=1 ✓( 7 × 104 = 728 7 \times 104 = 728 7×104=728, 729 − 728 = 1 729 - 728 = 1 729−728=1)
例 3: p = 7 p = 7 p=7(质数), a = 7 a = 7 a=7
- 注意: a a a 是 p p p 的倍数,定理不适用
- 7 6 m o d 7 = 0 7^6 \bmod 7 = 0 76mod7=0,不是 1 1 1
一个直观的理解方式
我们用 p = 5 p = 5 p=5, a = 2 a = 2 a=2 来演示:
考虑 1 , 2 , 3 , 4 1, 2, 3, 4 1,2,3,4 这四个数( 1 1 1 到 p − 1 p-1 p−1),把它们都乘以 a = 2 a = 2 a=2,再 m o d 5 \bmod 5 mod5:
1 × 2 = 2 mod 5 = 2
2 × 2 = 4 mod 5 = 4
3 × 2 = 6 mod 5 = 1
4 × 2 = 8 mod 5 = 3
得到的结果是 { 2 , 4 , 1 , 3 } \{2, 4, 1, 3\} {2,4,1,3}------其实就是 { 1 , 2 , 3 , 4 } \{1, 2, 3, 4\} {1,2,3,4} 换了个顺序!
这不是巧合。因为 a a a 和 p p p 互质,乘以 a a a 再 m o d p \bmod p modp 是一个一一映射:原来不同的数,乘完之后还是不同的。
所以两边相乘,结果应该相等:
1 × 2 × 3 × 4 ≡ ( 2 × 1 ) × ( 2 × 2 ) × ( 2 × 3 ) × ( 2 × 4 ) ( m o d 5 ) 1 \times 2 \times 3 \times 4 \equiv (2\times1) \times (2\times2) \times (2\times3) \times (2\times4) \pmod{5} 1×2×3×4≡(2×1)×(2×2)×(2×3)×(2×4)(mod5)
左边 = 4 ! = 24 4! = 24 4!=24
右边 = 2 4 × ( 1 × 2 × 3 × 4 ) = 2 4 × 4 ! 2^4 \times (1\times2\times3\times4) = 2^4 \times 4! 24×(1×2×3×4)=24×4!
所以 4 ! ≡ 2 4 × 4 ! ( m o d 5 ) 4! \equiv 2^4 \times 4! \pmod{5} 4!≡24×4!(mod5)
两边都除以 4 ! 4! 4!(因为 4 ! 4! 4! 和 5 5 5 互质,可以除),得到:
1 ≡ 2 4 ( m o d 5 ) 1 \equiv 2^4 \pmod{5} 1≡24(mod5)
这就是费马小定理!
完整的证明思路
- 考虑集合 S = { 1 , 2 , 3 , . . . , p − 1 } S = \{1, 2, 3, ..., p-1\} S={1,2,3,...,p−1}
- 把每个元素乘以 a a a,得到集合 a S = { a , 2 a , 3 a , . . . , ( p − 1 ) a } aS = \{a, 2a, 3a, ..., (p-1)a\} aS={a,2a,3a,...,(p−1)a},全部 m o d p \bmod p modp
- 因为 a a a 和 p p p 互质, a S aS aS 中的元素两两不同(如果 i a ≡ j a ( m o d p ) ia \equiv ja \pmod{p} ia≡ja(modp),两边乘 a a a 的逆元得 i ≡ j i \equiv j i≡j)
- 所以 a S aS aS 其实就是 S S S 的一个排列,元素完全一样,只是顺序不同
- 因此两边乘积相等: ∏ S ≡ ∏ ( a S ) ( m o d p ) \prod S \equiv \prod(aS) \pmod{p} ∏S≡∏(aS)(modp)
- 左边 = ( p − 1 ) ! (p-1)! (p−1)!,右边 = a p − 1 × ( p − 1 ) ! a^{p-1} \times (p-1)! ap−1×(p−1)!
- 两边约掉 ( p − 1 ) ! (p-1)! (p−1)!(和 p p p 互质,可以约),得到 a p − 1 ≡ 1 ( m o d p ) a^{p-1} \equiv 1 \pmod{p} ap−1≡1(modp)
费马小定理的应用
应用 1:快速求模逆元
这是费马小定理在密码学中最直接的应用。
由 a p − 1 ≡ 1 ( m o d p ) a^{p-1} \equiv 1 \pmod{p} ap−1≡1(modp),两边乘 a − 1 a^{-1} a−1,得:
a p − 2 ≡ a − 1 ( m o d p ) a^{p-2} \equiv a^{-1} \pmod{p} ap−2≡a−1(modp)
所以 a a a 的逆元就是 a p − 2 m o d p a^{p-2} \bmod p ap−2modp,用快速幂一算就出来了。
python
def mod_inverse_fermat(a, p):
# p 必须是质数
return pow(a, p - 2, p)
# 例:求 3 在模 7 下的逆元
inv = mod_inverse_fermat(3, 7)
print(f"3 的逆元是 {inv}") # 输出 5
# 验证:3 × 5 = 15 ≡ 1 mod 7 ✓
应用 2:素性检测(费马测试)
费马小定理反过来用,可以用来检测一个数是不是质数。
思路:如果 n n n 是质数,那么对于任意 a a a(不是 n n n 的倍数),都应该有 a n − 1 ≡ 1 ( m o d n ) a^{n-1} \equiv 1 \pmod{n} an−1≡1(modn)。
反过来,如果存在某个 a a a 使得 a n − 1 ≢ 1 ( m o d n ) a^{n-1} \not\equiv 1 \pmod{n} an−1≡1(modn),那 n n n 一定不是质数。
python
def fermat_test(n, a):
# 检测 n 是否可能是质数,a 是测试底数
if n <= 1:
return False
if n == 2:
return True
if pow(a, n - 1, n) != 1:
return False # 一定不是质数
return True # 可能是质数
print(fermat_test(561, 2)) # 输出 True(但 561 其实是合数!)
⚠️ 注意:费马测试是"概率性"的。通过测试不代表一定是质数,只能说"很可能是"。存在一些合数(叫做卡迈克尔数,比如 561 561 561)能骗过所有与它互质的底数的费马测试。不过在密码学实际应用中,结合多个底数测试,出错概率可以降到几乎为零。
💡 费马小定理是欧拉定理的特例。下一节我们会看到,当模数不是质数时,欧拉定理给出了更一般的结论。
3.2 欧拉定理与欧拉函数
费马小定理只适用于质数模数,但密码学中经常需要处理合数模数(比如 RSA 中的 n = p q n = pq n=pq)。这时候就需要欧拉定理------费马小定理的推广版本。
欧拉函数 φ ( n ) \varphi(n) φ(n)
在讲欧拉定理之前,先认识一个重要的函数:欧拉函数 φ ( n ) \varphi(n) φ(n)(读作"phi")。
φ ( n ) \varphi(n) φ(n) 的定义很简单:
φ ( n ) \varphi(n) φ(n) 表示 1 1 1 到 n n n 之间,与 n n n 互质的正整数的个数。
举几个例子:
例 1: n = 6 n = 6 n=6
- 1 1 1 到 6 6 6 中,与 6 6 6 互质的数有: 1 , 5 1, 5 1,5
- φ ( 6 ) = 2 \varphi(6) = 2 φ(6)=2
例 2: n = 7 n = 7 n=7(质数)
- 1 1 1 到 7 7 7 中,除了 7 7 7 本身,都和 7 7 7 互质
- φ ( 7 ) = 6 \varphi(7) = 6 φ(7)=6
例 3: n = 1 n = 1 n=1
- 只有 1 1 1 这一个数, 1 1 1 和 1 1 1 互质( g c d ( 1 , 1 ) = 1 gcd(1,1)=1 gcd(1,1)=1)
- φ ( 1 ) = 1 \varphi(1) = 1 φ(1)=1
例 4: n = 12 n = 12 n=12
- 与 12 12 12 互质的数: 1 , 5 , 7 , 11 1, 5, 7, 11 1,5,7,11
- φ ( 12 ) = 4 \varphi(12) = 4 φ(12)=4
欧拉函数的性质
性质 1 :如果 p p p 是质数,那么 φ ( p ) = p − 1 \varphi(p) = p - 1 φ(p)=p−1
这个很直观------质数和所有比它小的正整数都互质。
性质 2 :如果 p p p 是质数, k k k 是正整数,那么 φ ( p k ) = p k − p k − 1 = p k − 1 ( p − 1 ) \varphi(p^k) = p^k - p^{k-1} = p^{k-1}(p-1) φ(pk)=pk−pk−1=pk−1(p−1)
推导: 1 1 1 到 p k p^k pk 中,不与 p k p^k pk 互质的数就是 p p p 的倍数,共有 p k − 1 p^{k-1} pk−1 个( p , 2 p , 3 p , . . . , p k − 1 × p p, 2p, 3p, ..., p^{k-1}\times p p,2p,3p,...,pk−1×p)。所以互质的数量就是总数减去不互质的: p k − p k − 1 p^k - p^{k-1} pk−pk−1。
例: φ ( 8 ) = φ ( 2 3 ) = 2 3 − 2 2 = 8 − 4 = 4 \varphi(8) = \varphi(2^3) = 2^3 - 2^2 = 8 - 4 = 4 φ(8)=φ(23)=23−22=8−4=4
验证: 1 , 3 , 5 , 7 1, 3, 5, 7 1,3,5,7 与 8 8 8 互质,确实是 4 4 4 个 ✓
性质 3 :欧拉函数是积性函数。如果 m m m 和 n n n 互质,那么 φ ( m n ) = φ ( m ) × φ ( n ) \varphi(mn) = \varphi(m) \times \varphi(n) φ(mn)=φ(m)×φ(n)
这是欧拉函数最重要的性质,也是计算 φ ( n ) \varphi(n) φ(n) 的关键。
有了这三个性质,我们就能计算任意 n n n 的欧拉函数了------只要先把 n n n 分解质因数,再分别算每个质因数幂的 φ \varphi φ,最后乘起来。
计算欧拉函数的完整例子
例:计算 φ ( 36 ) \varphi(36) φ(36)
第一步:分解质因数
- 36 = 4 × 9 = 2 2 × 3 2 36 = 4 \times 9 = 2^2 \times 3^2 36=4×9=22×32
第二步:分别计算
- φ ( 2 2 ) = 2 2 − 2 1 = 4 − 2 = 2 \varphi(2^2) = 2^2 - 2^1 = 4 - 2 = 2 φ(22)=22−21=4−2=2
- φ ( 3 2 ) = 3 2 − 3 1 = 9 − 3 = 6 \varphi(3^2) = 3^2 - 3^1 = 9 - 3 = 6 φ(32)=32−31=9−3=6
第三步:相乘(因为 2 2 2 和 3 3 3 互质)
- φ ( 36 ) = φ ( 2 2 ) × φ ( 3 2 ) = 2 × 6 = 12 \varphi(36) = \varphi(2^2) \times \varphi(3^2) = 2 \times 6 = 12 φ(36)=φ(22)×φ(32)=2×6=12
验证: 1 1 1 到 36 36 36 中与 36 36 36 互质的数有 1 , 5 , 7 , 11 , 13 , 17 , 19 , 23 , 25 , 29 , 31 , 35 1, 5, 7, 11, 13, 17, 19, 23, 25, 29, 31, 35 1,5,7,11,13,17,19,23,25,29,31,35,共 12 12 12 个 ✓
Python 实现欧拉函数
python
def euler_phi(n):
result = n
p = 2
while p * p <= n:
if n % p == 0:
while n % p == 0:
n = n // p
result -= result // p
p += 1
if n > 1:
result -= result // n
return result
print(euler_phi(36)) # 输出 12
print(euler_phi(7)) # 输出 6
print(euler_phi(1)) # 输出 1
这个算法的思路是:遍历每个质因数 p p p,每找到一个,就把结果乘以 ( p − 1 ) / p (p-1)/p (p−1)/p,也就是 result -= result // p。
欧拉定理
有了欧拉函数,欧拉定理就很好表述了:
如果 a a a 和 n n n 互质,那么:
a φ ( n ) ≡ 1 ( m o d n ) a^{\varphi(n)} \equiv 1 \pmod{n} aφ(n)≡1(modn)
你看,和费马小定理长得很像吧?把费马小定理中的 p − 1 p-1 p−1 换成 φ ( n ) \varphi(n) φ(n) 就是欧拉定理了。
当 n n n 是质数时, φ ( n ) = n − 1 \varphi(n) = n-1 φ(n)=n−1,欧拉定理就退化成了费马小定理。所以说,费马小定理是欧拉定理的特例。
例子验证
例 1: n = 6 n = 6 n=6, φ ( 6 ) = 2 \varphi(6) = 2 φ(6)=2,取 a = 5 a = 5 a=5(与 6 6 6 互质)
- 5 2 = 25 5^2 = 25 52=25
- 25 m o d 6 = 1 25 \bmod 6 = 1 25mod6=1 ✓
例 2: n = 12 n = 12 n=12, φ ( 12 ) = 4 \varphi(12) = 4 φ(12)=4,取 a = 5 a = 5 a=5(与 12 12 12 互质)
- 5 4 = 625 5^4 = 625 54=625
- 625 m o d 12 = 1 625 \bmod 12 = 1 625mod12=1 ✓( 12 × 52 = 624 12 \times 52 = 624 12×52=624, 625 − 624 = 1 625 - 624 = 1 625−624=1)
例 3: n = 15 n = 15 n=15, φ ( 15 ) = φ ( 3 ) × φ ( 5 ) = 2 × 4 = 8 \varphi(15) = \varphi(3) \times \varphi(5) = 2 \times 4 = 8 φ(15)=φ(3)×φ(5)=2×4=8,取 a = 2 a = 2 a=2
- 2 8 = 256 2^8 = 256 28=256
- 256 m o d 15 = 1 256 \bmod 15 = 1 256mod15=1 ✓( 15 × 17 = 255 15 \times 17 = 255 15×17=255, 256 − 255 = 1 256 - 255 = 1 256−255=1)
欧拉定理的直观理解
和费马小定理的证明思路几乎一样:
- 考虑所有与 n n n 互质且小于 n n n 的正整数,共有 φ ( n ) \varphi(n) φ(n) 个,记为集合 S S S
- 把 S S S 中每个元素乘以 a a a( a a a 与 n n n 互质),再 m o d n \bmod n modn,得到集合 a S aS aS
- 因为 a a a 和 n n n 互质, a S aS aS 中的元素仍然都与 n n n 互质,且两两不同
- 所以 a S aS aS 就是 S S S 的一个排列
- 两边乘积相等,约掉公共部分,就得到 a φ ( n ) ≡ 1 ( m o d n ) a^{\varphi(n)} \equiv 1 \pmod{n} aφ(n)≡1(modn)
欧拉定理在密码学中的应用
应用 1:求合数模下的逆元
和费马小定理类似,由欧拉定理可以推出:如果 a a a 和 n n n 互质,那么 a a a 的逆元是 a φ ( n ) − 1 m o d n a^{\varphi(n)-1} \bmod n aφ(n)−1modn。
不过实际中求逆元还是用扩展欧几里得更多,因为不需要算 φ ( n ) \varphi(n) φ(n)(算 φ ( n ) \varphi(n) φ(n) 需要分解质因数,而分解大数恰恰是密码学的"难题")。
应用 2:RSA 算法的理论基础
RSA 加密和解密为什么能正确还原?核心就是欧拉定理。
RSA 中, n = p q n = pq n=pq( p p p 和 q q q 是大质数), φ ( n ) = ( p − 1 ) ( q − 1 ) \varphi(n) = (p-1)(q-1) φ(n)=(p−1)(q−1)。
公钥 e e e 和私钥 d d d 满足 e d ≡ 1 ( m o d φ ( n ) ) ed \equiv 1 \pmod{\varphi(n)} ed≡1(modφ(n)),即 e d = k φ ( n ) + 1 ed = k\varphi(n) + 1 ed=kφ(n)+1。
加密: c = m e m o d n c = m^e \bmod n c=memodn
解密: c d = m e d = m k φ ( n ) + 1 = ( m φ ( n ) ) k × m ≡ 1 k × m = m ( m o d n ) c^d = m^{ed} = m^{k\varphi(n)+1} = (m^{\varphi(n)})^k \times m \equiv 1^k \times m = m \pmod{n} cd=med=mkφ(n)+1=(mφ(n))k×m≡1k×m=m(modn)
看,解密后正好还原出明文 m m m!这就是 RSA 正确性的数学证明,核心就是欧拉定理。
💡 欧拉定理是费马小定理的推广,也是 RSA 等公钥密码算法的理论基石。理解了欧拉定理,你就理解了公钥密码学为什么能工作。
3.3 中国剩余定理(CRT)
中国剩余定理(Chinese Remainder Theorem,简称 CRT),顾名思义,是中国古代数学家的伟大发现。最早出现在《孙子算经》中,所以也叫"孙子定理"。
问题起源:韩信点兵
《孙子算经》里有这么一道题:
今有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二,问物几何?
翻译一下:有一堆东西,不知道多少个。三个三个数,剩 2 2 2 个;五个五个数,剩 3 3 3 个;七个七个数,剩 2 2 2 个。问这堆东西有多少个?
用数学语言说,就是解这个方程组:
{ x ≡ 2 ( m o d 3 ) x ≡ 3 ( m o d 5 ) x ≡ 2 ( m o d 7 ) \begin{cases} x \equiv 2 \pmod{3} \\ x \equiv 3 \pmod{5} \\ x \equiv 2 \pmod{7} \end{cases} ⎩ ⎨ ⎧x≡2(mod3)x≡3(mod5)x≡2(mod7)
中国剩余定理就是用来解这种"一元同余方程组"的。
定理内容
设 m 1 , m 2 , . . . , m k m_1, m_2, ..., m_k m1,m2,...,mk 是两两互质的正整数,那么对于任意整数 a 1 , a 2 , . . . , a k a_1, a_2, ..., a_k a1,a2,...,ak,同余方程组:
{ x ≡ a 1 ( m o d m 1 ) x ≡ a 2 ( m o d m 2 ) ⋮ x ≡ a k ( m o d m k ) \begin{cases} x \equiv a_1 \pmod{m_1} \\ x \equiv a_2 \pmod{m_2} \\ \quad \vdots \\ x \equiv a_k \pmod{m_k} \end{cases} ⎩ ⎨ ⎧x≡a1(modm1)x≡a2(modm2)⋮x≡ak(modmk)
在模 M = m 1 m 2 . . . m k M = m_1m_2...m_k M=m1m2...mk 下有唯一解。
注意两个关键点:
- 模数必须两两互质(任意两个模数的 g c d gcd gcd 都是 1 1 1)
- 解在模 M M M 下是唯一的------也就是说,最小正整数解只有一个,其他解都是它加上 M M M 的倍数
怎么求解?构造法
中国剩余定理不仅告诉我们"解存在且唯一",还给出了构造解的方法。
设 M = m 1 m 2 . . . m k M = m_1m_2...m_k M=m1m2...mk,对每个 i i i,设 M i = M / m i M_i = M / m_i Mi=M/mi(也就是除了 m i m_i mi 之外所有模数的乘积)。
因为所有模数两两互质,所以 M i M_i Mi 和 m i m_i mi 互质,因此 M i M_i Mi 在模 m i m_i mi 下有逆元,记为 t i t_i ti,即 M i × t i ≡ 1 ( m o d m i ) M_i \times t_i \equiv 1 \pmod{m_i} Mi×ti≡1(modmi)
那么方程组的解就是:
x ≡ a 1 M 1 t 1 + a 2 M 2 t 2 + . . . + a k M k t k ( m o d M ) x \equiv a_1M_1t_1 + a_2M_2t_2 + ... + a_kM_kt_k \pmod{M} x≡a1M1t1+a2M2t2+...+akMktk(modM)
为什么这个构造是对的?我们来验证一下:
看第 i i i 个方程 x ≡ a i ( m o d m i ) x \equiv a_i \pmod{m_i} x≡ai(modmi):
- 对于第 i i i 项 a i M i t i a_iM_it_i aiMiti: M i M_i Mi 包含了除 m i m_i mi 外的所有模数,所以 M i m o d m i M_i \bmod m_i Mimodmi 就是 M i M_i Mi 本身(因为不含 m i m_i mi 这个因子),而 t i t_i ti 是 M i M_i Mi 的逆元,所以 M i t i ≡ 1 ( m o d m i ) M_it_i \equiv 1 \pmod{m_i} Miti≡1(modmi),因此 a i M i t i ≡ a i ( m o d m i ) a_iM_it_i \equiv a_i \pmod{m_i} aiMiti≡ai(modmi)
- 对于其他项 a j M j t j a_jM_jt_j ajMjtj( j ≠ i j \neq i j=i): M j M_j Mj 包含了 m i m_i mi 这个因子(因为 M j M_j Mj 是除了 m j m_j mj 外所有模数的乘积,而 j ≠ i j \neq i j=i),所以 M j ≡ 0 ( m o d m i ) M_j \equiv 0 \pmod{m_i} Mj≡0(modmi),因此整个这一项 ≡ 0 ( m o d m i ) \equiv 0 \pmod{m_i} ≡0(modmi)
所以把所有项加起来, x ≡ a i + 0 + 0 + . . . + 0 ≡ a i ( m o d m i ) x \equiv a_i + 0 + 0 + ... + 0 \equiv a_i \pmod{m_i} x≡ai+0+0+...+0≡ai(modmi),正好满足第 i i i 个方程!
手算完整例子:韩信点兵
我们来解《孙子算经》中的那道题:
{ x ≡ 2 ( m o d 3 ) x ≡ 3 ( m o d 5 ) x ≡ 2 ( m o d 7 ) \begin{cases} x \equiv 2 \pmod{3} \\ x \equiv 3 \pmod{5} \\ x \equiv 2 \pmod{7} \end{cases} ⎩ ⎨ ⎧x≡2(mod3)x≡3(mod5)x≡2(mod7)
第一步:计算 M M M
- M = 3 × 5 × 7 = 105 M = 3 \times 5 \times 7 = 105 M=3×5×7=105
第二步:计算每个 M i M_i Mi
- M 1 = 105 / 3 = 35 M_1 = 105 / 3 = 35 M1=105/3=35
- M 2 = 105 / 5 = 21 M_2 = 105 / 5 = 21 M2=105/5=21
- M 3 = 105 / 7 = 15 M_3 = 105 / 7 = 15 M3=105/7=15
第三步:求每个 M i M_i Mi 在模 m i m_i mi 下的逆元 t i t_i ti
- 求 35 35 35 在模 3 3 3 下的逆元: 35 m o d 3 = 2 35 \bmod 3 = 2 35mod3=2,找 t 1 t_1 t1 使得 2 t 1 ≡ 1 ( m o d 3 ) 2t_1 \equiv 1 \pmod{3} 2t1≡1(mod3)
- 试一下: 2 × 2 = 4 ≡ 1 ( m o d 3 ) 2 \times 2 = 4 \equiv 1 \pmod{3} 2×2=4≡1(mod3),所以 t 1 = 2 t_1 = 2 t1=2
- 求 21 21 21 在模 5 5 5 下的逆元: 21 m o d 5 = 1 21 \bmod 5 = 1 21mod5=1, 1 1 1 的逆元就是 1 1 1,所以 t 2 = 1 t_2 = 1 t2=1
- 求 15 15 15 在模 7 7 7 下的逆元: 15 m o d 7 = 1 15 \bmod 7 = 1 15mod7=1,所以 t 3 = 1 t_3 = 1 t3=1
第四步:代入公式
- x ≡ a 1 M 1 t 1 + a 2 M 2 t 2 + a 3 M 3 t 3 ( m o d 105 ) x \equiv a_1M_1t_1 + a_2M_2t_2 + a_3M_3t_3 \pmod{105} x≡a1M1t1+a2M2t2+a3M3t3(mod105)
- x ≡ 2 × 35 × 2 + 3 × 21 × 1 + 2 × 15 × 1 x \equiv 2\times35\times2 + 3\times21\times1 + 2\times15\times1 x≡2×35×2+3×21×1+2×15×1
- x ≡ 140 + 63 + 30 x \equiv 140 + 63 + 30 x≡140+63+30
- x ≡ 233 ( m o d 105 ) x \equiv 233 \pmod{105} x≡233(mod105)
- 233 m o d 105 = 233 − 2 × 105 = 233 − 210 = 23 233 \bmod 105 = 233 - 2\times105 = 233 - 210 = 23 233mod105=233−2×105=233−210=23
所以最小正整数解是 23 23 23。
验证一下:
- 23 ÷ 3 = 7 23 \div 3 = 7 23÷3=7 余 2 2 2 ✓
- 23 ÷ 5 = 4 23 \div 5 = 4 23÷5=4 余 3 3 3 ✓
- 23 ÷ 7 = 3 23 \div 7 = 3 23÷7=3 余 2 2 2 ✓
完美!古代人叫这个"韩信点兵,多多益善",还编了首口诀:"三人同行七十稀,五树梅花廿一枝,七子团圆正半月,除百零五便得知。" 其实就是 70 = 35 × 2 70 = 35\times2 70=35×2, 21 = 21 × 1 21 = 21\times1 21=21×1, 15 = 15 × 1 15 = 15\times1 15=15×1, 105 105 105 是 M M M。
再练一个例子
解方程组:
{ x ≡ 1 ( m o d 4 ) x ≡ 2 ( m o d 5 ) x ≡ 3 ( m o d 7 ) \begin{cases} x \equiv 1 \pmod{4} \\ x \equiv 2 \pmod{5} \\ x \equiv 3 \pmod{7} \end{cases} ⎩ ⎨ ⎧x≡1(mod4)x≡2(mod5)x≡3(mod7)
第一步: M = 4 × 5 × 7 = 140 M = 4 \times 5 \times 7 = 140 M=4×5×7=140
第二步: M 1 = 35 , M 2 = 28 , M 3 = 20 M_1 = 35,\ M_2 = 28,\ M_3 = 20 M1=35, M2=28, M3=20
第三步:求逆元
- 35 m o d 4 = 3 35 \bmod 4 = 3 35mod4=3,找 3 3 3 的逆元 m o d 4 \bmod 4 mod4: 3 × 3 = 9 ≡ 1 ( m o d 4 ) 3\times3 = 9 \equiv 1 \pmod{4} 3×3=9≡1(mod4), t 1 = 3 t_1 = 3 t1=3
- 28 m o d 5 = 3 28 \bmod 5 = 3 28mod5=3,找 3 3 3 的逆元 m o d 5 \bmod 5 mod5: 3 × 2 = 6 ≡ 1 ( m o d 5 ) 3\times2 = 6 \equiv 1 \pmod{5} 3×2=6≡1(mod5), t 2 = 2 t_2 = 2 t2=2
- 20 m o d 7 = 6 20 \bmod 7 = 6 20mod7=6,找 6 6 6 的逆元 m o d 7 \bmod 7 mod7: 6 × 6 = 36 ≡ 1 ( m o d 7 ) 6\times6 = 36 \equiv 1 \pmod{7} 6×6=36≡1(mod7), t 3 = 6 t_3 = 6 t3=6
第四步: x ≡ 1 × 35 × 3 + 2 × 28 × 2 + 3 × 20 × 6 = 105 + 112 + 360 = 577 x \equiv 1\times35\times3 + 2\times28\times2 + 3\times20\times6 = 105 + 112 + 360 = 577 x≡1×35×3+2×28×2+3×20×6=105+112+360=577
- 577 m o d 140 = 577 − 4 × 140 = 577 − 560 = 17 577 \bmod 140 = 577 - 4\times140 = 577 - 560 = 17 577mod140=577−4×140=577−560=17
验证:
- 17 m o d 4 = 1 17 \bmod 4 = 1 17mod4=1 ✓
- 17 m o d 5 = 2 17 \bmod 5 = 2 17mod5=2 ✓
- 17 m o d 7 = 3 17 \bmod 7 = 3 17mod7=3 ✓
Python 实现
python
def crt(remainders, moduli):
"""
中国剩余定理求解
remainders: 余数列表 [a1, a2, ..., ak]
moduli: 模数列表 [m1, m2, ..., mk],要求两两互质
返回最小正整数解
"""
M = 1
for m in moduli:
M *= m
x = 0
for a, m in zip(remainders, moduli):
Mi = M // m
# 求 Mi 在模 m 下的逆元
ti = pow(Mi, -1, m) # Python 3.8+ 支持 pow 直接求逆元
x += a * Mi * ti
return x % M
# 测试:韩信点兵
rem = [2, 3, 2]
mod = [3, 5, 7]
result = crt(rem, mod)
print(f"解为 x ≡ {result} (mod 105)") # 输出 23
注:Python 3.8 及以上版本,
pow(a, -1, m)可以直接求 a a a 在模 m m m 下的逆元(内部用的是扩展欧几里得)。如果逆元不存在会抛异常。
中国剩余定理在密码学中的应用
应用 1:RSA 解密加速
RSA 解密需要算 c d m o d n c^d \bmod n cdmodn,其中 n = p q n = pq n=pq。直接算的话,模数是 n n n,数字很大。
用中国剩余定理可以拆开算:
- 先算 m 1 = c d m o d p m_1 = c^d \bmod p m1=cdmodp
- 再算 m 2 = c d m o d q m_2 = c^d \bmod q m2=cdmodq
- 最后用 CRT 合并出 m m o d n m \bmod n mmodn
因为 p p p 和 q q q 比 n n n 小很多(大约是 n n n 的平方根位数),所以模幂运算快很多。实际中用 CRT 可以让 RSA 解密快大约 4 4 4 倍。
应用 2:秘密共享
可以把一个秘密数 s s s 拆分成多个"份额",每个份额是 s m o d m i s \bmod m_i smodmi( m i m_i mi 是不同的模数)。只要拿到足够多的份额(使得模数乘积大于 s s s),就能用 CRT 还原出 s s s。这就是一种简单的秘密共享方案。
应用 3:加速模运算
很多密码学运算都可以用 CRT 拆成小模数的运算,再合并结果,从而大幅提升效率。
💡 中国剩余定理告诉我们:在两两互质的模数下,一个数和它在各个模数下的余数是"一一对应"的。这意味着我们可以把"大模数的运算"拆成"多个小模数的运算",算完再拼回去------这是密码学中常用的加速技巧。
4. 离散对数问题
4.1 什么是离散对数
从普通对数说起
你肯定学过普通对数:如果 a x = b a^x = b ax=b,那么 x = log a b x = \log_a b x=logab。
比如 2 3 = 8 2^3 = 8 23=8,所以 log 2 8 = 3 \log_2 8 = 3 log28=3。
在实数范围内,求对数是件很容易的事------计算器按一下就出来了。但如果把运算放在模运算的世界里,事情就完全不一样了。
离散对数的定义
给定一个质数 p p p,一个整数 g g g(叫做"生成元"或"底数"),以及另一个整数 h h h,离散对数问题就是:
找到整数 x x x,使得 g x ≡ h ( m o d p ) g^x \equiv h \pmod{p} gx≡h(modp)
这个 x x x 就叫做 h h h 在模 p p p 下以 g g g 为底的离散对数 ,记作 x = log g h ( m o d p ) x = \log_g h \pmod{p} x=loggh(modp)。
听起来和普通对数差不多?但加上了"模 p p p"之后,难度天差地别。
举个小例子建立直觉
取 p = 7 p = 7 p=7(质数), g = 3 g = 3 g=3。
我们来算一下 3 的各次幂模 7 的结果:
| x x x | 3 x 3^x 3x | 3 x m o d 7 3^x \bmod 7 3xmod7 |
|---|---|---|
| 0 | 1 | 1 |
| 1 | 3 | 3 |
| 2 | 9 | 2 |
| 3 | 27 | 6 |
| 4 | 81 | 4 |
| 5 | 243 | 5 |
| 6 | 729 | 1 |
看到了吗?从 x = 0 x=0 x=0 到 x = 5 x=5 x=5,结果遍历了 1 到 6 所有非零余数,到 x = 6 x=6 x=6 又回到了 1,开始循环。
现在问你: 3 x ≡ 5 ( m o d 7 ) 3^x \equiv 5 \pmod{7} 3x≡5(mod7),求 x x x 是多少?
查表就知道, x = 5 x=5 x=5。这就是一个离散对数问题。
数字小的时候,你可以一个个试出来。但如果 p p p 是一个 2048 位的大质数呢?
正向 vs 反向
离散对数问题之所以重要,是因为它的两个方向难度完全不对等:
- 正向计算(模幂) :给你 g g g 和 x x x,算 g x m o d p g^x \bmod p gxmodp → 用快速幂, O ( log x ) O(\log x) O(logx) 时间,毫秒级完成
- 反向求解(离散对数) :给你 g g g 和 h = g x m o d p h = g^x \bmod p h=gxmodp,求 x x x → 目前没有高效算法,对足够大的 p p p 来说, practically impossible
这种"正向容易、反向困难"的函数,在密码学中叫做单向函数(One-way Function),而离散对数问题就是最重要的单向函数之一。
💡 一句话理解:离散对数就是"模运算世界里的对数",但和实数对数不同,离散对数非常难算------这正是密码学需要的。
4.2 离散对数的"难解性"
离散对数到底有多难?为什么说它是"难解"的?我们来具体看看。
暴力求解有多慢?
最朴素的方法就是"试":从 x = 0 x=0 x=0 开始,一个个算 g x m o d p g^x \bmod p gxmodp,直到等于 h h h 为止。
最坏情况下,你要试 p − 1 p-1 p−1 次才能找到答案。时间复杂度是 O ( p ) O(p) O(p)。
如果 p p p 是一个 2048 位的质数, p p p 大约是 2 2048 2^{2048} 22048,也就是大约 10 616 10^{616} 10616。
10 616 10^{616} 10616 次运算是什么概念?
- 假设你的电脑每秒能算 10 12 10^{12} 1012 次(一万亿次)
- 一年大约 3 × 10 7 3 \times 10^7 3×107 秒
- 一年能算 3 × 10 19 3 \times 10^{19} 3×1019 次
- 算完需要大约 3 × 10 596 3 \times 10^{596} 3×10596 年
而宇宙的年龄才 10 10 10^{10} 1010 年左右......暴力破解等于不可能。
有没有更快的算法?
当然,暴力法是最慢的。实际中有一些更快的算法,比如:
- Baby-step Giant-step(小步大步法) :时间复杂度 O ( p ) O(\sqrt{p}) O(p )
- Pohlig-Hellman 算法 :当 p − 1 p-1 p−1 的质因数都很小时很快,但对精心选择的 p p p 无效
- Index Calculus(指标演算法):目前对一般情况最快的算法,亚指数时间,但仍然远慢于多项式时间
听起来 O ( p ) O(\sqrt{p}) O(p ) 比 O ( p ) O(p) O(p) 快多了?我们来算一下:
还是 2048 位的 p p p, p ≈ 2 1024 ≈ 10 308 \sqrt{p} \approx 2^{1024} \approx 10^{308} p ≈21024≈10308。
10 308 10^{308} 10308 次运算,还是要算到天荒地老。
⚠️ 注意:这里说的"难解"是指对经典计算机而言。如果有了足够大的量子计算机,Shor 算法可以在多项式时间内求解离散对数问题------这也是为什么密码学界在研究后量子密码。
为什么模幂这么快,离散对数这么慢?
因为模幂运算有"分治"的结构:你可以把指数折半,底数平方,一步步缩小问题规模(就是快速幂的思路)。
但离散对数没有这种"分治"结构------你没法把"找 x x x 使得 g x = h g^x = h gx=h"这个问题拆成两个更小的同类问题。至少目前人类还没找到这样的方法。
这就像:
- 把一块玻璃打碎很容易(正向)
- 把碎玻璃拼回原样很难(反向)
离散对数问题就是数学世界里的"打碎玻璃"。
生成元的条件
前面的例子中, g = 3 g=3 g=3 在模 7 下能遍历 1 到 6 所有非零余数。这样的 g g g 叫做本原根 (Primitive Root)或生成元。
不是所有数都能当生成元。比如 p = 7 p=7 p=7,取 g = 2 g=2 g=2:
- 2 1 ≡ 2 , 2 2 ≡ 4 , 2 3 ≡ 1 , 2 4 ≡ 2... 2^1 \equiv 2,\ 2^2 \equiv 4,\ 2^3 \equiv 1,\ 2^4 \equiv 2... 21≡2, 22≡4, 23≡1, 24≡2...
- 只能遍历 {1, 2, 4},只有 3 个元素,循环周期是 3
生成元的循环周期是 p − 1 p-1 p−1,也就是最长的可能周期。密码学中通常用生成元,这样离散对数的搜索空间才最大。
💡 离散对数的"难解性"是现代公钥密码学的安全性基石之一。Diffie-Hellman、ElGamal、DSA、ECC......这些算法的安全性全部建立在"离散对数很难算"这个假设上。
4.3 离散对数在密码学中的应用
离散对数问题不是纯粹的数学玩具------它是公钥密码学的两大基石之一(另一个是大整数分解)。现代互联网的很多安全机制,背后都有离散对数的身影。
这里先简单列几个核心应用,后面讲具体密码算法时再展开。
应用 1:Diffie-Hellman 密钥交换
公钥密码学的开山之作。两个人在完全不安全的信道上,仅凭互相交换几个公开数字,就能协商出只有他俩知道的共享密钥。
核心逻辑:双方各自保密一个私钥,把"底数的私钥次方"发给对方,然后用对方的公钥再做一次幂运算,两边得到相同的结果。攻击者截获了中间传输的所有数据,却因为算不出离散对数而还原不了密钥。
应用 2:ElGamal 加密算法
基于离散对数的公钥加密方案。用公钥加密,用私钥解密,安全性建立在"知道公钥却求不出私钥"的离散对数难题上。
应用 3:数字签名算法(DSA)
美国国家标准的数字签名算法,用于身份认证和防抵赖。签名者用私钥签名,验证者用公钥验证,同样基于离散对数问题。
应用 4:椭圆曲线密码学(ECC)
本质上也是离散对数问题,只不过运算场景从"模 p 乘法群"换成了"椭圆曲线点群"。
ECC 的优势是效率更高:相同安全级别下,ECC 的密钥比 RSA 短得多。比如 256 位 ECC 的安全性,大约相当于 3072 位 RSA。现在的 HTTPS、区块链、移动设备加密,很多都用 ECC。
核心思想
所有这些应用,本质上都在利用同一件事:
正向运算(模幂 / 点乘)很快,反向运算(求离散对数)很难。
把秘密藏在"指数"里,公开"幂"的结果------攻击者拿到公开值,却没法反推出指数,因为离散对数太难算了。
这就是基于离散对数的密码学的核心逻辑。具体算法的细节,我们后面讲到对应章节再深入。
💡 一句话总结:离散对数撑起了半壁公钥密码学,从密钥交换到数字签名,从 HTTPS 到区块链,背后都是这个数学难题在守护安全。
5. 抽象代数基础
5.1 群、环、域的概念
为什么要学抽象代数?
你可能会问:我学密码学,为什么要搞懂群环域这些抽象的东西?
答案很简单:密码学的运算都是在某个代数结构里进行的。模运算有模运算的规则,椭圆曲线有椭圆曲线的规则------这些规则不是随便定的,它们都满足某种代数结构的性质。
理解了群、环、域,你就能从更高的视角看清楚:为什么这些运算能构成加密体系?为什么逆元一定存在?为什么解密一定能还原?
用程序员的话说:群、环、域就像是接口------它们定义了一套运算规则和性质。只要某个集合满足这些规则,你就能用这套接口上的所有结论和算法。
群(Group)
群是最基础的代数结构。一个群 ( G , ⋅ ) (G, \cdot) (G,⋅) 由一个集合 G G G 和一个二元运算 ⋅ \cdot ⋅ 组成,满足以下四条公理:
| 公理 | 含义 |
|---|---|
| 封闭性 | 对任意 a , b ∈ G a, b \in G a,b∈G,都有 a ⋅ b ∈ G a \cdot b \in G a⋅b∈G |
| 结合律 | 对任意 a , b , c ∈ G a, b, c \in G a,b,c∈G,都有 ( a ⋅ b ) ⋅ c = a ⋅ ( b ⋅ c ) (a \cdot b) \cdot c = a \cdot (b \cdot c) (a⋅b)⋅c=a⋅(b⋅c) |
| 单位元 | 存在 e ∈ G e \in G e∈G,使得对任意 a ∈ G a \in G a∈G,都有 e ⋅ a = a ⋅ e = a e \cdot a = a \cdot e = a e⋅a=a⋅e=a |
| 逆元 | 对任意 a ∈ G a \in G a∈G,存在 a − 1 ∈ G a^{-1} \in G a−1∈G,使得 a ⋅ a − 1 = a − 1 ⋅ a = e a \cdot a^{-1} = a^{-1} \cdot a = e a⋅a−1=a−1⋅a=e |
如果还满足交换律 (对任意 a , b ∈ G a, b \in G a,b∈G,都有 a ⋅ b = b ⋅ a a \cdot b = b \cdot a a⋅b=b⋅a),那就叫阿贝尔群(Abelian Group)或交换群。
举几个群的例子
例 1:整数加法群 ( Z , + ) (\mathbb{Z}, +) (Z,+)
- 封闭性:整数加整数还是整数 ✓
- 结合律:加法结合律成立 ✓
- 单位元:0,因为 a + 0 = a a + 0 = a a+0=a ✓
- 逆元: − a -a −a,因为 a + ( − a ) = 0 a + (-a) = 0 a+(−a)=0 ✓
- 交换律:加法交换律成立 ✓
- 这是一个阿贝尔群
例 2:模 7 乘法群 ( Z 7 ∗ , × ) (\mathbb{Z}_7^*, \times) (Z7∗,×)
- 集合是 {1, 2, 3, 4, 5, 6}(所有和 7 互质的数)
- 封闭性:两个数相乘 mod 7,结果还是 1~6 之间 ✓
- 结合律:乘法结合律成立 ✓
- 单位元:1 ✓
- 逆元:每个数都有逆元(比如 3 的逆元是 5) ✓
- 交换律:乘法交换律成立 ✓
- 这也是一个阿贝尔群
例 3:2×2 可逆矩阵乘法群
- 封闭性:可逆矩阵相乘还是可逆矩阵 ✓
- 结合律:矩阵乘法结合律成立 ✓
- 单位元:单位矩阵 ✓
- 逆元:逆矩阵 ✓
- 交换律:不成立(矩阵乘法一般不可交换)
- 这是一个群,但不是阿贝尔群
环(Ring)
环比群多了一个运算。一个环 ( R , + , × ) (R, +, \times) (R,+,×) 有两个二元运算:加法和乘法,满足:
- ( R , + ) (R, +) (R,+) 是一个阿贝尔群(加法交换群)
- 乘法满足封闭性和结合律
- 分配律 :对任意 a , b , c ∈ R a, b, c \in R a,b,c∈R,都有
- a × ( b + c ) = a × b + a × c a \times (b + c) = a \times b + a \times c a×(b+c)=a×b+a×c(左分配)
- ( a + b ) × c = a × c + b × c (a + b) \times c = a \times c + b \times c (a+b)×c=a×c+b×c(右分配)
简单说:环就是"加法是交换群,乘法是半群(只有封闭性和结合律),加法和乘法之间满足分配律"。
环的乘法不要求有单位元,也不要求有逆元,甚至不要求交换。
环的例子
例 1:整数环 ( Z , + , × ) (\mathbb{Z}, +, \times) (Z,+,×)
- 加法是交换群 ✓
- 乘法封闭、结合律成立 ✓
- 分配律成立 ✓
- 这是一个有单位元(1)的交换环
例 2:n×n 矩阵环
- 加法是交换群 ✓
- 乘法封闭、结合律成立 ✓
- 分配律成立 ✓
- 有单位元(单位矩阵),但乘法不交换
- 这是一个有单位元的非交换环
域(Field)
域是要求最严格的代数结构。一个域 ( F , + , × ) (F, +, \times) (F,+,×) 有两个二元运算,满足:
- ( F , + ) (F, +) (F,+) 是一个阿贝尔群(加法交换群)
- ( F ∖ { 0 } , × ) (F \setminus \{0\}, \times) (F∖{0},×) 是一个阿贝尔群(非零元乘法交换群)
- 乘法对加法满足分配律
简单说:域里加减乘除都能做(除以零除外),而且运算都满足交换律、结合律、分配律------和你熟悉的有理数、实数运算规则一模一样。
域的例子
例 1:有理数域 Q \mathbb{Q} Q
例 2:实数域 R \mathbb{R} R
例 3:复数域 C \mathbb{C} C
这些都是无限域,元素有无穷多个。
但密码学里用的大多是有限域(Finite Field),也叫伽罗瓦域(Galois Field,简称 GF)------元素个数有限的域。
三者的关系
从群到环到域,要求越来越严格:
域 ⊂ 环 ⊂ 群 \text{域} \subset \text{环} \subset \text{群} 域⊂环⊂群
- 群:只有一个运算,要求最少
- 环:有两个运算,乘法要求不高
- 域:有两个运算,加减乘除都齐全,要求最高
💡 一句话理解:群是"有一个运算且性质良好"的集合,环是"有两个运算但乘法要求不高"的集合,域是"加减乘除都能做"的集合。密码学的核心运算大多是在有限域上进行的。
5.2 有限域 GF§
什么是有限域?
有限域,顾名思义,就是元素个数有限的域。
有限域有个很重要的性质:有限域的元素个数一定是某个质数的幂 ,也就是 p n p^n pn,其中 p p p 是质数, n n n 是正整数。
当 n = 1 n=1 n=1 时,就是 G F ( p ) GF(p) GF(p),叫做素域(Prime Field)------最简单的有限域。
GF§ 的定义
G F ( p ) GF(p) GF(p)(读作"伽罗瓦域 p")的元素是 0 到 p-1 的整数:
G F ( p ) = { 0 , 1 , 2 , . . . , p − 1 } GF(p) = \{0, 1, 2, ..., p-1\} GF(p)={0,1,2,...,p−1}
上面的运算就是模 p 的加法和乘法:
- 加法: a + b ( m o d p ) a + b \pmod{p} a+b(modp)
- 乘法: a × b ( m o d p ) a \times b \pmod{p} a×b(modp)
为什么 p 必须是质数?
这是个关键问题。因为域要求每个非零元都有乘法逆元。
如果 p 是质数,那么 1 到 p-1 的每个数都和 p 互质,根据之前讲的,每个数都有模 p 下的逆元------满足域的要求。
如果 p 不是质数呢?比如 p = 4:
- 元素是 {0, 1, 2, 3}
- 2 有没有逆元?找 x 使得 2x ≡ 1 mod 4
- 2×1=2, 2×2=0, 2×3=2... 永远得不到 1
- 2 没有逆元,不满足域的要求
所以 p 必须是质数,才能保证每个非零元都有逆元,才能构成域。
具体例子:GF(5)
我们来完整看一下 GF(5) 的结构。
元素:{0, 1, 2, 3, 4}
加法表(模 5 加法):
| + | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| 0 | 0 | 1 | 2 | 3 | 4 |
| 1 | 1 | 2 | 3 | 4 | 0 |
| 2 | 2 | 3 | 4 | 0 | 1 |
| 3 | 3 | 4 | 0 | 1 | 2 |
| 4 | 4 | 0 | 1 | 2 | 3 |
乘法表(模 5 乘法):
| × | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 1 | 2 | 3 | 4 |
| 2 | 0 | 2 | 4 | 1 | 3 |
| 3 | 0 | 3 | 1 | 4 | 2 |
| 4 | 0 | 4 | 3 | 2 | 1 |
验证一下逆元:
- 1 的逆元是 1(1×1=1)
- 2 的逆元是 3(2×3=6≡1 mod 5)
- 3 的逆元是 2(3×2=6≡1 mod 5)
- 4 的逆元是 4(4×4=16≡1 mod 5)
每个非零元都有逆元 ✓
生成元(本原元)
在 GF§ 的乘法群里,存在一些特殊的元素,叫做生成元 (Generator)或本原元(Primitive Element)。
生成元 g 的特点是:g 的各次幂能遍历所有非零元素。
比如在 GF(5) 中,2 是生成元:
- 2¹ = 2
- 2² = 4
- 2³ = 8 ≡ 3
- 2⁴ = 16 ≡ 1
正好遍历了 {1, 2, 3, 4} 所有非零元素。
而 4 就不是生成元:
- 4¹ = 4
- 4² = 16 ≡ 1
- 4³ = 4
- ...
只能遍历 {1, 4},周期只有 2。
生成元在密码学中很重要------Diffie-Hellman、ElGamal 这些算法的底数 g 用的就是生成元,这样离散对数的搜索空间才最大。
💡 GF§ 是密码学中最常用的有限域之一。RSA、Diffie-Hellman、ElGamal、DSA......这些经典公钥算法的运算全都是在 GF§ 上进行的。
5.3 有限域 GF(2ⁿ)
为什么需要 GF(2ⁿ)?
GF§ 虽然好用,但计算机是二进制的,处理 2 的幂次方的数特别方便。所以密码学中还有另一类非常重要的有限域:GF(2ⁿ)------特征为 2 的有限域。
GF(2ⁿ) 的元素个数是 2ⁿ 个,正好对应 n 位二进制数,非常适合计算机处理。AES 加密算法的核心运算就是在 GF(2⁸) 上进行的。
GF(2):最基础的二元域
先从最小的 GF(2ⁿ) 说起------GF(2),也就是 n=1 的情况。
GF(2) 只有两个元素:{0, 1}
运算规则:
加法(就是异或 XOR):
| + | 0 | 1 |
|---|---|---|
| 0 | 0 | 1 |
| 1 | 1 | 0 |
乘法(就是与 AND):
| × | 0 | 1 |
|---|---|---|
| 0 | 0 | 0 |
| 1 | 0 | 1 |
你看,GF(2) 的加法就是异或,乘法就是按位与------计算机原生支持,效率极高。
验证一下 GF(2) 是不是域:
- 加法交换群:单位元 0,每个元素的逆元是自己(因为 0+0=0,1+1=0)✓
- 非零元乘法交换群:只有 1 一个元素,1×1=1,单位元 1,逆元 1 ✓
- 分配律:成立 ✓
确实是域。
GF(2ⁿ) 的多项式表示
GF(2ⁿ) 中的每个元素,可以表示为一个次数小于 n 的多项式,系数在 GF(2) 中(也就是系数只能是 0 或 1)。
比如 GF(2³) = GF(8),每个元素是次数小于 3 的多项式:
| 二进制 | 多项式 | 十进制 |
|---|---|---|
| 000 | 0 | 0 |
| 001 | 1 | 1 |
| 010 | x | 2 |
| 011 | x + 1 | 3 |
| 100 | x² | 4 |
| 101 | x² + 1 | 5 |
| 110 | x² + x | 6 |
| 111 | x² + x + 1 | 7 |
一个元素有三种表示方式:二进制数、多项式、十进制整数,本质上是一样的。
GF(2ⁿ) 上的加法
加法非常简单:对应系数相加(GF(2) 加法,也就是异或)。
因为系数只能是 0 或 1,加起来就是异或。
用二进制表示的话,加法就是按位异或。
例子(GF(8) 中):
- (x² + 1) + (x + 1) = x² + x + (1+1) = x² + x + 0 = x² + x
- 二进制:101 + 011 = 110
- 十进制:5 + 3 = 6
验证:5 XOR 3 = 6 ✓
注意:GF(2ⁿ) 中,每个元素的加法逆元就是它自己!因为 a + a = 0(每一位都是 1+1=0 或 0+0=0)。
GF(2ⁿ) 上的乘法
乘法稍微复杂一点。两个多项式相乘,然后模一个 n 次的不可约多项式。
什么是不可约多项式?就是在 GF(2) 上不能再分解因式的多项式,类似于整数中的质数。
为什么要模不可约多项式?和 GF§ 中 p 必须是质数的道理一样------为了保证每个非零元都有逆元,从而构成域。
完整手算例子:GF(8) 乘法
我们用 GF(2³) = GF(8) 来演示,选取不可约多项式 p ( x ) = x 3 + x + 1 p(x) = x^3 + x + 1 p(x)=x3+x+1。
例 1:计算 ( x 2 + 1 ) × ( x + 1 ) (x^2 + 1) \times (x + 1) (x2+1)×(x+1),也就是 5 × 3。
第一步:多项式相乘
( x 2 + 1 ) ( x + 1 ) = x 3 + x 2 + x + 1 (x^2 + 1)(x + 1) = x^3 + x^2 + x + 1 (x2+1)(x+1)=x3+x2+x+1
第二步:模不可约多项式 x 3 + x + 1 x^3 + x + 1 x3+x+1
因为 x 3 ≡ x + 1 ( m o d p ( x ) ) x^3 \equiv x + 1 \pmod{p(x)} x3≡x+1(modp(x))(从 x 3 + x + 1 ≡ 0 x^3 + x + 1 \equiv 0 x3+x+1≡0 移项得到),把 x 3 x^3 x3 替换掉:
x 3 + x 2 + x + 1 ≡ ( x + 1 ) + x 2 + x + 1 = x 2 + 2 x + 2 = x 2 x^3 + x^2 + x + 1 \equiv (x + 1) + x^2 + x + 1 = x^2 + 2x + 2 = x^2 x3+x2+x+1≡(x+1)+x2+x+1=x2+2x+2=x2
(注意系数是 GF(2) 的,2 ≡ 0)
所以结果是 x 2 x^2 x2,也就是二进制 100,十进制 4。
验证一下:5 × 3 = 15,在 GF(8) 中结果是 4?我们换个方式验证:
- 4 × 3 = 12,按同样方法算应该等于 5(因为如果 5×3=4,那么 4×3⁻¹ 应该等于 5)
- 暂时先相信推导过程,后面会验证乘法群的性质。
例 2:计算 x × x²,也就是 2 × 4。
- x × x² = x³ ≡ x + 1
- 结果是 x + 1,也就是 011,十进制 3
生成元验证
还是用 GF(8),不可约多项式 x³ + x + 1。
我们看看 x(也就是 2)是不是生成元:
- x¹ = x = 2
- x² = x² = 4
- x³ = x + 1 = 3
- x⁴ = x × x³ = x(x + 1) = x² + x = 6
- x⁵ = x × x⁴ = x(x² + x) = x³ + x² = (x+1) + x² = x² + x + 1 = 7
- x⁶ = x × x⁵ = x(x² + x + 1) = x³ + x² + x = (x+1) + x² + x = x² + 1 = 5
- x⁷ = x × x⁶ = x(x² + 1) = x³ + x = (x+1) + x = 1
正好遍历了 1 到 7 所有非零元素!x 确实是生成元。
这也验证了乘法确实构成一个群------封闭性、单位元、逆元都有了。
AES 中的 GF(2⁸)
实际密码学中最常用的是 GF(2⁸),也就是 256 个元素的有限域。AES 加密算法的 S 盒就是基于 GF(2⁸) 上的乘法逆元构造的。
AES 使用的不可约多项式是:
x 8 + x 4 + x 3 + x + 1 x^8 + x^4 + x^3 + x + 1 x8+x4+x3+x+1
每个字节(8 位)就是 GF(2⁸) 中的一个元素,字节的异或就是 GF(2⁸) 加法,字节乘法就是 GF(2⁸) 乘法------非常自然。
💡 GF(2ⁿ) 是计算机最擅长处理的有限域。加法就是异或,硬件一秒钟能做几十亿次;乘法虽然复杂一点,但也有高效的硬件实现。对称密码、哈希函数、纠错码......很多地方都能看到 GF(2ⁿ) 的身影。
5.4 有限域在密码学中的作用
为什么密码学偏爱有限域?
简单说,有限域就是一个"数字游乐场"------里面的数字是有限的,但加减乘除都能玩,而且结果永远不会跑出这个圈子。
密码学选它当数学基础,主要因为三个好处:
1. 数字不会越变越大(不会溢出)
计算机存数据的空间是有限的,不能无限增长。
普通整数乘法:2 × 3 = 6,再乘 5 = 30......越乘越大,最后存不下。
有限域里的运算就不一样了------不管你怎么加、怎么乘,结果永远在这个有限集合里,位数固定,存储空间可控,效率稳定。
2. 四则运算齐全(想怎么算怎么算)
有限域里加减乘除都能用,而且运算规则和你学过的实数差不多(交换律、结合律、分配律都成立)。
这意味着程序员可以在上面构建各种复杂算法,不用担心"这个运算合不合法"的问题。
3. 藏着难解的数学题(保证安全)
有限域上有个叫"离散对数问题"的东西,公认很难算。正因为难,密码算法才安全------黑客想破解,就得解这道难题,而目前还没有快速解法。
具体用在哪些地方?
应用 1:对称加密(AES 等)
你可能听过 AES 加密,它的核心运算全都是在 GF(2⁸) 上进行的。
不用纠结这个符号是什么意思,记住一点就行:一个字节(8位)正好对应 GF(2⁸) 里的一个元素,硬件实现起来特别高效。
不只是 AES,很多对称加密算法(SM4、Camellia 等)都用到了类似的思路。
应用 2:公钥加密(RSA、DH 等)
RSA、Diffie-Hellman 密钥交换这些经典公钥算法,都是在 GF§ 上玩的。
- RSA 加密解密:本质是"模幂运算"(一个数的多少次方,再取模)
- DH 密钥交换:两个人各自算一个数,最后能得到相同的密钥
这些算法的安全性,全靠"离散对数很难算"这个假设撑着。
应用 3:椭圆曲线加密(ECC)
椭圆曲线密码学(ECC)现在越来越火,因为它用更短的密钥就能达到同样的安全级别。
虽然不是直接在有限域的乘法群上,但椭圆曲线本身就是定义在有限域上的。曲线上的点构成一个"群",这个群上的离散对数问题比 GF§ 上的更难解------所以更安全、更高效。
应用 4:纠错码
CD、DVD、二维码、深空通信......这些地方都用到了纠错码(比如 Reed-Solomon 码),背后的数学也是有限域。
简单说就是:数据传输时可能出错,纠错码能帮你把错的地方找出来、修正过来。
应用 5:哈希函数
一些哈希函数的设计也用到了有限域的思想,比如用线性变换来"打乱"数据,让输出更均匀、更难预测。
一句话总结
有限域是密码学的"运算舞台"。
所有加密、解密、签名、验证......这些操作都是在某个有限域上进行的。它提供了:
- 封闭的运算空间(不会溢出)
- 完整的代数结构(加减乘除都能用)
- 难解的数学问题(保证安全)
理解了有限域,你就理解了密码算法为什么这么设计、为什么能工作、为什么安全。
🔑 一句话记住:群环域是抽象代数的三层结构,有限域是密码学的运算舞台。从对称密码到公钥密码,从 AES 到 ECC,背后都有它的身影。