宝塔拨测博客拨测技术博客
宝塔拨测

RSA 公钥加密还是私钥加密?2048 位密钥对怎么生成、方向别搞反

RSA 只有一个正确的用法方向:公钥加密、私钥解密,用来保密;私钥签名、公钥验签,用来证明身份。搞反方向不会「稍微不安全」,而是直接用不了——公钥是公开的,谁都能用它「加密」,所以「用公钥解密」这件事本身没有意义。

打开 RSA 密钥对生成,位数留默认的 2048,点「生成密钥对」,两个框立刻填满:

2048 位生成结果:左边是公钥 BEGIN PUBLIC KEY,右边是私钥 BEGIN RSA PRIVATE KEY

页面自己写明了范围和实现方式:密钥位数 256–16384、须为 8 的倍数,在浏览器本地用 node-forge 生成 PEM 格式的公私钥,密钥不上传到服务器。这一组 2048 位的结果是:公钥 451 字节(Base64 部分 7 行),私钥 1679 字节(Base64 部分 25 行)。

左框是公钥,右框是私钥,两种 PEM 头不是同一个格式

两个框长得几乎一样,都带一个绿色的「复制」按钮,但内容开头的标记不同,这是最容易复制错的地方:

位置 第一行 格式 2048 位实测大小
左框 -----BEGIN PUBLIC KEY----- X.509 SubjectPublicKeyInfo(SPKI) 451 字节
右框 -----BEGIN RSA PRIVATE KEY----- PKCS#1 1679 字节

公钥的 SPKI 结构里先声明算法是 rsaEncryption,再跟一段 BIT STRING 装模数和指数;私钥的 PKCS#1 则是把 RSA 的各个参数按顺序打包。两种都是 PEM(Base64 加首尾标记),但不是可以互换的同一格式——把右框内容当公钥贴给对接方,对方验签会一直失败;把左框当私钥,程序会直接报「不是私钥」。

用 OpenSSL 3.5.7 读这组实测密钥,格式是标准的:

openssl rsa -in same.priv.pem -noout -text | head -2
# Private-Key: (2048 bit, 2 primes)
# publicExponent: 65537 (0x10001)

openssl rsa -in same.priv.pem -check -noout
# RSA key ok

公开指数固定是 65537(0x10001),这是通行默认值:比 e=3 这类小指数更稳妥,验签的模幂运算又不至于变慢。

私钥比公钥长 3.7 倍,因为它把公钥装在里面

公钥只有两个数:模数 n 和公开指数 e。私钥除了这两个,还带私钥指数 d、两个大素数 p 和 q,以及 CRT 加速用的 dP、dQ、qInv。openssl rsa -text 打出来的参数名就是这八个:

modulus  publicExponent  privateExponent  prime1  prime2  exponent1  exponent2  coefficient

这意味着私钥文件本身就包含公钥。实测验证:拿工具右框的私钥导出公钥,和左框给的公钥文件逐字节比对,完全相同:

openssl rsa -in same.priv.pem -pubout -out pub2.pem
diff -q pub2.pem same.pub.pem && echo IDENTICAL
# IDENTICAL

这次比对用的就是同一屏上那两段文本:左边的公钥和右边的私钥本身就是配对的,不是两次生成拼出来的两个文件。

反过来不成立:公钥推不出私钥。所以保管责任全在私钥那一侧——泄露私钥等于把签名能力和解密能力一起交出去,公钥泄露则无所谓。

位数怎么选:2048 起步,别用「能生成」的下限

位数决定了模数长度,也决定了生成耗时和文件体积。同一台机器、同一个工具,重复跑多次的结果:

密钥位数 生成耗时(多次实测) 公钥大小 私钥大小
1024 292 / 224 / 173 ms 272 字节 887 字节
2048 594 ms / 726 ms / 3.9 s 451 字节 1679 字节
4096 11.8 s / 29.1 s 800 字节 3243 字节

2048 位约等于 112 bit 安全强度,是现在 TLS 证书体系的 RSA 下限,也是绝大多数接口签名、自签测试的默认选择。3072 和 4096 位留给有效期更长、合规要求更严的场景。1024 位及以下不要再用于任何新系统。

