从零实现RSA加密算法:原理、代码与实战指南
2026/7/30 10:08:48 网站建设 项目流程

1. 项目概述:从“黑话”到“白话”的RSA之旅

最近在社区里看到不少朋友在讨论RSA,从“前端rsa +aes加密安全吗”到“navicat15激活 rsa public key not find”,再到各种“解密工具”和“算法实现”,感觉这个概念既熟悉又陌生。熟悉是因为它无处不在,是HTTPS、SSH、软件激活的基石;陌生是因为一提到“非对称加密”、“大素数分解”,很多人就开始头疼了。今天,我就想抛开那些复杂的数学证明和晦涩的术语,用最直白的方式,带你亲手实现一遍RSA加密解密算法。我们的目标不是成为密码学家,而是真正理解这个守护我们数字世界安全四十多年的老将,到底是怎么工作的。你会发现,它的核心思想,其实非常优雅和简单。

简单来说,RSA解决了一个古老的安全难题:如何在不安全的信道上(比如互联网),让两个从未见过面的人,安全地传递秘密。想象一下,你想给远方的朋友寄一个上锁的箱子,但钥匙只有一把。传统方法(对称加密)是你先把钥匙寄给他,但这路上钥匙可能被复制。RSA的做法是,你的朋友造了一把“魔法锁”(公钥)寄给你,这把锁任何人拿到都能锁上箱子,但一旦锁上,只有你朋友手里那把唯一的“魔法钥匙”(私钥)才能打开。公钥可以公开分发,私钥则必须严格保密。接下来,我们就从零开始,造出这把“魔法锁”和“魔法钥匙”。

2. RSA算法核心原理拆解:为什么“简单”却“难破”?

在动手写代码之前,我们必须先搞懂RSA赖以生存的数学基础。别怕,我们不用深究数论,只抓住最关键的几个概念和它们之间的关系。

2.1 基石:欧拉函数与模反元素

RSA的核心安全性建立在“大数分解难题”上,但它的运转依赖于两个更基础的数学工具。

首先欧拉函数 φ(n)。对于一个正整数n,φ(n)表示小于n的正整数中,与n互质(最大公约数为1)的数的个数。比如φ(8),小于8的数有1,2,3,4,5,6,7,其中与8互质的数是1,3,5,7,所以φ(8)=4。对于RSA最关键的一个性质是:如果n是两个质数p和q的乘积,那么φ(n) = (p-1)*(q-1)。这个公式是我们后续生成密钥的钥匙。

其次模反元素(模逆元)。如果两个正整数e和d,满足 (e * d) mod φ(n) = 1,那么我们称d是e对于模φ(n)的模反元素,或者说e和d在模φ(n)下互为乘法逆元。你可以把它理解为在“模运算”这个世界里的“倒数”关系。寻找这个d,需要用到扩展欧几里得算法,这是算法实现中的一个关键步骤。

注意:这里说的“简单易懂”,是指理解算法流程和实现思路的简单,而不是指其数学原理或破解难度简单。RSA的安全性恰恰来自于这些基础数学运算组合后产生的计算复杂性。

2.2 公钥与私钥的生成:一个精心设计的数学结构

理解了上述概念,生成RSA密钥对就变成了一个清晰的流程:

  1. 选择两个大质数p和q。这是安全性的根本。p和q必须足够大、随机且保密。在实际应用中,它们通常是1024位或2048位的二进制数。
  2. 计算n和φ(n)。计算n = p * q。这个n就是模数,会是公钥和私钥的一部分。接着计算φ(n) = (p-1) * (q-1)。
  3. 选择一个整数e。e需要满足两个条件:1 < e < φ(n),且e与φ(n)互质(即最大公约数gcd(e, φ(n)) = 1)。e通常选择一个较小的质数,如65537 (0x10001),这样加密运算会更快。这个e将成为公钥的一部分
  4. 计算e的模反元素d。计算d,使得 (e * d) mod φ(n) = 1。这个d就是私钥的核心部分

至此,我们得到了:

  • 公钥 (Public Key):由(n, e)组成。可以公开发给任何人。
  • 私钥 (Private Key):由(n, d)组成。必须绝对保密。

这个设计的精妙之处在于,从公开的(n, e)推导出私钥d,在数学上等价于要对大整数n进行质因数分解,求出p和q,从而算出φ(n)。而大数分解在经典计算机上是一个公认的计算难题。

