为什么 RSA 需要填充?

RSA 算法本身是确定性的:相同的明文总是产生相同的密文。这带来几个安全问题:

  1. 确定性攻击:攻击者可以识别重复的密文
  2. 小指数攻击:当明文很小且公钥指数 e 也小时,可直接开 e 次方解密
  3. 可乘性:密文相乘对应明文相乘,可用于选择密文攻击

填充方案通过添加随机性和结构,解决这些问题。

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 更安全,新系统应优先选择

参考来源