☰
RSA加密算法原理与时间复杂度分析:从密钥生成到工程实践
2026/10/17 1:35:23 网站建设 项目流程

做安全开发这些年,RSA加密算法几乎每次一聊到密钥分发都会出现。它的数学原理说复杂也复杂,说简单也简单:把两个大质数乘起来很容易,但把一个非常大的合数拆回质因数很难。这个不对称性支撑起了整个公钥体系,也是我们做时间复杂度分析时真正的起点。搞清楚RSA不只是学会调接口,更重要的是理解“为什么它能成立”和“为什么它这么慢”,这两件事直接关系到密钥长度选择、加解密性能调优,以及你是否敢在真实系统里用它。

这篇文章我会从数论基础讲起,把密钥生成、加解密流程、时间复杂度一条线串下来,最后分享一些实际踩过的坑。适合刚接触密码学的开发同学,也适合已经在用RSA但没时间细抠原理的人。看完你至少能回答三个问题:RSA为什么能加解密互逆?为什么加密快解密慢?密钥长度为什么不能随便降?

1. RSA的核心思路:从对称加密到公钥体系

1.1 对称加密的分发困境

在RSA出现之前,主流加密方式是双方持有同一个密钥完成加解密。问题很直接:甲乙两人要想安全通信,必须先把这同一个密钥安全地送到对方手里。你可以在办公室见面交接,可以通过信使传递,但放到互联网场景下,双方素未谋面,线上传密钥又等于裸奔,那就陷入了一个先有鸡还是先有蛋的死循环。

有人会想,那先建立加密通道再去传密钥行不行?不行,建立通道本身又需要密钥。传统的解决办法是引入一个可信中心,大家各自和中心提前共享密钥,再让中心负责转发。这个方案可用,但存在单点风险:中心一旦出问题,整个系统的信任基础就崩了。

RSA干脆换了一条路:不要求双方共享同一个秘密。每个接收方生成一对钥匙,一把公钥公开给全世界,谁都能用它加密消息;一把私钥自己保存,只有它能解开。想给某人发消息时,直接用他的公钥加密,密文在网络上被任意劫持都没有意义。理论上,这条信道上不存在需要秘密配送的东西,密钥分发问题被彻底绕开了。

1.2 单向函数与陷门

这件事能做成功,核心依赖一个数学概念:单向函数。给定输入算出输出容易,反过来从输出推出原始输入非常困难。日常生活里最典型的例子就是搅拌鸡蛋和蛋糕:把蛋清蛋黄打进面粉里容易,想从烤好的蛋糕里分离出鸡蛋就基本不可能了。

但公钥加密需要的不只是单向,还需要一个“后门”,让持有某些额外信息的人能逆转。这类结构叫陷门单向函数。RSA选中的函数是大整数乘法:随机选两个大质数相乘,得到一个大合数,这个计算毫秒级完成;但如果只给你这个大合数,要分解回两个质因数,在当前算力下就是天文数字。

关键在设计这个“后门”:公钥可以从 n 出发直接生成,任何人都能加密;但私钥的获取必须额外知道 n 是由哪两个质数相乘而来。换句话说,不知道 p 和 q,就没法求出私钥 d。这样一来,单向性和陷门就同时具备了。

1.3 为什么选大整数分解作为安全底座

市场上也有其他数学难题可用,比如离散对数、椭圆曲线上的点群问题。但RSA选择大整数分解作为安全底座,有一个实际优势:安全强度可以显式量化,工程实现也直观。一个整数有多大、分解难度大致什么样,都有比较成熟的经验结论,不会轻易出现“看起来安全但一算就破”的乌龙。

更要紧的是,把“两个质数相乘”作为正向操作,对性能非常友好。加密时只需要做一次模幂运算,哪怕是在慢速设备上也能跑。公钥本身可以公开到证书、网页、邮件里,任何人拿到就能用,不需要额外协商。

这里也要顺带点一句:大整数分解的困难至今没有被严格证明。也就是说,数学家并没有证明“不存在高效分解算法”,只是现阶段几十年的大规模尝试都没有成功。密码学接受这种“经验安全”,同时也在持续跟踪算法进展。理解了这一点,你就能明白密钥长度为什么要不断往上加,也明白为什么后量子密码那么受关注。

