用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. 二进制表示只有两个1,加速模幂运算
  2. 足够大以避免某些攻击
  3. 是质数,与多数φ(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时需要特别注意以下几点:

  1. 质数生成质量:使用强随机数生成器,避免可预测的质数
  2. 侧信道攻击防护:确保幂运算时间不泄露密钥信息
  3. 密钥管理:私钥必须严格保护,建议使用HSM等安全硬件
  4. 协议层面安全:单纯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的内部工作原理。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