工具的范围写着 256–16384,但能生成不等于能用。实测 256 位密钥用 SHA-256 签名,OpenSSL 直接拒绝:

openssl dgst -sha256 -sign rsa-256.priv.pem -out s.bin msg.txt
# Error signing data
# error:02000070:rsa routines:RSA_sign:digest too big for rsa key

原因很直观:PKCS#1 v1.5 签名要先拼一段固定填充,SHA-256 摘要是 32 字节,加填充最少要 51 字节,而 256 位模数只有 32 字节,连摘要本身都放不下。512 位能签能验(实测 Verified OK),但 512 位的强度早就不在任何现代标准里,只适合当玩具。

位数越大越慢,而且不是线性变慢。4096 位一次要十几秒到半分钟,因为 node-forge 是纯 JavaScript 在浏览器主线程里找大素数——这段时间页面是卡住的,不是没反应。真要批量生成密钥,别在网页上循环点,用服务器上的 openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048。

1024 位的结果:公钥 272 字节、私钥 887 字节,明显比 2048 位短

4096 位的结果:两个框都长了一大截,私钥 3243 字节

四个方向只有两个能用,另外两个会报错

用同一对实测密钥跑四个方向,结论很清楚:

# 1) 私钥签名,公钥验签 —— 证明「是我发的」
openssl dgst -sha256 -sign same.priv.pem -out s.bin msg.txt
openssl dgst -sha256 -verify same.pub.pem -signature s.bin msg.txt
# Verified OK

# 2) 公钥加密,私钥解密 —— 保证「只有我能看」
openssl pkeyutl -encrypt -pubin -inkey same.pub.pem -in msg.txt -out e.bin
openssl pkeyutl -decrypt -inkey same.priv.pem -in e.bin
# hello

# 3) 用公钥解密 —— 报错
openssl pkeyutl -decrypt -pubin -inkey same.pub.pem -in e.bin
# A private key is needed for this operation
# pkeyutl: Error loading key

# 4) 用私钥文件「加密」 —— 居然不报错
openssl pkeyutl -encrypt -inkey same.priv.pem -in msg.txt -out e2.bin
# rc=0

第 4 条最容易误导人:拿私钥文件当 -inkey 去加密,OpenSSL 3 不报错。不是因为它支持「私钥加密」,而是私钥文件里本来就带着 n 和 e,它内部取公钥部分去加密了。解这个结果仍然需要私钥。看到「私钥加密能跑通」就以为方向可以互换,是最常见的误读。

另外 RSA 加密只能加密很短的数据:2048 位模数是 256 字节,PKCS#1 v1.5 填充至少要占 11 字节,一次最多加密 245 字节。所以实际用法是拿它加密一把随机的对称密钥,再用 AES 加密正文——这也是上一篇文章里 AES 加密结果每次都不同 那套流程存在的原因。

拿到密钥之后的三件事

一、确认格式对不对。 这个工具给的是 PKCS#1 私钥(BEGIN RSA PRIVATE KEY),Nginx 的 ssl_certificate_key、OpenSSL 命令都能直接读。对接 Java、Go 或部分 SDK 时它们常要 PKCS#8:

openssl pkcs8 -topk8 -nocrypt -in same.priv.pem -out priv8.pem
head -1 priv8.pem
# -----BEGIN PRIVATE KEY-----

2048 位实测体积从 1679 字节变成 1704 字节,多了 25 字节的结构开销,密钥内容不变。

二、权限和存放。 私钥写成 chmod 600,属主只给运行服务的账号;不要提交进 git(.gitignore 里加 *.pem *.key),不要贴进工单、聊天记录和截图。工具页面上那句话不是客套:密钥不会上传,但复制出去之后就归你负责了。

三、不要一对密钥打通所有事。 用于 TLS 的、用于接口签名的、用于 SSH 的,各自生成一对。一对密钥在多个系统里复用,轮换时就得同时改所有地方,泄漏时的爆炸半径也大一圈。

