RSA加密算法:数学之美守护信息安全

一、从生活场景说起

想象这样一个场景:你想给好友发送一份机密文件,但你们之间没有任何安全的通信渠道。如果直接发送密码,窃听者就能截获;如果不发送密码,对方又无法解密。

RSA的解决方案是:把钥匙分成两把。

  • 公钥:公开给所有人,用来"上锁"(加密)
  • 私钥:只有你自己保管,用来"开锁"(解密)

这听起来像魔法,但背后的数学却优雅而严谨。


二、核心数学原理

2.1 欧拉定理(Euler's Theorem)

RSA的数学基石是欧拉定理:

\[a^{\phi(n)} \equiv 1 \pmod{n} \]

其中 \(\phi(n)\) 是欧拉函数,表示小于 \(n\) 且与 \(n\) 互质的正整数的个数。

2.2 RSA密钥生成四步法

第一步:选择两个大素数

\[p, q \quad (\text{实际应用中通常是几百位的大素数}) \]

第二步:计算模数

\[n = p \times q \]

第三步:计算欧拉函数

\[\phi(n) = (p-1)(q-1) \]

第四步:选择公钥和私钥

  • 选择公钥指数 \(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)}\),可设:

\[e \times d = k \times \phi(n) + 1 \]

因此:
image

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

\[m^{\phi(n)} \equiv 1 \pmod{n} \]

所以:
image

证毕!


三、实战案例演示

让我们用具体数字来走一遍完整流程(实际应用中的数字要大得多)。

📋 场景设定

Alice 要给 Bob 发送秘密消息 "7"


步骤1:Bob 生成密钥对

选择素数

  • \(p = 11\)
  • \(q = 3\)

计算模数

\[n = 11 \times 3 = 33 \]

计算欧拉函数

\[\phi(33) = (11-1)(3-1) = 10 \times 2 = 20 \]

选择公钥 \(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 = m^e \mod n = 7^3 \mod 33 \]

计算:

\[7^3 = 343 \]

\[343 \div 33 = 10 \text{ 余 } 13 \]

密文\(c = 13\)

Alice 将 "13" 发送给 Bob


步骤4:Bob 解密密文

密文\(c = 13\)

解密计算

\[m = c^d \mod n = 13^7 \mod 33 \]

计算 \(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}\)

\[832 \div 33 = 25 \text{ 余 } 7 \]

明文\(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计算较慢,实际使用中通常采用混合加密

  1. 用RSA加密一个随机的对称密钥
  2. 用对称加密(如AES)加密实际数据
  3. 这样既安全又高效

六、总结

RSA算法的优雅之处在于:

  1. 不对称性:加密和解密使用不同的钥匙
  2. 数学基础扎实:建立在数论中经过严格证明的定理之上
  3. 计算不对称:正向计算(加密)容易,逆向计算(破解)困难
核心公式回顾:
┌─────────────────────────────────────┐
│  密钥生成: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()
posted @ 2026-05-18 19:24  古松青的技术博客  阅读(29)  评论(0)    收藏  举报