RSA 密钥对生成:从数学原理到实战操作
什么是 RSA 密钥对生成?
RSA 密钥对生成,说白了就是制造两把数学上互相关联的”数字钥匙”——一把公开(公钥),一把保密(私钥)。公钥加密的东西只有私钥能解开,私钥签名的东西任何人都能用公钥验证。
这两把钥匙是怎么来的?不是随便拍脑袋生出来的,背后是一整套严格的数学流程。先来看最直接的方式——用 openssl 在命令行生成一对 RSA 密钥,拿到第一手直观感受:
# 生成 2048 位 RSA 私钥
openssl genrsa -out private.pem 2048
# 从私钥中提取公钥
openssl rsa -in private.pem -pubout -out public.pem
执行完这两条命令,你手头会多出两个文件。private.pem 长这样:
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC...
-----END PRIVATE KEY-----
而 public.pem 则短得多:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----
第一次看到这两串东西的时候,我完全是懵的——“这不就是两坨乱码吗?它们之间到底有什么关系?“后来一点点啃了数论的资料才明白,这”乱码”其实是两个大素数和一堆推导出来的参数用 ASN.1 结构编码之后的结果。
把 RSA 密钥生成类比成现实世界里的场景:你订制了一把特殊的双面锁——一面可以公开给人用来锁箱子,但只有你的钥匙能开;另一面你能用它在文件上盖”章”,任何人都能拿你的公开信息来对比、证明这个章确实是你盖的。
RSA 密钥生成的工作原理
RSA 的安全性建立在大整数分解难题上——两个大素数相乘很容易,但给你乘积,让你反推出原来那两个素数,在计算上不可行。整个密钥生成过程就是在”搭”这个难题。
第一步:选两个大素数 p 和 q
这不是随便挑两个素数就行。openssl 内部用 Miller-Rabin 概率素性测试来找大素数。这个过程大致是这样的:随机生成一个 1024 位左右的奇数,跑若干轮 Miller-Rabin 测试,如果都通过,就认定它是素数。
我之前在自己的笔记本上跑过一次 openssl genrsa 4096,特意盯着 CPU 看了几分钟——找素数这一步占了整个密钥生成时间的 90% 以上。毕竟在茫茫数海里找到两个 2048 位级别的素数,即使有概率算法加持,计算量也不小。
第二步:计算模数 n 和欧拉函数 φ(n)
找到 p 和 q 后,乘法就很快了:
n = p × q
φ(n) = (p - 1) × (q - 1)
n 是模数,也是公钥和私钥共同依赖的核心参数。它的位数决定了 RSA 的安全强度——2048 位的 n 意味着你有 2048 位的安全边界。φ(n) 则是私钥推导的关键中间量。这里有一个容易踩的坑:如果 p 和 q 太接近(差值太小),攻击者可以从 n 的平方根附近开始搜索素数因子,用 Fermat 因式分解法快速破解。openssl 在生成素数时会检查这一点,确保两个素数的位差足够大。
第三步:选择公钥指数 e
实际应用中,e 通常不是随机选的,而是直接用一个固定值——65537。
为什么是 65537?三个原因:第一,它是素数(确切说是费马素数 F4 = 2^16 + 1);第二,它的二进制表示里只有两个 1(10000000000000001),做模幂运算时乘法次数极少,验证签名飞快;第三,它够大,能抵抗低指数攻击,又不像随机大 e 那样拖慢加密速度。你偶尔也会见到 e=3 的历史遗留密钥,但那是几十年前为了极致性能做的妥协,现在基本不再推荐了。
公钥就是 (n, e) 的组合。
第四步:推导私钥指数 d
d 是 e 关于模 φ(n) 的乘法逆元:
d × e ≡ 1 (mod φ(n))
这个计算依赖扩展欧几里得算法,对于计算机来说几乎是瞬间完成。私钥就是 (n, d) 的组合。不过实际上,openssl 生成的私钥文件不只有 n 和 d,它还额外保存了 p、q 和几个预计算参数(dp、dq、qinv),这样在解密和签名时可以用中国剩余定理(CRT)加速——解密速度大概能提升 4 倍。
第五步:编码输出
参数都算好了,但你不能直接把一堆大整数裸存成文件,得有个大家都能读的标准格式。openssl 默认输出的是 PEM 格式——本质上是把 DER 二进制数据 Base64 编码之后,加上 -----BEGIN/END----- 头尾标记。如果你想看真实的结构,可以用:
openssl rsa -in private.pem -text -noout
这条命令会把 p、q、n、e、d 等所有参数都以十六进制打印出来,你会发现它们每一个都是几百字节的大数。
核心特性
| 特性 | 说明 |
|---|---|
| 密钥长度 | 常用 2048 位和 4096 位,对应模数 n 的位宽 |
| 公钥组成 | (n, e),通常 e 固定为 65537 |
| 私钥组成 | (n, d) 加 CRT 加速参数 (p, q, dp, dq, qinv) |
| 编码格式 | PEM(文本,带头尾标记)和 DER(纯二进制) |
| 安全性基础 | 大整数分解难题(目前无法在合理时间内分解 2048 位以上的 n) |
| 性能特点 | 加密和验证签名快(e 小),解密和签名慢(d 大,但有 CRT 加速) |
密钥长度这件事值得单独多说两句。2015 年 NIST 正式停止推荐 1024 位 RSA,现在的最低标准是 2048 位。到了 2030 年之后,这个标准大概率会上调到 3072 位。至于 4096 位——坦白讲,对绝大多数系统来说,它带来的安全边际提升远小于计算开销的增加。我之前测试过,4096 位的 TLS 握手比 2048 位慢了将近 4 倍。
实际应用场景
1. TLS/HTTPS 证书
这是 RSA 密钥最常见的归宿。你申请 SSL 证书时,第一步就是生成一个 RSA 密钥对(或 ECC 密钥对)。CSR(证书签名请求)里嵌着你的公钥,CA 用它的私钥给你的公钥”背书”——签好之后就成了一张合法的证书。
不过话说回来,Google 和 Cloudflare 这几年在大力推 ECC(椭圆曲线)证书,因为 ECDSA 的签名和验证都比 RSA 快,密钥也更小。但 RSA 在兼容性上仍然无可替代,老旧设备上几乎只认 RSA。
2. SSH 免密登录
ssh-keygen -t rsa -b 4096 -C "[email protected]"
这条命令你可能跑过无数遍了。它生成 ~/.ssh/id_rsa(私钥)和 ~/.ssh/id_rsa.pub(公钥)。你把这个公钥丢到服务器的 ~/.ssh/authorized_keys 里,之后登录服务器时,服务端用你的公钥加密一个随机数发给你,你用私钥解密后回传,完成身份验证——全程不需要输密码。
老实说,现在我也已经转向 ed25519 了——它比 RSA 快得多,密钥又短,安全性也至少和 RSA 3072 相当。但如果你要兼容一些旧系统(比如某些嵌入式 Linux),RSA 还是绕不开的选项。
3. 代码签名
移动应用、桌面软件、驱动程序的数字签名,很多都在用 RSA。开发者在本地或者 CI 环境里持有私钥,构建产物的哈希值用私钥签名。用户下载安装时,操作系统用内置的公钥验证签名——中间如果有人篡改安装包,签名就对不上了。
不过私钥泄漏在这类场景下是灾难性的。2015 年那次 Android 平台签名密钥泄漏事件导致数万个应用不得不更换签名,至今还是移动安全教材里的反面案例。
4. JWT 的 RS256 签名算法
JWT 除了用 HMAC(HS256)签名,还支持 RSA 签名——也就是 RS256、RS384、RS512。跟 HMAC 的区别在于:HMAC 的签名和验证用的是同一把密钥(对称),而 RS256 是非对称的。你的认证服务持有私钥签发 Token,其他微服务只需要公钥就能验证——不需要共享任何秘密。
这个场景里 RSA 的优势特别明显:私钥永远不需要离开认证服务,公钥可以随意分发。即使某个微服务被攻破了,攻击者拿到的也只是公钥,没法伪造新 Token。
5. PGP/GPG 邮件加密
PGP 的核心也是非对称加密。你生成一对 RSA 密钥后,把公钥上传到密钥服务器或者直接发给联系人。别人用你的公钥加密邮件内容,只有你的私钥能解密。反过来,你用私钥签名邮件,收件人用你的公钥确认邮件确实来自你而且没有被篡改。
常见误区
误区一:4096 位的密钥一定比 2048 位更安全
字面上是的——4096 位对抗暴力分解的确更强。但安全不是一条单变量的函数。密钥越长,生成越慢、签名越慢、TLS 握手越慢。如果你的私钥存在一个有漏洞的系统里、或者备份不当被泄露了,你就是 8192 位也救不了你。大多数场景下 2048 位的安全边际已经完全够用,省下来的计算资源可以投入到密钥管理上——后者才是一般系统的真正短板。
误区二:PEM 和 DER 是两种不同的密钥
我之前在配置 Nginx HTTPS 的时候就栽在这个误区里。我先用 openssl 生成了一个 PEM 格式的私钥,后来从某 CA 那里拿回来的证书是 DER 格式的,我以为它们不兼容,折腾了半天格式转换。实际上 PEM 和 DER 描述的是同一把密钥,只是编码方式不同——DER 是 ASN.1 结构的二进制序列化,PEM 是 DER 的 Base64 包装。你随时可以在两者之间互转:
# PEM 转 DER
openssl rsa -in private.pem -outform DER -out private.der
# DER 转 PEM
openssl rsa -in private.der -inform DER -outform PEM -out private.pem
误区三:公钥可以直接从私钥”推导”出来,所以公钥不用单独保存
严格来说,公钥的 (n, e) 确实是私钥文件的子集——openssl 私钥文件里本来就包含 n 和 e。说回正题,虽然你可以随时用 openssl rsa -pubout 从私钥提取公钥,但生产环境里绝对不应该把私钥当成公钥的替代品来用。私钥需要最高级别的保护,只放在极少数节点上。公钥应该独立分发、独立存储。你让所有微服务都拿着私钥只是为了从中提取公钥——这跟把保险柜钥匙复印几十份、分给所有员工一样荒谬。
RSA vs ECC vs Ed25519
| RSA | ECC (ECDSA) | Ed25519 | |
|---|---|---|---|
| 密钥长度 | 2048-4096 位 | 256 位 | 256 位 |
| 安全性等价 | ~112 位(2048) | ~128 位 | ~128 位 |
| 签名速度 | 慢(ms 级) | 快 | 极快 |
| 验证速度 | 快 | 中等 | 极快 |
| 兼容性 | 极广 | 较广 | 较新系统 |
| 密钥体积 | 大(几 KB) | 小(几百 B) | 极小(64 B) |
| 抗量子性 | 弱 | 弱 | 弱 |
RSA 的兼容性是它最大的护城河。你去翻任何一台还在用的服务器——哪怕是跑了十年的 CentOS 6——它都能处理 RSA。ECC 的普及度在快速增长,但遇到老旧系统还是可能掉链子。Ed25519 的速度和安全性都非常出色,OpenSSH 从 6.5 版本起就支持了,如果你的所有目标系统都比较新,它可能是更好的选择。
有一说一,这三种方案在可预见的量子计算威胁面前都是脆弱的。真正抗量子的是格密码(Lattice-based),NIST 已经在 2024 年标准化了 ML-KEM(FIPS 203)和 ML-DSA(FIPS 204),但那是另一个话题了。
常见问题
Q: 怎么判断一个现有私钥的位数?
openssl rsa -in private.pem -text -noout | head -5
输出的第一行就是私钥的位数。或者更直接地用 openssl rsa -in private.pem -check,它会告诉你密钥长度和结构是否完整。我通常会用后面这条命令,因为它在返回位数的同时还会做一次完整性检查,能发现 CRT 参数损坏这类隐藏问题。
Q: 私钥文件的后缀名是 .pem,但它真的是 PEM 格式吗?
不一定。.pem 只是一种约定,文件的实际编码取决于内容。你可以用文本编辑器打开看一眼——如果内容是以 -----BEGIN 开头、-----END 结尾的 Base64 文本,那就是 PEM 格式。如果是不可读的二进制,那大概率是 DER 格式只是被错误命名了。公私钥文件本质上就是 ASN.1 结构体的编码,格式头尾标记才是区分 PEM 和 DER 的真正依据,文件后缀仅供参考。
Q: 生成 RSA 密钥时能不能自己指定 p 和 q?
技术上可以,但不应该。openssl 的素数生成过程包含了各种安全检查(p 和 q 是否太接近、是否够大、是否通过了足够轮数的素性测试),你手动指定的话这些检查就绕开了。而且你自己选的”素数”可能根本就不是素数——除非你也有一套 Miller-Rabin 测试工具链。我在学习阶段出于好奇尝试过手工构造一个小 RSA 密钥对(用两个 8 位素数),结果在算 d 的时候搞错了一次模运算,整个密钥对全废了。机器不会犯这种算术错误。
Q: 密钥生成时能不能设置 e 为其他值?
openssl 默认用 65537,你也可以用 -F4(指定 65537,这是默认行为)或 -3(指定 e=3)来显式选择。但除非你有一个经过深思熟虑的理由(例如和某些古老系统的互操作),不要改 e。e=3 在一些特定条件下容易受到广播攻击(相同的明文发给多个接收方,配合 CRT 可以还原明文)。65537 是最稳妥的选择。
Q: 私钥文件丢了但公钥还在,能不能从公钥恢复私钥?
不能。如果能,RSA 的安全性就崩溃了。公钥只有 (n, e),要恢复私钥你必须从 n 反推出 p 和 q——这就是大整数分解问题,也是 RSA 整个算法赖以生存的数学难题。如果你真的丢了私钥,唯一的补救措施是:用原来的公钥撤销关联的证书(CRL),然后重新生成一对新密钥、申请新证书。这也是为什么密钥备份策略在实际运维中至关重要——有时候证书重签的流程成本远超你的预期。