2.3 加密与解密:模幂运算的魔法

有了密钥,加解密过程出乎意料地简洁,都是一个模幂运算:

  • 加密(用公钥):假设明文是一个数字m(文本需要先转换成数字,例如通过ASCII或Unicode),且m必须小于n。计算密文c = m^e mod n
  • 解密(用私钥):收到密文c后,计算明文m = c^d mod n

为什么这样就能恢复明文?这背后是欧拉定理在支撑。简单来说,因为 ed ≡ 1 (mod φ(n)),所以 c^d ≡ (m^e)^d ≡ m^(ed) ≡ m^(k*φ(n)+1) ≡ m (mod n)。这个推导保证了解密的正确性。

实操心得:很多初学者在这里会困惑,加解密不就是求个幂再取模吗?难点在哪?真正的难点和性能瓶颈在于,当e、d、n都是几百上千位的大整数时,直接计算m^e会是一个天文数字,根本算不出来。因此,实际实现必须使用快速模幂算法,它能在对数时间复杂度内完成计算,这是工程实现的关键。

3. 手把手实现:Python代码逐行解析

理论说再多,不如一行代码。我们用Python来实现一个“教学版本”的RSA。这个版本为了清晰,使用了小质数,绝对不可用于实际加密,但能让你看清每一个步骤。

3.1 核心工具函数实现

任何RSA实现都离不开几个基础数学函数:判断质数、求最大公约数、扩展欧几里得算法和快速模幂算法。

import random import math def is_prime(num: int) -> bool: """简单的质数判断函数(教学用,非高效)""" if num < 2: return False if num == 2 or num == 3: return True if num % 2 == 0: return False # 只需检查到平方根即可 limit = int(math.isqrt(num)) + 1 for i in range(3, limit, 2): if num % i == 0: return False return True def gcd(a: int, b: int) -> int: """欧几里得算法求最大公约数""" while b != 0: a, b = b, a % b return a def extended_gcd(a: int, b: int): """扩展欧几里得算法,返回 (gcd, x, y) 满足 ax + by = gcd(a,b)""" if b == 0: return a, 1, 0 else: g, x1, y1 = extended_gcd(b, a % b) x = y1 y = x1 - (a // b) * y1 return g, x, y def modinv(e: int, phi: int) -> int: """利用扩展欧几里得算法求模逆元 d,满足 (e*d) % phi == 1""" g, x, _ = extended_gcd(e, phi) if g != 1: raise ValueError('e 和 φ(n) 必须互质') # 确保返回正数 return x % phi def fast_pow_mod(base: int, exponent: int, modulus: int) -> int: """快速模幂算法,高效计算 (base^exponent) % modulus""" result = 1 base = base % modulus while exponent > 0: # 如果指数是奇数,乘一次当前的底数 if exponent & 1: result = (result * base) % modulus # 指数右移一位(除以2),底数平方 exponent >>= 1 base = (base * base) % modulus return result

为什么需要快速模幂算法?假设我们计算1234567^65537 mod 999999999,直接先算1234567^65537,这个数字的位数将超过30万位,任何计算机的内存都无法容纳。快速模幂算法通过“边乘边模”和“指数二分”的策略,将计算复杂度从O(exponent)降低到O(log exponent),使得大数运算成为可能。

3.2 密钥生成:打造你的公钥与私钥

现在,我们将理论步骤转化为代码。

def generate_rsa_keys(bit_length=64): """生成RSA密钥对。bit_length仅为演示,实际应用需>=1024""" # 1. 选择两个质数p和q(这里简化,随机选取) # 注意:真实场景应使用密码学安全的随机数生成器和更高效的质数检测算法(如Miller-Rabin) primes = [i for i in range(2** (bit_length//2 - 1), 2** (bit_length//2)) if is_prime(i)] if len(primes) < 2: raise ValueError("范围内质数不足") p = random.choice(primes) q = random.choice([x for x in primes if x != p]) print(f"[密钥生成] 选择质数 p = {p}, q = {q}") # 2. 计算 n 和 φ(n) n = p * q phi_n = (p - 1) * (q - 1) print(f"[密钥生成] 计算 n = p*q = {n}, φ(n) = (p-1)*(q-1) = {phi_n}") # 3. 选择公钥指数 e,通常为65537,因为它与大多数φ(n)互质且二进制表示中1很少(计算快) e = 65537 # 确保 e 小于 φ(n) 且互质 if e >= phi_n or gcd(e, phi_n) != 1: # 如果不满足,找一个小的互质奇数 e = 3 while gcd(e, phi_n) != 1: e += 2 print(f"[密钥生成] 选择公钥指数 e = {e}") # 4. 计算私钥指数 d,即 e 模 φ(n) 的逆元 d = modinv(e, phi_n) print(f"[密钥生成] 计算私钥指数 d = {d}") print(f"[密钥生成] 验证 (e*d) % φ(n) = {(e*d) % phi_n} (应为1)") # 返回公钥和私钥 public_key = (n, e) private_key = (n, d) return public_key, private_key, p, q # 返回p,q仅用于演示,实际应丢弃