2. 数学原理拆解:RSA到底在算什么

2.1 φ函数与互质计数

RSA所有运算都在模 n 的整数世界里进行,而这个世界有个关键的“人口统计数字”要提前算清楚:小于 n 且与 n 互质的正整数有多少个。这个计数函数通常记作 φ(n),含义是“欧拉函数”,但你可以先不记名字,只记住它是“互质元素的个数”。

对单个质数 p 来说,从 1 到 p-1 的数都和 p 互质,所以 φ(p) = p-1。当 n 由两个不同质数相乘得到,也就是 n = p×q 时,φ(n) = (p-1)×(q-1)。这个式子是整个RSA设计的支点,可以简单理解:1 到 pq 之间的整数,去掉 p 的倍数和 q 的倍数,剩下的都同时不含因子 p 和 q,因此和 n 互质。

举个小例子:p=7,q=13,n=91。φ(91) = 6×12 = 72。也就是说,从 1 到 90 这 90 个数里,有 72 个数和 91 互质。不要小看这一步,后面所有加解密逆运算都建立在“这个φ值只有持有私钥的人才知道”这个前提上。

2.2 同余定理里的幂循环

RSA加解密共同用同一个运算:模幂。如果先看最简单的规律:对一个质数 p,任意和 p 互质的 a,都有 a^(p-1) ≡ 1 mod p。这说的是,指数走到 φ(p) 的时候,结果会绕回 1,再继续乘就进入循环。这个性质可以推广到合数:只要 a 和 n 互质,就有 a^φ(n) ≡ 1 mod n。

RSA正是利用这个循环来设计逆运算的。我们要求加密指数 e 和解密指数 d 满足:

e×d ≡ 1 mod φ(n)

也就是说,e×d = 1 + k×φ(n)。那么对任意消息 m,先加密再解密就是:

(m^e)^d = m^(ed) = m^(1+kφ(n)) = m × (m^φ(n))^k

括号里的 m^φ(n) 根据上面的规律等于 1 mod n,所以整体又回到 m。加密解密就像一个模运算下的旋转门:走过去是密文,拿着私钥再走回来就是明文。

严格说,上面这串推导默认 m 和 n 互质。真实消息如果恰好等于 p 或 q 的倍数,这个特例在概率上几乎不会发生,而且工程上通过填充等手段也把这种异常排除在外,所以可以放心使用。

2.3 扩展辗转相除法求私钥

现在只剩一个问题:给定 e 和 φ(n),怎么求 d。这需要解同余方程 e×d ≡ 1 mod φ(n),也就是求 e 在模 φ(n) 下的乘法逆元。最经典的算法是用扩展版辗转相除法,先求 e 和 φ(n) 的最大公约数,一路回代把最大公约数表示成两者的线性组合。

如果 gcd(e, φ(n)) = 1,就存在整数 x 和 y 满足:

e×x + φ(n)×y = 1

把等式两边同时取模 φ(n),右侧的 φ(n)×y 这一项消失,立刻得到 e×x ≡ 1 mod φ(n),所以 x 就是我们要的 d。如果算出来 x 是负数,就加一个 φ(n) 把它变成正的同余值。

拿数字走一遍:p=17,q=23,n=391,φ(n)=(17-1)×(23-1)=352。取 e=13,先算 352 除以 13:352 = 27×13 + 1。回代得到 1 = 352 - 27×13,所以 13×(-27) ≡ 1 mod 352,d = -27 mod 352 = 325。验证一下:13×325 = 4225,4225 除以 352 余 1,完全正确。

2.4 一个完整的小数字算例

把上面的参数凑成一套完整流程。公钥是 (n=391, e=13),私钥是 (n=391, d=325)。假设消息 m=42。

加密:c = 42^13 mod 391。手工算的话,先把指数展开成二进制逐步平方,42²=200,42⁴=118,42⁸=239,最后 42^13 = 42^8 × 42^4 × 42^1 = 239×118×42 mod 391 = 133。

解密要用 133^325 mod 391,手工指数太大,但数学上一定回到 42。实际代码里用快速模幂一行就能验:

p, q = 17, 23 n = p * q m = 42 e = 13 d = pow(e, -1, (p - 1) * (q - 1)) # 325 c = pow(m, e, n) # 133 assert pow(c, d, n) == m

注意上面这段只适合教学演示,真实系统绝不能做这么小的密钥,也绝不能裸用这种“无填充RSA”。

3. 密钥生成与加解密流程实操

3.1 密钥生成的7个步骤

生成RSA密钥的完整流程可以拆成七步:

  1. 用安全的随机数源生成两个长度相近的大质数 p 和 q,两者不能相等。
  2. 计算 n = p×q,n 的二进制位数就是密钥强度。
  3. 计算 φ(n) = (p-1)×(q-1)。
  4. 选择一个公开指数 e。工程上通常固定用 e=65537,它是个适当大的质数,二进制里只有两个 1,后续模幂计算很快。
  5. 检查 gcd(e, φ(n)) 是否等于 1。如果不等于 1,说明 e 在模 φ(n) 下没有逆元,需要重新选 e,或者重新生成 p、q。
  6. 用扩展辗转相除法求出 d,满足 e×d ≡ 1 mod φ(n)。
  7. 公钥发布为 (n, e),私钥保存为 (n, d),同时把 p、q、φ(n) 安全销毁,或者严格加密保存。

如果你在某些密码库里打开私钥想看内部字段,你会发现私钥里通常还保留着 p、q 和很多额外参数,这是为了用中国剩余定理优化私钥运算速度,并不是设计冗余。

3.2 加解密与签名

加密过程一句话:c = m^e mod n。密文 c 和 n 等长,都是密钥长度的二进制数。解密过程同样结构:m' = c^d mod n。

RSA的签名方向和加密正好相反。签名者用私钥对消息摘要做“私钥加密”,验证者用公钥“解密”这段签名,能解出来且和消息摘要一致,就说明签名确实来自持有私钥的人。这里必须强调,实际签名是对消息的哈希值做运算,不是直接签整个文件,否则文件一大,RSA的模幂开销会非常难看。

真实项目中,不会有人直接用RSA加密正文数据,因为长度限制和性能问题都扛不住。常规做法是混合加密:发送方随机生成一把对称密钥,用RSA加密这把很短的小密钥,正文数据用对称密码加密。接收方先用RSA解开小密钥,再用对称密码解正文。这样既安全又高效,RSA只管保护密钥,不碰大数据。

3.3 填充方案与长度限制

教科书里展示的 c = m^e mod n 被称为原始RSA,直接在生产里用会出问题。最明显的问题是确定性:同一个明文每次加密结果都一样,攻击者能通过密文比对猜测内容。此外还有小指数攻击、变形攻击等一大堆陷阱。

所以标准做法是先对明文做填充,再执行模幂。加密场景常用 OAEP 这类非对称加密填充,签名场景用 PSS 方案。填充会加入随机数和校验信息,让同样的明文每次产生不同的密文,也顺手破坏掉很多攻击路径。

同时填充压缩了可用明文长度。以 2048 位密钥为例,密钥长度 256 字节,使用 OAEP 并配合常见哈希函数时,实际能塞进去的明文大概只有 190 字节左右。超过这个限制就会报“明文过长”的错,解决办法不是硬塞,而是换成混合加密。

提示:生产环境千万不要自己去实现填充。成熟密码库默认开启的安全填充已经是多年攻防验证过的结果,自己加随机数、自己拼字节,十有八九会埋雷。

3.4 参数选择的工程经验

首要是 p 和 q 的随机性。两个质数必须来自密码学安全的随机数生成器,不能用时间种子、固定种子,更不能拿同一个 p、q 反复生成密钥。曾经出现过的安全事件里,大量公钥出现共享质因子,根因就是随机源质量不行。

其次是长度分配。p 和 q 的二进制位长要接近,不要让其中一个特别小,否则攻击者可以先做小因子试除,很快拆掉一半的 n。另一方面,p 和 q 也不该选得太近,太近会让平方差分解法有机可乘。合格的实现会同时规避这两端。

再就是公开指数 e。常见值 65537 是 2¹⁶+1,加密时只需要 17 次平方和少量乘法,比随机大质数当 e 快得多。历史上也有人用过更小的 e 如 3,运算更快,但各种小指数攻击也随之而来,现代实现基本都统一到 65537。

