一、两种用法,方向相反
| 用途 | 谁加密/签名 | 谁解密/验签 | 解决什么 |
|---|---|---|---|
| 加密 | 公钥 | 私钥 | 只有私钥持有者能看内容 |
| 签名 | 私钥 | 公钥 | 证明内容确实来自私钥持有者 |
公钥可以公开,私钥必须保密。加密用对方的公钥,签名用自已的私钥——记不住方向时,想「谁能解开、我想让谁相信」即可。
二、密钥长度怎么选
- 2048 位:当前通用最低标准,兼容性最好。
- 4096 位:更安全,但签名/解密慢数倍,握手开销也更大。
- 1024 位:已不安全,不要再新生成。
RSA 的安全性与大数分解难度相关,而 ECC(如 P-256)在更短密钥下提供同等安全强度,所以新系统里 ECDSA/Ed25519 越来越常见,尤其是证书和 SSH 密钥。
三、填充方式很关键
RSA 本身只能加密比密钥短的数据,且必须配填充:
- PKCS#1 v1.5:老标准,广泛兼容,但存在 Bleichenbacher 类攻击风险,新代码不建议用于加密。
- OAEP:推荐用于加密,基于 SHA-2,安全性好。
- PSS:推荐用于签名,比 v1.5 签名更安全。
对接时经常遇到「Java 默认 v1.5、Node 默认 OAEP」,两边不匹配就解不开。这不是密钥问题,是填充参数不一致。
四、长度限制与混合加密
RSA-2048 最多只能加密约 190 字节(取决于填充),4096 位约 446 字节。所以 RSA 从来不直接加密大文件,标准做法是混合加密:
- 随机生成一个 AES 密钥(32 字节)。
- 用 AES-GCM 加密真正的数据。
- 用对方 RSA 公钥加密这个 AES 密钥。
- 把「RSA 密文 + AES 密文 + IV + tag」一起发出去。
这就是 TLS、PGP、各类信封加密的基本结构。
五、签名流程与常见误区
签名:signature = RSA-PSS(SHA256(消息), 私钥)
验签:RSA-PSS-verify(SHA256(消息), signature, 公钥) == true
- 先哈希再签名:RSA 签名实际签的是摘要,不要直接对大文件做 RSA 运算。
- 验签必须用原始字节:JSON 重新序列化后空白变了,验签就会失败。
- 不要把「加密」当「签名」:用私钥加密一段内容当签名,虽然某些实现能验,但语义错误且不安全,应使用标准签名方案。
- 私钥不要进仓库:
.pem文件要加进.gitignore,生产环境用密钥管理服务。
六、格式问题:PEM、DER、PKCS#8
同一个密钥有几种外壳,最容易搞混:
- PEM:Base64 文本,带
-----BEGIN PUBLIC KEY-----头尾,适合配置文件。 - DER:二进制,适合程序内部与部分协议。
- PKCS#1(
BEGIN RSA PRIVATE KEY)与 PKCS#8(BEGIN PRIVATE KEY):私钥的两种封装,很多库只认其中一种,需要转换:
openssl pkcs8 -topk8 -inform PEM -in old.pem -outform PEM -nocrypt -out new.pem
- 公钥也有
BEGIN PUBLIC KEY(SPKI)和BEGIN RSA PUBLIC KEY(PKCS#1)两种,Java 与 Node 常因此对不上。
七、调试建议
联调失败时按顺序排查:密钥是不是同一对 → 填充方式(OAEP/v1.5、PSS)是否一致 → 摘要算法(SHA-1/SHA-256)是否一致 → 数据是原文还是 Base64 后的文本 → 字符编码是否都是 UTF-8。
需要快速生成一对测试密钥、跑通加解密流程,用 RSA 加解密工具;真实业务的密钥请在服务端或本地用 OpenSSL 生成,不要用在线服务生成生产密钥。
小结
公钥加密、私钥签名;2048 位起步;加密用 OAEP、签名用 PSS;RSA 只加密密钥,数据交给 AES;PEM/DER/PKCS#8 格式不一致是最常见的「密钥明明对却解不开」。