关键参数选择解析

  • p和q:演示中我们用了小质数。现实中,它们必须是从极大范围(如2^1023到2^1024)中随机选取并通过多次米勒-拉宾素性测试的“很可能质数”。
  • e的选择:为什么常用65537?第一,它是一个质数,与绝大多数φ(n)互质的概率极高。第二,它的二进制表示是10000000000000001,只有两个1,这使得基于它的加密操作(快速模幂)非常高效。第三,它足够大,避免了某些低指数攻击。
  • d的计算modinv函数利用扩展欧几里得算法,高效地找到了d。这是密钥生成中最核心的计算之一。

3.3 加密与解密函数实现

有了密钥,加解密函数就非常直观了。

def rsa_encrypt(plaintext_int: int, public_key): """使用公钥加密整数明文""" n, e = public_key if plaintext_int >= n: raise ValueError("明文数字必须小于模数 n") ciphertext_int = fast_pow_mod(plaintext_int, e, n) return ciphertext_int def rsa_decrypt(ciphertext_int: int, private_key): """使用私钥解密密文整数""" n, d = private_key plaintext_int = fast_pow_mod(ciphertext_int, d, n) return plaintext_int # 文本与数字的转换辅助函数(简单示例) def text_to_int(text: str) -> int: """将文本转换为整数(简单拼接ASCII码,仅演示)""" # 注意:此方法仅适用于很短文本,且效率不高。实际使用PKCS#1等填充方案。 num = 0 for char in text: num = num * 256 + ord(char) # 假设ASCII扩展,每个字符占一个字节 return num def int_to_text(num: int) -> str: """将整数转换回文本""" chars = [] while num > 0: chars.append(chr(num % 256)) num //= 256 return ''.join(reversed(chars))

3.4 完整流程演示

让我们把上面的零件组装起来,跑一个完整的例子。

def main(): print("="*50) print("RSA加密解密算法完整演示") print("="*50) # 1. 生成密钥 print("\n--- 步骤1: 生成RSA密钥对 ---") public_key, private_key, p, q = generate_rsa_keys(bit_length=32) # 小位数用于演示 n, e = public_key _, d = private_key print(f"公钥 (n, e) = ({n}, {e})") print(f"私钥 (n, d) = ({n}, {d})") # 2. 准备明文 plaintext = "HELLO" print(f"\n--- 步骤2: 准备明文 ---") print(f"明文文本: '{plaintext}'") m = text_to_int(plaintext) print(f"明文转换为整数 m = {m}") if m >= n: print(f"警告:明文整数 {m} >= 模数 n {n},需要分块或使用填充方案。本例中我们假设 m < n。") # 为演示继续,我们换一个更短的文本 plaintext = "HI" m = text_to_int(plaintext) print(f"更换明文为: '{plaintext}', m = {m}") # 3. 加密 print(f"\n--- 步骤3: 使用公钥加密 ---") print(f"加密计算: c = m^e mod n = {m}^{e} mod {n}") c = rsa_encrypt(m, public_key) print(f"得到密文整数 c = {c}") # 4. 解密 print(f"\n--- 步骤4: 使用私钥解密 ---") print(f"解密计算: m' = c^d mod n = {c}^{d} mod {n}") m_decrypted = rsa_decrypt(c, private_key) print(f"解密得到整数 m' = {m_decrypted}") decrypted_text = int_to_text(m_decrypted) print(f"整数转换回文本: '{decrypted_text}'") # 5. 验证 print(f"\n--- 步骤5: 验证 ---") if m == m_decrypted: print("✅ 成功!解密后的明文与原始明文一致。") else: print("❌ 失败!解密结果错误。") print("="*50) if __name__ == "__main__": main()