最后,私钥 d 不能太小。若 d 远小于 n,攻击者可以通过连分数等数学技巧还原私钥。正规实现会校验 d 的长度,普通用户只要不自己拼接参数,基本不用管这些细节。

4. RSA的时间复杂度分析

4.1 大整数运算的复杂度基元

要分析RSA的复杂度,得先定一个度量单位。最简单的方式是看 n 的二进制位数 k,也就是密钥长度。那么一次大整数乘法,用最基本的“逐位乘再累加”的学校算法,时间复杂度是 O(k²) 次位运算。虽然现代大数库会用更快的分治乘法,但常数阶优化不改变大O结论。

模运算也不能直接免费。传统做法是先乘出结果再除以模数取余,除法同样不便宜。工程优化会用特殊构造的模约减方案,把除法替换成乘法、移位和加法,但整体仍然落在“每次模乘 O(k²)”这个量级上。为了把“模乘次数”和“单次模乘成本”分开讨论,下面先数次数,再乘上 O(k²)。

4.2 快速模幂:加解密为什么快

如果老老实实把 m 自乘 e 次再取模,那 e 是 65537 也就算了,私钥 d 可是和 n 同量级的大数,会慢到没法用。所以实际用的是“平方-乘”快速模幂算法,步骤很简单:把指数写成二进制,从左往右处理,每个比特先平方一次,如果当前比特是 1,就再额外乘一次。

设指数的二进制位数为 t,平方的次数就是 t,乘法的次数平均约为 t/2,所以整体需要约 1.5t 次模乘。每次模乘 O(k²),总位运算复杂度就是:

O(k² × t)

当指数是私钥 d,t 与 k 同量级,复杂度就是 O(k³)。这个结果说明,RSA 加解密不是指数级,而是多项式级。密钥长度从 2048 加到 4096,按纯位操作来看成本并不是指数爆炸,这也是它能作为常规加密算法跑普通 CPU 的原因。

加密方向更占便宜。e=65537 的二进制位数只有 17,绝大部分位是 0,实际只需要约 18 次模乘。而解密指数 d 有 2048 位,平均要两千多次模乘。所以同样一个密钥,公钥加密通常比私钥解密快几十到上百倍,直觉上完全合理。

4.3 密钥生成的时间开销

密钥生成的主要成本在找两个大质数。给定一个 k 位随机奇整数,它是质数的概率大概是 O(1/k)。这意味着想找到一个可靠质数,平均要试探 O(k) 个候选数。每个候选数要用随机基做若干轮概率素性检测,每轮检测本质上是一次模幂运算,复杂度 O(k³)。综合下来,生成一个质数的期望开销约 O(k⁴) 位操作,生成两个质数后仍维持同一量级。

这个结论在工程上的感受是:2048 位密钥生成通常在几十毫秒到几百毫秒之间,属于“可以接受但不能频繁做”的范畴。所以系统里一般不会每次会话现生成密钥,而是提前生成好,或者用长期密钥配合临时会话密钥混合方案。

4.4 攻击侧的复杂度:为什么密钥长度得往上加

RSA 的安全性对应因式分解的困难程度。当前最强的通用分解方法,复杂度大致是:

exp(C × (ln n)^(1/3) × (ln ln n)^(2/3))

其中 C 是某个常数。它比任何多项式都快不了多少,但又不是简单指数爆炸,密码学界叫它“亚指数复杂度”。它的特点是:密钥长度每增加一点点,分解难度就跳一大截。1024 位在十年多年前还算常用,到今天已经明确不够安全,行业通行基线早就推到了 2048 位以上。

从复杂度曲线可以直观理解为什么“2048→3072”看似只是多了三分之一长度,安全强度却翻了不是一倍两倍。攻击者要想跟上,就得付出指数级增长的算力,这就是RSA仍然可信赖的根本支撑。

密钥长度通用判断典型适用场景
1024 位已不适合新系统只建议用于验证旧数据
2048 位当前常见基线一般互联网业务安全传输
3072 位更高安全余量高价值长期数据保护
4096 位保守高配对性能不敏感的强安全场景

