别再死记硬背了!用Python从零实现RSA加密,顺便搞懂‘大质数’和‘欧拉函数’
用Python手撕RSA加密:从质数生成到密钥分发的全流程实战
当你在网上购物输入信用卡信息时,当你在公司内网登录邮箱时,当你在手机端进行指纹支付时——所有这些场景背后,都有一双无形的手在保护数据安全,这就是非对称加密算法中的皇冠明珠:RSA。不同于需要预先共享密钥的传统加密方式,RSA允许完全陌生双方建立安全通信,这种特性让它成为现代互联网的基石加密技术。
但翻开任何一本密码学教材,映入眼帘的往往是令人望而生畏的数学公式:欧拉定理、模反元素、大整数分解...这些抽象概念让很多开发者对RSA敬而远之。本文将采用代码优先的逆向学习法,用Python从零实现完整的RSA流程,让你在运行代码的过程中自然理解那些"可怕"的数学概念。我们会从质数检测开始,逐步构建密钥生成、加密解密的全套工具,最终实现一个可以实际运行的微型RSA系统。
1. 质数:RSA的原子结构
任何加密系统的强度都取决于其基础数学问题的计算复杂度。RSA的核心安全假设基于一个简单事实:大整数的质因数分解在计算上不可行。让我们用代码体验这个特性。
1.1 质数生成算法
要构造RSA密钥,首先需要生成两个大质数p和q。以下是使用Miller-Rabin概率性质数检测算法的实现:
import random
def is_prime(n, k=5):
"""Miller-Rabin质数检测"""
if n <= 1:
return False
elif n <= 3:
return True
elif n % 2 == 0:
return False
# 将n-1表示为d*2^s
d = n - 1
s = 0
while d % 2 == 0:
d //= 2
s += 1
for _ in range(k):
a = random.randint(2, n - 2)
x = pow(a, d, n)
if x == 1 or x == n - 1:
continue
for __ in range(s - 1):
x = pow(x, 2, n)
if x == n - 1:
break
else:
return False
return True
def generate_prime(bit_length=1024):
"""生成指定位数的大质数"""
while True:
num = random.getrandbits(bit_length)
# 确保是奇数且足够大
num |= (1 << bit_length - 1) | 1
if is_prime(num):
return num
这个实现中:
is_prime函数通过多次检测(默认5次)将误判概率降到极低generate_prime通过位操作确保生成指定位数的奇数
实际应用中,RSA-2048使用的质数通常是1024位。在个人电脑上生成一个1024位质数约需1-5秒。
1.2 质因数分解实验
为什么RSA依赖大质数?让我们做个对比实验:
import time
def factorize(n):
"""试除法质因数分解"""
factors = []
while n % 2 == 0:
factors.append(2)
n = n // 2
i = 3
while i * i <= n:
while n % i == 0:
factors.append(i)
n = n // i
i += 2
if n > 2:
factors.append(n)
return factors
# 测试不同位数的分解时间
for bits in [8, 16, 32, 64]:
p = generate_prime(bits)
q = generate_prime(bits)
n = p * q
start = time.time()
factors = factorize(n)
elapsed = time.time() - start
print(f"{bits}位数字分解结果: {factors} 耗时: {elapsed:.4f}秒")
运行结果可能类似:
8位数字分解结果: [61, 167] 耗时: 0.0001秒
16位数字分解结果: [223, 541] 耗时: 0.0012秒
32位数字分解结果: [32491, 57803] 耗时: 3.2145秒
64位数字分解结果: [4294967291, 4294967311] 耗时: 超过10分钟
这个实验直观展示了随着位数增加,分解难度呈指数级上升。现代RSA通常使用2048位以上的模数,用现有算法分解需要数亿年计算时间。
2. 密钥工程:从数学到代码
有了质数p和q,我们就可以构建完整的RSA密钥系统。这个阶段会涉及几个关键数学概念,我们将通过代码实现来揭示它们的实际意义。
2.1 欧拉函数与模数计算
欧拉函数φ(n)表示小于n且与n互质的正整数个数。对于RSA使用的n=pq(p,q为质数),φ(n)=(p-1)(q-1)。以下是计算过程:
def compute_modulus(p, q):
"""计算模数n和欧拉函数φ(n)"""
n = p * q
phi = (p - 1) * (q - 1)
return n, phi
2.2 选择公开指数e
公开指数e需要满足1 < e < φ(n)且与φ(n)互质。通常选择65537(2^16+1),原因有三:
- 二进制表示只有两个1,加速模幂运算
- 足够大以避免某些攻击
- 是质数,与多数φ(n)互质
def choose_public_exponent(phi):
"""选择公开指数e,默认使用65537"""
e = 65537
if math.gcd(e, phi) == 1:
return e
# 如果不互质,寻找附近的小质数
for candidate in [3, 5, 17, 257, 65537]:
if math.gcd(candidate, phi) == 1:
return candidate
raise ValueError("无法找到合适的e值")
2.3 计算私钥d:模反元素
私钥d是e关于φ(n)的模反元素,即满足ed ≡ 1 mod φ(n)。使用扩展欧几里得算法高效计算:
def extended_gcd(a, b):
"""扩展欧几里得算法"""
if a == 0:
return (b, 0, 1)
else:
g, y, x = extended_gcd(b % a, a)
return (g, x - (b // a) * y, y)
def compute_private_exponent(e, phi):
"""计算私钥d"""
g, x, _ = extended_gcd(e, phi)
if g != 1:
raise ValueError('e和φ(n)不互质,无法计算模反元素')
return x % phi
2.4 完整的密钥生成
整合以上步骤,得到完整的密钥生成函数:
def generate_rsa_keys(bit_length=1024):
"""生成RSA密钥对"""
p = generate_prime(bit_length // 2)
q = generate_prime(bit_length // 2)
n, phi = compute_modulus(p, q)
e = choose_public_exponent(phi)
d = compute_private_exponent(e, phi)
public_key = (n, e)
private_key = (n, d)
return public_key, private_key
密钥生成示例输出:
公钥 (n, e):
(178593930223239871678901234567890123456789...123456789, 65537)
私钥 (n, d):
(178593930223239871678901234567890123456789...123456789, 123456789012345678901234567890...123456789)
3. 加密与解密实现
有了密钥对,我们就可以实现RSA的核心功能:加密和解密。这两个操作本质上都是模幂运算。
3.1 加密:将明文转化为密文
加密过程是计算c ≡ m^e mod n,其中m是明文(转换为整数),c是密文:
def encrypt(message, public_key):
"""使用公钥加密消息"""
n, e = public_key
# 将消息转换为整数
m = int.from_bytes(message.encode('utf-8'), 'big')
if m >= n:
raise ValueError("消息过长,需要分块加密")
# 模幂运算
c = pow(m, e, n)
return c
3.2 解密:恢复原始消息
解密过程是计算m ≡ c^d mod n,恢复出原始明文:
def decrypt(ciphertext, private_key):
"""使用私钥解密密文"""
n, d = private_key
# 模幂运算
m = pow(ciphertext, d, n)
# 将整数转换回字节串
message = m.to_bytes((m.bit_length() + 7) // 8, 'big').decode('utf-8')
return message
3.3 完整流程演示
让我们看一个端到端的例子:
# 生成密钥对
public_key, private_key = generate_rsa_keys(bit_length=512)
# 原始消息
message = "RSA非对称加密实战"
# 加密
ciphertext = encrypt(message, public_key)
print(f"加密结果: {ciphertext}")
# 解密
decrypted = decrypt(ciphertext, private_key)
print(f"解密结果: {decrypted}")
典型输出:
加密结果: 123456789012345678901234567890...123456789
解密结果: RSA非对称加密实战
4. 性能优化与安全实践
在实际应用中,RSA的实现需要考虑性能和安全性问题。以下是几个关键优化点:
4.1 使用中国剩余定理加速解密
RSA解密可以通过中国剩余定理(CRT)显著加速:
def decrypt_crt(ciphertext, private_key, p, q):
"""使用CRT加速解密"""
n, d = private_key
dp = d % (p - 1)
dq = d % (q - 1)
qinv = pow(q, -1, p)
m1 = pow(ciphertext, dp, p)
m2 = pow(ciphertext, dq, q)
h = (qinv * (m1 - m2)) % p
m = m2 + h * q
message = m.to_bytes((m.bit_length() + 7) // 8, 'big').decode('utf-8')
return message
这种优化可以将解密速度提升3-4倍,是生产环境中的标准实践。
4.2 处理长消息:分块与填充
RSA一次能加密的数据长度受限于模数n的位数。对于长消息,需要分块处理并应用适当的填充方案(如OAEP):
def encrypt_long(message, public_key, block_size=64):
"""分块加密长消息"""
n, e = public_key
max_block = (n.bit_length() + 7) // 8 - 11 # 留出填充空间
encrypted_blocks = []
for i in range(0, len(message), block_size):
block = message[i:i+block_size]
m = int.from_bytes(block.encode('utf-8'), 'big')
c = pow(m, e, n)
encrypted_blocks.append(c)
return encrypted_blocks
def decrypt_long(encrypted_blocks, private_key):
"""分块解密密文"""
n, d = private_key
message_parts = []
for c in encrypted_blocks:
m = pow(c, d, n)
block = m.to_bytes((m.bit_length() + 7) // 8, 'big').decode('utf-8')
message_parts.append(block)
return ''.join(message_parts)
4.3 安全注意事项
实现RSA时需要特别注意以下几点:
- 质数生成质量:使用强随机数生成器,避免可预测的质数
- 侧信道攻击防护:确保幂运算时间不泄露密钥信息
- 密钥管理:私钥必须严格保护,建议使用HSM等安全硬件
- 协议层面安全:单纯RSA加密不安全,应结合适当协议(如RSA-OAEP)
以下是一个安全增强版的密钥生成示例:
import secrets
def secure_generate_prime(bit_length):
"""安全版本的质数生成"""
while True:
num = secrets.randbits(bit_length)
num |= (1 << bit_length - 1) | 1 # 确保最高位和最低位为1
if is_prime(num):
return num
在真实项目中使用RSA时,建议优先选择成熟的密码学库如Python的cryptography,而非自行实现。本文的实现主要用于教学目的,帮助理解RSA的内部工作原理。
更多推荐



所有评论(0)