运行这段代码,你会在控制台看到一个完整的RSA流程:生成质数、计算密钥、加密、解密、验证。这比任何文字描述都更直观。

4. 从教学到实战:必须跨越的鸿沟

上面我们实现了一个“玩具级”的RSA。它能帮你理解原理,但离真正能用的加密还差十万八千里。以下是教学版本和工业级应用的主要差距,也是你未来深入时需要关注的点。

4.1 密钥生成与安全性

  1. 质数生成:我们用了简单的试除法,效率极低。工业级使用米勒-拉宾素性测试等概率性算法,快速找到“很可能质数”。OpenSSL等库的密钥生成过程是高度优化的。
  2. 密钥长度:我们用了32位模数,一眨眼就能被分解。当前最低安全标准是2048位(n的长度),相当于两个1024位的质数相乘。3072位或4096位用于更高安全需求。
  3. 质数选择要求:p和q不能太接近,它们的差要足够大,否则可以通过费马分解法或平方根逼近法轻易破解。通常要求|p-q| > 2^(比特数/2 - 100)。

4.2 数据编码与填充方案

这是我们演示版最大的缺陷。我们简单地将文本转成了整数,这存在严重问题:

  • 无法加密长文本:因为m必须小于n。对于2048位的n,其数值上限约10^616,看似很大,但直接编码文本很快就会超过。
  • 确定性加密:同样的明文,每次加密都会得到同样的密文。这不符合语义安全要求,攻击者可以通过猜测和比对密文来破解。
  • 脆弱性:对小明文(如m=0, 1, 或非常小的数)加密,密文可能等于明文,毫无安全性可言。

解决方案是使用填充方案。最著名的是PKCS#1 v1.5OAEP

  • PKCS#1 v1.5:在加密前,在明文前面添加一个特定的、包含随机数的填充字符串,然后再转换为整数。这确保了每次加密结果不同,并且明文被“放大”到接近n的大小。
  • OAEP:更安全的填充方案,结合了随机性和哈希函数,能提供更好的安全性证明,是现代应用的首选。

一个简单的PKCS#1 v1.5加密模式思想

加密前数据块 = 0x00 || 0x02 || 随机非零填充串 || 0x00 || 原始明文

解密后,需要解析这个格式,去除填充,得到原始明文。

4.3 性能优化

RSA计算量大,尤其是解密(私钥指数d通常很大)。优化手段包括:

  • 使用中国剩余定理:私钥持有者知道p和q,可以用CRT将模n的大计算分解为模p和模q的两个较小计算,再将结果合并,速度提升约4倍。
  • 选择小公钥e:如前所述,e=65537几乎是标准,加密极快。
  • 硬件加速:使用支持大数模幂运算的专用指令集(如Intel的MPX)。

5. 常见问题与实战排坑指南

在实际开发和应用RSA时,你会遇到各种各样的问题。这里我整理了几个最常见的“坑”。

5.1 “RSA公钥未找到”类错误

就像热词里提到的“navicat15激活 rsa public key not find”,这类错误通常不是算法问题,而是工程和格式问题

  • 问题根源:公钥/私钥不是简单的(n, e)或(n, d)数字对。为了便于存储和交换,它们被编码成标准格式,如PEM(Base64编码的文本,带有-----BEGIN PUBLIC KEY-----头尾)或DER(二进制格式)。很多工具和库需要读取这种格式化的文件。
  • 解决方案
    1. 确认密钥格式:用文本编辑器打开你的公钥文件,看是否是PEM格式。
    2. 使用正确的加载方式:不要自己解析数字。使用标准库。
    # Python示例:使用cryptography库加载PEM格式公钥 from cryptography.hazmat.primitives import serialization with open("public_key.pem", "rb") as key_file: public_key = serialization.load_pem_public_key(key_file.read())
    1. 检查文件路径:确保程序指定的密钥文件路径绝对正确。

5.2 前端RSA + AES加密是否安全?