要注意,表格里的“通用判断”来自我的工程习惯,不是某个标准机构的规定。具体选多长,还应结合性能预算和设备能力综合权衡。

4.5 性能与安全的实际权衡

从复杂度出发做工程决策时,要分清三类操作的成本。RSA公钥加密最轻,因为指数 e 短;私钥解密和签名最重,因为指数 d 长,但可以用中国剩余定理把一次大模幂拆成两个短模幂,速度通常能提升大约四倍;密钥生成最重,属于低频但高成本的初始化操作。

所以一个合理系统的画像是:用非对称加密只保护一把很短的对称密钥,正文交给对称加密;签名只对哈希摘要做;密钥生成被设计成一次性或低频动作。这样,RSA慢的特点就被隔离在系统薄薄的一层,整体性能不会因此崩掉。

5. 常见问题与避坑记录

5.1 为什么加密比解密快

很多人第一次压测时会惊到:同一台机器,RSA加密跑了上万次还没怎么发热,解密却肉眼可见地慢。原因在前面复杂度部分已经明说了,不是库的问题,是指数长度的差异。65537 的二进制只有 17 位,而 2048 位私钥 d 的二进制是全长的,模乘次数差出一个数量级。

如果私钥操作实在拖后腿,第一选择是打开中国剩余定理优化。原理是把解密过程拆分到 p 和 q 两个模数上分别计算,再用同余性质拼回结果。因为 p、q 只有 n 的一半长,单次模乘成本降到四分之一,总提速非常明显。成熟密码库默认会做,但如果你是在某个嵌入式设备上手工移植,要确认这个开关确实开了。

5.2 “明文过长”异常怎么处理

本地验证明文加密时,最容易撞上的异常就是输入太长被拒。比如 2048 位密钥最多只能放两百字节不到的明文,你却试图把一整条 JSON 报文塞进去,那必然报错。这不是密钥不对,也不是库坏了,而是RSA本身的设计就只适合加密小消息。

正解是分层。把 JSON 报文用对称密钥加密成密文,再用RSA加密这把对称密钥,两边组合传给接收方。接收方先解出对称密钥,再用它解正文。这种方式能让任意长数据都能跑,同时性能还很稳。

5.3 求私钥时算出 d 不存在

自己推数学式时,偶尔会遇到扩展辗转相除的结果不是 1,而是别的公因子。这说明 e 和 φ(n) 不互质,满足 e×d ≡ 1 mod φ(n) 的 d 压根不存在。常见诱因是随机选 e 时没有检查 gcd,或者 p、q 中某一个等于 e 的因子,导致 φ(n) 被 e 整除。

解决办法很直接:重新生成一个 e,或者重新选择 p、q 后再次检查。这也是为什么工程上固定用 65537 而不是随便选一个大数——可以少踩一次互质检查的坑。当然,正规库生成密钥时已经处理了这一步,你手推小数字例子时遇到这种情况,先回去查 p、q 和 e 的选择范围。

5.4 使用现成密码库时的小心点

手写RSA可以用来理解原理,但绝不能代替库。我在实际项目中见过让人后背发凉的“自实现流程”:随机数来源不对、填充被省略、私钥格式拼错、异常分支直接把密文返回明文。这些坑在教科书示例里不会出现,一旦上线就全是安全漏洞。

用库的时候也要保持清醒。要确认填充模式是 OAEP 而不是没有填充,要确认随机数源是系统级安全随机源,要确认生成密钥时没有复用旧的 p、q。密钥轮换同样重要:不要一个 RSA 密钥用十年不动,至少在有怀疑时及时换掉。做安全开发,真正值钱的往往不是会写公式,而是知道哪里不能省、哪里不能偷懒。

最后再分享一个小技巧。无论公式推得多漂亮,我都建议先用手算级别的测试向量跑通一遍再上真实密钥。用 p=17、q=23 这种小参数生成一对密钥,加密一段短消息,再用私钥解回来,确认结果一致,然后再切到 2048 位。这样你既能验证自己对原理的理解,也能给库的调用方式做一个快速冒烟测试。踩过几次坑之后你就会发现,RSA 真正复杂的地方不在数学,而在每个容易被忽略的工程细节里,固定习惯比临时认真可靠得多。

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

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

立即咨询