RSA加密算法:数学之美守护信息安全
一、从生活场景说起
想象这样一个场景:你想给好友发送一份机密文件,但你们之间没有任何安全的通信渠道。如果直接发送密码,窃听者就能截获;如果不发送密码,对方又无法解密。
RSA的解决方案是:把钥匙分成两把。
- 公钥:公开给所有人,用来"上锁"(加密)
- 私钥:只有你自己保管,用来"开锁"(解密)
这听起来像魔法,但背后的数学却优雅而严谨。
二、核心数学原理
2.1 欧拉定理(Euler's Theorem)
RSA的数学基石是欧拉定理:
其中 \(\phi(n)\) 是欧拉函数,表示小于 \(n\) 且与 \(n\) 互质的正整数的个数。
2.2 RSA密钥生成四步法
第一步:选择两个大素数
第二步:计算模数
第三步:计算欧拉函数
第四步:选择公钥和私钥
- 选择公钥指数 \(e\),满足:\(1 < e < \phi(n)\) 且 \(\gcd(e, \phi(n)) = 1\)
- 计算私钥指数 \(d\),满足:\(e \times d \equiv 1 \pmod{\phi(n)}\)
2.3 加密与解密
- 加密:\(c = m^e \mod n\) (用公钥)
- 解密:\(m = c^d \mod n\) (用私钥)
2.4 为什么能解密?
关键证明:
我们需要证明:\((m^e)^d \equiv m \pmod{n}\)
由于 \(e \times d \equiv 1 \pmod{\phi(n)}\),可设:
因此:

根据欧拉定理,当 \(\gcd(m, n) = 1\) 时:
所以:

证毕! ✓
三、实战案例演示
让我们用具体数字来走一遍完整流程(实际应用中的数字要大得多)。
📋 场景设定
Alice 要给 Bob 发送秘密消息 "7"
步骤1:Bob 生成密钥对
选择素数:
- \(p = 11\)
- \(q = 3\)
计算模数:
计算欧拉函数:
选择公钥 \(e\):
选择 \(e = 3\)(满足 \(1 < 3 < 20\) 且 \(\gcd(3, 20) = 1\))
计算私钥 \(d\):
需要找到 \(d\) 使得 \(3d \equiv 1 \pmod{20}\)
即 \(3d = 20k + 1\)
尝试:
- \(k=1\): \(3d = 21 \Rightarrow d = 7\) ✓
验证:\(3 \times 7 = 21 \equiv 1 \pmod{20}\) ✓
步骤2:Bob 公开公钥,保管私钥
| 公钥(公开) | 私钥(保密) |
|---|---|
| \((e=3, n=33)\) | \((d=7, n=33)\) |
步骤3:Alice 加密消息
明文:\(m = 7\)
加密计算:
计算:
密文:\(c = 13\)
Alice 将 "13" 发送给 Bob
步骤4:Bob 解密密文
密文:\(c = 13\)
解密计算:
计算 \(13^7 \mod 33\):
采用快速幂算法:
- \(13^1 \equiv 13 \pmod{33}\)
- \(13^2 = 169 \equiv 4 \pmod{33}\) (\(169 = 5 \times 33 + 4\))
- \(13^4 \equiv 4^2 = 16 \pmod{33}\)
- \(13^7 = 13^4 \times 13^2 \times 13^1 \equiv 16 \times 4 \times 13 = 832 \pmod{33}\)
明文:\(m = 7\) ✓
步骤5:验证结果
| 阶段 | 数值 |
|---|---|
| 原始明文 | 7 |
| 加密后密文 | 13 |
| 解密后明文 | 7 |
完全正确! 🎉
四、为什么RSA是安全的?
🔒 安全性的数学基础
RSA的安全性依赖于一个数学难题:
大整数分解问题:给定 \(n = p \times q\),当 \(p\) 和 \(q\) 是几百位的大素数时,从 \(n\) 反推出 \(p\) 和 \(q\) 在计算上是不可行的。
攻击者的困境
假设黑客截获了:
- 密文 \(c = 13\)
- 公钥 \((e=3, n=33)\)
他想解密,必须知道 \(d\),而 \(d\) 需要 \(\phi(n)\),\(\phi(n)\) 需要知道 \(p\) 和 \(q\)。
对于 \(n=33\),分解很容易(就是 \(3 \times 11\))。但对于一个2048位的 \(n\):
- 目前最快的算法也需要数亿年
- 即使是量子计算机,也需要足够大的规模才能威胁RSA
五、实际应用中的RSA
📱 场景
| 场景 | RSA的作用 |
|---|---|
| HTTPS网站 | 浏览器和服务器交换对称加密密钥 |
| 数字签名 | 验证软件更新、合同的真实性 |
| SSH登录 | 免密码安全登录服务器 |
| 加密货币 | 比特币钱包地址生成 |
⚡ 优化技巧
由于RSA计算较慢,实际使用中通常采用混合加密:
- 用RSA加密一个随机的对称密钥
- 用对称加密(如AES)加密实际数据
- 这样既安全又高效
六、总结
RSA算法的优雅之处在于:
- 不对称性:加密和解密使用不同的钥匙
- 数学基础扎实:建立在数论中经过严格证明的定理之上
- 计算不对称:正向计算(加密)容易,逆向计算(破解)困难
核心公式回顾:
┌─────────────────────────────────────┐
│ 密钥生成:n = p×q, φ(n) = (p-1)(q-1) │
│ 公钥:(e, n),私钥:(d, n) │
│ 加密:c = m^e mod n │
│ 解密:m = c^d mod n │
│ 核心:e×d ≡ 1 (mod φ(n)) │
└─────────────────────────────────────┘
附 RSA关键算法伪代码
1. 2048位大素数是怎么找出来的? - Miller-Rabin素性测试法
生成指定位数的大素数
def generate_large_prime(bits: int = 2048) -> int:
"""
生成指定位数的大素数
实际流程:
1. 随机生成指定位数的奇数
2. 用Miller-Rabin测试快速排除合数
3. 通过测试后即为"工业级素数"
2048位素数在密码学中的意义:
- 搜索空间约 2^2048,暴力分解不可行
- 目前全球计算能力无法在合理时间内分解2048位RSA
"""
while True:
# 生成 bits 位的随机数,并确保最高位和最低位为1(保证位数和奇数)
candidate = random.getrandbits(bits)
candidate |= (1 << bits - 1) | 1 # 设置最高位和最低位
# 先进行小素数试除,快速排除明显合数
small_primes = [3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]
if any(candidate % p == 0 for p in small_primes):
continue
# Miller-Rabin严格测试
if is_prime_miller_rabin(candidate, k=40):
return candidate
Miller-Rabin素性测试:用于概率性地判断一个大整数 n 是否为素数
def is_prime_miller_rabin(n: int, k: int = 40) -> bool:
"""
Miller-Rabin素性测试 - 实际工业标准方法
参数:
n: 待测试的数
k: 测试轮数,每轮错误概率 < 1/4,k=40时错误概率 < 1/2^80,可忽略
返回结果:
- 如果 `n` 是素数 → 一定返回 `True`
- 如果 `n` 是合数 → 有极小概率误判为素数(但可通过增加测试轮数 `k` 将错误率降到任意低)
"""
if n < 2:
return False
if n in (2, 3):
return True
if n % 2 == 0:
return False
# 这段代码把 `n-1` 不断除以2,直到变成奇数,记录除了多少次2——这是在为概率性素数判定算法做数学预处理。
r, d = 0, n - 1
while d % 2 == 0:
r += 1
d //= 2
# 以上代码执行示例
# 假设 n=561
# 初始: r=0, d=560
# 第1轮: 560是偶数 → r=1, d=280
# 第2轮: 280是偶数 → r=2, d=140
# 第3轮: 140是偶数 → r=3, d=70
# 第4轮: 70是偶数 → r=4, d=35
# 第5轮: 35是奇数 → 循环结束
# 结果: 560 = 2⁴ × 35,即 n-1 = 2ʳ × d
# 进行 k 轮测试
for _ in range(k):
# 随机选择底数 a ∈ [2, n-2]
a = random.randrange(2, n - 1)
# 计算 x = a^d mod n
x = pow(a, d, n)
if x == 1 or x == n - 1:
continue
# 反复平方检验
for _ in range(r - 1):
x = pow(x, 2, n)
if x == n - 1:
break # 触发 break,跳过 else
else:
# 未通过测试,确定为合数
return False # 循环正常结束(未 break),执行 else
return True
# 以上代码执行示例
# n = 561 (合数), k = 5, r = 4, d = 35
# 第1轮: a = 2
# x = 2³⁵ mod 561 = 263
# 263 ≠ 1, ≠ 560
# 平方1次: 263² mod 561 = 166
# 平方2次: 166² mod 561 = 67
# 平方3次: 67² mod 561 = 1
# 序列: 263 → 166 → 67 → 1, 从未出现 560,且最终到1之前没有-1 → 合数!
# return False ← 561 被正确识别为合数
2. RSA公钥和私钥生成算法
def generate_rsa_keypair(bits: int = 2048) -> dict:
"""
RSA密钥对生成算法
数学原理:
1. 选择两个大素数 p, q
2. 计算 n = p * q (模数)
3. 计算 φ(n) = (p-1)(q-1) (欧拉函数)
4. 选择公钥指数 e,通常取 65537(= 2^16 + 1)
5. 计算私钥指数 d,使得 e*d ≡ 1 (mod φ(n))
公钥: (e, n) 私钥: (d, n)
"""
print("正在生成2048位大素数 p...")
p = generate_large_prime(bits // 2) # 1024位素数
print("正在生成2048位大素数 q...")
q = generate_large_prime(bits // 2) # 1024位素数
n = p * q
phi = (p - 1) * (q - 1)
# 公钥指数 e = 65537,这是工业标准选择
# 原因:1) 是素数 2) 二进制只有3个1,加密运算快 3) 足够大,安全性好
e = 65537
print("正在计算私钥指数 d...")
d = mod_inverse(e, phi)
return {
'public_key': (e, n),
'private_key': (d, n),
'p': p,
'q': q,
'phi': phi
}
def mod_inverse(e: int, phi: int) -> int:
"""
计算模逆元 d,使得 (e * d) ≡ 1 (mod φ(n))
使用扩展欧几里得算法
"""
g, x, _ = extended_gcd(e, phi)
if g != 1:
raise ValueError("模逆元不存在,e 和 φ(n) 必须互素")
return (x % phi + phi) % phi # 确保结果为正
def extended_gcd(a: int, b: int) -> tuple:
"""扩展欧几里得算法:返回 (g, x, y) 使得 ax + by = gcd(a, b) = g"""
if a == 0:
return b, 0, 1
g, x1, y1 = extended_gcd(b % a, a)
x = y1 - (b // a) * x1
y = x1
return g, x, y
3. 明文加密算法
def encrypt_message(message: str, public_key: tuple) -> list:
"""
将字符串消息分段加密(演示用,实际使用PKCS#1 v1.5或OAEP填充)
每块加密的最大长度受限于密钥长度
2048位密钥:每块最多可加密约256字节(实际更少,需留填充空间)
"""
e, n = public_key
block_size = (n.bit_length() // 8) - 11 # 预留填充空间(简化处理)
message_bytes = message.encode('utf-8')
ciphertext_blocks = []
for i in range(0, len(message_bytes), block_size):
block = message_bytes[i:i + block_size]
plaintext_int = int.from_bytes(block, byteorder='big')
cipher_int = rsa_encrypt(plaintext_int, public_key)
ciphertext_blocks.append(cipher_int)
return ciphertext_blocks
def rsa_encrypt(plaintext: int, public_key: tuple) -> int:
"""
RSA加密算法
数学公式: ciphertext = plaintext^e mod n
约束:明文必须小于模数 n
实际应用中,明文通常是对称密钥(如AES密钥),而非直接加密长消息
参数:
plaintext: 明文(整数形式,必须 < n)
public_key: (e, n)
返回:
ciphertext: 密文
"""
e, n = public_key
if plaintext >= n:
raise ValueError(f"明文 {plaintext} 必须小于模数 n = {n}")
# 使用快速幂算法(Python内置pow支持三参数模幂运算)
ciphertext = pow(plaintext, e, n)
return ciphertext
4.密文解密算法
def decrypt_message(ciphertext_blocks: list, private_key: tuple) -> str:
"""
将分段密文解密还原为字符串
"""
d, n = private_key
message_bytes = b''
for cipher_int in ciphertext_blocks:
plain_int = rsa_decrypt(cipher_int, private_key)
# 确定字节长度
block_bytes = plain_int.to_bytes((n.bit_length() + 7) // 8, byteorder='big')
# 去除前导零
block_bytes = block_bytes.lstrip(b'\x00')
message_bytes += block_bytes
return message_bytes.decode('utf-8')
def rsa_decrypt(ciphertext: int, private_key: tuple) -> int:
"""
RSA解密算法
数学公式: plaintext = ciphertext^d mod n
正确性证明(基于欧拉定理):
若 gcd(m, n) = 1,则 m^φ(n) ≡ 1 (mod n)
因此 m^(ed) = m^(kφ(n)+1) ≡ m (mod n)
"""
d, n = private_key
plaintext = pow(ciphertext, d, n)
return plaintext
5.完整演示
def demo():
print("=" * 60)
print("RSA算法完整演示")
print("=" * 60)
# 1. 生成密钥对
print("\n【步骤1】生成2048位RSA密钥对...")
keys = generate_rsa_keypair(2048)
e, n = keys['public_key']
d, _ = keys['private_key']
print(f"\n公钥 (e, n):")
print(f" e = {e} (65537)")
print(f" n = {str(n)[:50]}... (共{n.bit_length()}位)")
print(f"\n私钥 (d, n):")
print(f" d = {str(d)[:50]}... (共{d.bit_length()}位)")
# 2. 加密演示
print("\n【步骤2】加密演示")
message = "Hello, RSA! 这是一段测试明文。"
print(f"原始消息: {message}")
# 使用公钥加密
ciphertext_blocks = encrypt_message(message, keys['public_key'])
print(f"密文块数: {len(ciphertext_blocks)}")
print(f"第一块密文: {str(ciphertext_blocks[0])[:50]}...")
# 3. 解密演示
print("\n【步骤3】解密演示")
decrypted = decrypt_message(ciphertext_blocks, keys['private_key'])
print(f"解密结果: {decrypted}")
# 4. 验证
print("\n【验证】")
print(f"解密成功: {message == decrypted}")
# 5. 数学原理验证
print("\n【数学原理验证】")
test_msg = 123456789
encrypted = rsa_encrypt(test_msg, keys['public_key'])
decrypted_num = rsa_decrypt(encrypted, keys['private_key'])
print(f"原始数字: {test_msg}")
print(f"加密后: {encrypted}")
print(f"解密后: {decrypted_num}")
print(f"还原正确: {test_msg == decrypted_num}")
# 展示关键数学关系
print(f"\n关键验证: (m^e)^d mod n = m")
manual_check = pow(pow(test_msg, e, n), d, n)
print(f"手动计算: {manual_check}")
print(f"验证通过: {manual_check == test_msg}")
if __name__ == "__main__":
demo()

浙公网安备 33010602011771号