这是一个经典的混合加密架构,也是HTTPS等协议的核心思想。

  • RSA的短板:速度慢,不适合加密大量数据。
  • AES的短板:是对称加密,需要安全地共享密钥。
  • 混合加密模式
    1. 客户端随机生成一个AES密钥(称为会话密钥)。
    2. 客户端用服务器的RSA公钥加密这个AES密钥。
    3. 客户端用这个AES密钥加密实际要传输的数据
    4. 客户端将加密后的AES密钥和加密后的数据一起发送给服务器。
    5. 服务器用RSA私钥解密出AES密钥。
    6. 服务器用AES密钥解密数据。
  • 安全性:只要RSA密钥足够强,且实现正确(使用OAEP填充等),这个模式是安全的。前端RSA加密的意义在于,在不安全信道上安全地传递对称加密的密钥。

5.3 数据长度限制与分段加密

这是新手最常踩的坑。直接用RSA加密超过密钥长度限制的数据会报错。

  • 计算最大加密长度:对于RSA with PKCS#1 v1.5 padding,最大明文长度(字节)≈ 密钥长度(字节) - 11。例如,2048位密钥(256字节),最大明文长度约为 256 - 11 = 245字节。
  • 解决方案
    1. 混合加密:如上所述,用RSA传AES密钥,用AES加密数据。这是最佳实践。
    2. 分段加密:如果非要纯RSA,需要将数据按最大长度分块,每块单独加密,解密后再拼接。极其不推荐,因为效率低且易出错。

5.4 签名与验签

RSA除了加密,另一个核心用途是数字签名,用于验证数据的完整性和来源。

  • 签名过程(用私钥):对数据的哈希值(如SHA-256)用私钥进行“解密”运算(实际是计算s = hash(m)^d mod n),得到签名s。
  • 验签过程(用公钥):对收到的数据计算同样的哈希值,然后用公钥对签名s进行“加密”运算(计算h' = s^e mod n),比较h'是否等于计算出的哈希值。如果相等,则证明数据来自私钥持有者且未被篡改。
  • 注意:签名和加密是逆过程,但绝不能混用。即,不要用加密的私钥去做签名的事,反之亦然。应使用专门的签名/验签API。

5.5 密钥管理与存储

“密钥安全,则系统安全”。

  • 私钥存储:绝不能硬编码在代码或前端。应存储在安全的服务器端,使用密钥管理服务或硬件安全模块,并设置严格的访问控制。
  • 公钥分发:通过可信渠道分发,如内置在客户端、通过HTTPS下载等,防止中间人替换公钥。
  • 密钥轮换:定期更换密钥对,即使私钥未泄露,也能降低长期风险。

6. 超越基础:理解现实世界的RSA生态

当你理解了基础实现,再看那些热词,就会有豁然开朗的感觉。

  • ssl/tls:远程主机支持rsa密钥交换:在TLS握手初期,客户端和服务器可能使用RSA来交换预主密钥,从而生成后续对称加密的会话密钥。虽然现在更推荐基于椭圆曲线的密钥交换,但RSA仍然被广泛支持。
  • cobalt strike 加密流量解密:这通常指安全研究人员或防御方试图解密C2(命令与控制)流量。如果C2使用RSA加密通信密钥,那么获取到服务器的私钥是解密的关键,这凸显了私钥保密的重要性。
  • navicat15激活 rsa public key not find:很多软件的离线激活机制,是客户端用内置的公钥加密机器信息生成请求码,服务器用私钥解密并验证后,再生成激活码。这个错误就是客户端找不到用于加密的那个公钥文件。
  • linux rocky 获取用户的rsa密钥:这通常指获取用于SSH认证的RSA密钥对(~/.ssh/id_rsaid_rsa.pub)。SSH利用RSA(或Ed25519等)进行身份认证,免去了密码登录。

实现一个教学版的RSA算法,就像亲手搭了一个乐高模型,它让你看清了每一个齿轮是如何咬合的。但真正的密码学工程,是在这个模型之上,构建一座能抵御狂风暴雨的坚固城堡——这涉及到标准的填充方案、高效的底层库、严格的随机数生成、安全的密钥管理和持续演进的协议。希望这次从零开始的旅程,能帮你拆解了RSA的神秘感。下次当你再遇到“RSA加密”时,你看到的将不再是一个黑盒,而是一个由大素数、模幂运算和欧拉定理构成的精巧世界。记住,在安全领域,理解原理是正确使用的前提,而“不要自己发明密码学”则是保护自己和他人数据的第一法则。如果你要在实际项目中使用加密,请务必使用久经考验的成熟库,如Python的cryptography、Java的Bouncy Castle或系统自带的OpenSSL

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询