还要分清「密钥对」和「证书」:这个工具只产出密钥对,不签发证书。要把它用到 HTTPS 上,得先用私钥生成 CSR 交给 CA 校验域名签发;反过来,acme.sh、面板里申请 Let’s Encrypt 这类流程会自己生成密钥,不需要你手动贴。日常更常见的用途还是自用场景:接口签名、配置加密、自签测试。

至于「在线生成密钥安不安全」,页面写明所有计算在浏览器本地完成、密钥不上传。本次生成时抓到的 POST 请求只有统计类接口(Google Analytics、/api/v3/usage-events、RUM 测速上报),没有看到提交密钥内容的请求。如果连埋点都不想有,可以断网后再点生成,或者干脆在服务器上用 openssl genpkey 生成后再把公钥带出来。

一句话收尾

公钥发出去,私钥锁起来;公钥加密、私钥签名。位数从 2048 起,256 位那种「能生成」的下限不要碰——实测连一次 SHA-256 签名都装不下。

常见问题

RSA 到底是公钥加密还是私钥加密?

两个方向都成立,但用途不同:公钥加密、私钥解密,解决「内容只有对方能看」;私钥签名、公钥验签,解决「这段内容确实出自持私钥的人」。日常说的「用私钥加密」通常是签名,因为私钥不能给出去,而公钥本来就是公开的,用公钥加密才有意义。

公钥和私钥哪个可以发给别人?

只发公钥。公钥(-----BEGIN PUBLIC KEY----- 开头)可以贴到对接文档、发给合作方、写进代码仓库;私钥(-----BEGIN RSA PRIVATE KEY----- 开头)只能留在自己机器上,权限 600,不进 git、不进聊天窗口、不贴工单。私钥一旦外泄,唯一正确做法是作废并重新生成一对,再让对方换公钥。

为什么私钥文件比公钥大那么多?

因为私钥里包含了公钥的全部内容,还多了推导用的参数。2048 位实测:公钥 451 字节(只有模数 n 和公开指数 e),私钥 1679 字节(n、e、私钥指数 d、两个大素数 p 和 q、以及 dP、dQ、qInv 三个加速参数)。所以用 openssl rsa -in priv.pem -pubout 能从私钥直接导出公钥,实测与工具左边的公钥文件逐字节相同。

RSA 密钥 2048 位够用吗,什么时候要 4096?

2048 位是现在的起点,约等于 112 bit 安全强度,TLS 证书体系也要求 RSA 不低于 2048 位。一般业务、接口签名、自签测试用 2048 位;需要更长有效期或合规要求时用 3072 或 4096 位。1024 位和更小的一律不要用于新系统——本机实测 256 位密钥连 SHA-256 签名都算不出来,OpenSSL 直接报 digest too big for rsa key。

为什么生成 4096 位密钥要等十几秒,页面还卡住?

这个工具用的是 node-forge,纯 JavaScript 在浏览器主线程里找大素数。位数翻倍,运算量涨得远不止一倍:本机实测 1024 位约 0.2 秒、2048 位约 0.6 到 4 秒、4096 位 12 到 29 秒。生成过程中页面无法响应,不是卡死,等它算完即可;不要连点按钮,否则会重复生成。

工具生成的私钥能直接给 Nginx 用吗,要不要转 PKCS#8?

PKCS#1 格式(BEGIN RSA PRIVATE KEY)Nginx、Apache、OpenSSL 都能直接读,不用转。但如果对接的是 Java、Go、部分 SDK,它们常要求 PKCS#8,用 openssl pkcs8 -topk8 -nocrypt -in priv.pem -out priv8.pem 转一下,文件头会从 BEGIN RSA PRIVATE KEY 变成 BEGIN PRIVATE KEY,2048 位实测体积从 1679 字节变成 1704 字节。

这个工具生成的密钥能拿去申请 HTTPS 证书吗?

工具只生成密钥对,不签发证书。想用到 HTTPS 上,要先拿私钥生成 CSR(openssl req -new -key priv.pem -out req.csr)交给 CA 校验域名,签发回来的证书再和这把私钥配对;面板里申请免费证书(ACME)这类流程则会自己生成密钥,不需要手动贴。日常更常见的用法是自用:接口签名、配置加密、自签测试。

RSA非对称加密公钥私钥PEMOpenSSL宝塔拨测