为什么 RSA 需要填充?#
RSA 算法本身是确定性的:相同的明文总是产生相同的密文。这带来几个安全问题:
- 确定性攻击:攻击者可以识别重复的密文
- 小指数攻击:当明文很小且公钥指数 e 也小时,可直接开 e 次方解密
- 可乘性:密文相乘对应明文相乘,可用于选择密文攻击
填充方案通过添加随机性和结构,解决这些问题。
PKCS#1 v1.5:传统方案#
PKCS#1 v1.5 是最早的 RSA 填充标准,分为两种用途:
加密填充#
1
|
0x00 || 0x02 || 随机字节 || 0x00 || 明文
|
签名填充#
1
|
0x00 || 0x01 || 0xFF...FF || 0x00 || ASN.1(哈希算法标识) || 哈希值
|
安全缺陷#
PKCS#1 v1.5 存在已知漏洞:
- Bleichenbacher 攻击(1998):利用 PKCS#1 v1.5 加密填充的格式错误信息,进行选择密文攻击,可解密任意密文
- Padding Oracle 攻击:服务器返回填充错误时,可逐步恢复明文
结论:PKCS#1 v1.5 加密已不安全,应迁移到 OAEP。
OAEP:最优非对称加密填充#
OAEP(Optimal Asymmetric Encryption Padding)是 RSA 加密的推荐方案,在 PKCS#1 v2.0 中引入。
工作原理#
OAEP 使用两个哈希函数 G 和 H,通过 Feistel 网络结构混合明文和随机种子:
1
2
3
4
5
6
|
明文 || 0...0 → XOR ← G(随机种子)
↓
X = 明文 ⊕ G(种子)
Y = 种子 ⊕ H(X)
↓
RSA(X || Y)
|
OpenSSL 命令:OAEP 加密#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
# 生成 RSA 密钥对
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem
# 使用 OAEP 加密(推荐)
echo "Secret message" | openssl pkeyutl -encrypt -pubin -inkey public.pem \
-pkeyopt rsa_padding_mode:oaep \
-pkeyopt rsa_oaep_md:sha256 \
-out encrypted.bin
# OAEP 解密
openssl pkeyutl -decrypt -inkey private.pem \
-in encrypted.bin \
-pkeyopt rsa_padding_mode:oaep \
-pkeyopt rsa_oaep_md:sha256
|
参数说明#
| 参数 |
说明 |
推荐值 |
rsa_oaep_md |
OAEP 内部哈希算法 |
sha256 |
rsa_mgf1_md |
MGF1 掩码生成函数的哈希 |
sha256 |
PSS:概率签名方案#
PSS(Probabilistic Signature Scheme)是 RSA 签名的推荐方案,在 PKCS#1 v2.1 中引入。
与 PKCS#1 v1.5 签名的对比#
| 特性 |
PKCS#1 v1.5 |
PSS |
| 随机性 |
无(确定性签名) |
有(概率签名) |
| 安全证明 |
无严格证明 |
可证明安全 |
| 抗攻击 |
存在潜在风险 |
安全 |
OpenSSL 命令:PSS 签名#
1
2
3
4
5
6
7
8
9
10
11
12
13
|
# 使用 PSS 签名(推荐)
echo -n "Message to sign" | openssl dgst -sha256 \
-sign private.pem \
-sigopt rsa_padding_mode:pss \
-sigopt rsa_pss_saltlen:-1 \
-out signature.bin
# PSS 验证
echo -n "Message to sign" | openssl dgst -sha256 \
-verify public.pem \
-sigopt rsa_padding_mode:pss \
-sigopt rsa_pss_saltlen:-1 \
-signature signature.bin
|
盐值长度说明#
| 值 |
含义 |
-1 |
使用哈希长度作为盐值长度(推荐) |
-2 |
使用最大可能的盐值长度 |
>= 0 |
指定具体盐值字节数 |
实战:对比两种签名#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
# 同一消息,两种签名方式
echo -n "Test message" > msg.txt
# PKCS#1 v1.5 签名(传统)
openssl dgst -sha256 -sign private.pem -out sig_pkcs1.bin msg.txt
# PSS 签名(推荐)
openssl dgst -sha256 -sign private.pem \
-sigopt rsa_padding_mode:pss \
-sigopt rsa_pss_saltlen:-1 \
-out sig_pss.bin msg.txt
# 验证 PKCS#1 v1.5 签名
openssl dgst -sha256 -verify public.pem -signature sig_pkcs1.bin msg.txt
# 验证 PSS 签名
openssl dgst -sha256 -verify public.pem \
-sigopt rsa_padding_mode:pss \
-sigopt rsa_pss_saltlen:-1 \
-signature sig_pss.bin msg.txt
|
选择建议#
| 场景 |
推荐方案 |
| RSA 加密 |
OAEP(禁用 PKCS#1 v1.5) |
| RSA 签名 |
PSS(优先于 PKCS#1 v1.5) |
| 兼容旧系统 |
PKCS#1 v1.5(仅签名,不用于加密) |
- PKCS#1 v1.5 加密不安全:存在 Bleichenbacher 攻击,必须迁移到 OAEP
- OAEP 是加密标准:可证明安全,TLS 1.3 仅支持 OAEP
- PSS 是签名推荐:概率签名,可证明安全
- PKCS#1 v1.5 签名仍可用:但 PSS 更安全,新系统应优先选择
参考来源#