跳到主要内容
ecdsa.com

RSA-PSS vs PKCS#1 v1.5:RSA 签名该用哪种填充

同样的 RSA 密钥,同样的大小,同样的速度。PSS 有安全性证明,也是 TLS 1.3 握手的强制要求;PKCS#1 v1.5 无处不在,只要验证方式正确,就不存在实际可行的攻破方法。

在 RSA-PSS 和 PKCS#1 v1.5 之间做选择,就是选择在 RSA 私钥运算之前如何编码哈希值。两者都出自 PKCS #1(RFC 8017),都获得 FIPS 186-5 批准,也都能使用同一个 RSA 密钥对。区别在于它们的安全性论证、确定性,以及签名者和验证者之间必须一致的参数。

概览

RSASSA-PSSRSASSA-PKCS1-v1_5
规范RFC 8017 §8.1,编码方式 EMSA-PSS §9.1RFC 8017 §8.2,编码方式 EMSA-PKCS1-v1_5 §9.2
编码加盐哈希,用 MGF1 掩码0x00 01 FF…FF 00 ‖ DigestInfo
随机化是(除非盐长度为 0)否:相同的密钥和消息产生相同的签名
安全性证明在随机预言模型下紧归约到 RSA 问题没有可比的证明;针对正确实现的验证者,不存在实际可行的攻击
需要约定的参数哈希、MGF 哈希、盐长度、尾部字段只有哈希
签名大小等于模数长度(RSA-2048 为 256 字节)相同
速度相同的 RSA 运算相同
JOSEPS256、PS384、PS512(盐 = 哈希长度)RS256、RS384、RS512
TLS 1.3使用 RSA 的 CertificateVerify 必须采用(rsa_pss_rsae_sha256 0x0804、rsa_pss_pss_sha256 0x0809)只用于证书内的签名(rsa_pkcs1_sha256 0x0401)
X.509id-RSASSA-PSS 1.2.840.113549.1.1.10,带参数sha256WithRSAEncryption 1.2.840.113549.1.1.11
FIPS 186-5 约束盐长度介于 0 和哈希长度之间§5.4(h) 中的 DigestInfo 检查

安全性差异

PKCS#1 v1.5 签名填充始于1991年。它是确定性的,也从未有过像 PSS 那样简洁的安全性证明,但三十年过去,针对会检查完整编码块的验证者,仍然不存在实际可行的伪造方法。现实中的攻破都源于实现缺陷:Bleichenbacher 在2006年提出的攻击,针对会忽略 DigestInfo 之后多余字节的解析器,伪造了公钥指数为 3 时的签名;同类错误的变体后来又在 NSS(BERserk,2014年)等库中重现。RFC 8017 §8.2.2 通过重新编码预期的块并逐字节比较,避免了这一整类问题,FIPS 186-5 §5.4(h) 则把这些检查写进了规范。

RSASSA-PSS(由 Bellare 和 Rogaway 提出,2002年在 PKCS #1 v2.1 中标准化)把随机盐与消息哈希一起做哈希,再用 MGF1 对结果做掩码。它的安全性在随机预言模型下可以紧归约到 RSA 问题;而且由于签名是随机化的,单个签名中出现故障或侧信道时泄露的信息更少。盐长度为零时,PSS 变为确定性签名,但在略有不同的假设下仍保有安全性证明。

不要把两者与 PKCS #1 v1.5 的加密填充混为一谈:后者确实已被填充预言攻击攻破(Bleichenbacher 1998年,ROBOT 2017年),并已从 TLS 1.3 中移除。签名填充和加密填充是共用一个名字的两种不同方案。

互操作性:盐长度陷阱

PSS 要求签名者和验证者就三个参数达成一致:哈希、MGF1 哈希和盐长度。JOSE 把三者都固定了(PS256 表示 SHA-256、MGF1-SHA-256 和 32 字节的盐),TLS 1.3 则要求盐长度等于哈希长度。其他场合就必须自己检查:OpenSSL 1.1.1 和 3.0 默认使用最大长度的盐签名(RSA-2048 搭配 SHA-256 时为 222 字节),而 OpenSSL 3.1 及更高版本默认使用 auto-digestmax,生成与摘要等长的盐。于是,配置了固定盐长度的验证者会拒绝另一代版本生成的签名。

下面的脚本用 OpenSSL 3.6.5 运行,展示了这两种行为。

OpenSSL:确定性与 PSS 盐长度检查
#!/bin/sh
dir=$(mktemp -d)
cd "$dir" || exit 1
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out key.pem 2>/dev/null
openssl pkey -in key.pem -pubout -out pub.pem
printf 'hello' > msg

openssl dgst -sha256 -sign key.pem -out v15a.sig msg
openssl dgst -sha256 -sign key.pem -out v15b.sig msg
cmp -s v15a.sig v15b.sig && echo "PKCS#1 v1.5: identical signatures" || echo "PKCS#1 v1.5: different signatures"

openssl dgst -sha256 -sign key.pem -sigopt rsa_padding_mode:pss -out pssa.sig msg
openssl dgst -sha256 -sign key.pem -sigopt rsa_padding_mode:pss -out pssb.sig msg
cmp -s pssa.sig pssb.sig && echo "PSS: identical signatures" || echo "PSS: different signatures"

for saltlen in digest max; do
  printf 'verify PSS with rsa_pss_saltlen:%s -> ' "$saltlen"
  openssl dgst -sha256 -verify pub.pem -sigopt rsa_padding_mode:pss \
    -sigopt "rsa_pss_saltlen:$saltlen" -signature pssa.sig msg 2>&1 | grep -o -E "Verified OK|salt length check failed"
done
cd / && rm -rf "$dir"
输出
PKCS#1 v1.5: identical signatures
PSS: different signatures
verify PSS with rsa_pss_saltlen:digest -> Verified OK
verify PSS with rsa_pss_saltlen:max -> salt length check failed

接受 auto 的验证者会从签名中检测盐长度;在签名方不受你控制时,这是兼容性最好的设置。

密钥与证书

同一个 RSA 密钥可以生成这两种签名,证书中的 rsaEncryption 密钥也可用于两者(TLS 称之为 rsa_pss_rsae)。X.509 还定义了 id-RSASSA-PSS 密钥,其参数把密钥限定为只能用于 PSS(TLS 中的 rsa_pss_pss);这类密钥能防止跨方案使用,但许多工具和部分浏览器对它们支持不佳,因此用 rsaEncryption 密钥做 PSS 签名才是实际可行的选择。WebPKI 中的证书签名几乎全是 PKCS#1 v1.5 或 ECDSA,单凭这一点,验证者就必须支持 v1.5。

如何选择

  • TLS 1.3 握手:PSS,强制要求;无论使用哪种 RSA 证书,库都会自动处理。
  • JWT:所有使用方都支持时用 PS256,否则用 RS256;更好的做法是考虑 ES256(参见 ES256 与 RS256 对比)。
  • 你签发的 X.509 证书和 CRL:为获得最大兼容性,使用 PKCS#1 v1.5(sha256WithRSAEncryption),除非某个封闭生态系统统一采用了 PSS。
  • 使用 RSA 密钥的新文档签名、固件签名或 API 签名:PSS,盐长度等于哈希长度。
  • 与 HSM 和云 KMS 互操作:两者都受支持(例如 AWS KMS 中的 RSASSA_PSS_SHA_256 和 RSASSA_PKCS1_V1_5_SHA_256);与验证方的预期保持一致。
  • 验证任一种签名:使用库提供的高层验证函数,切勿手动解码编码后的消息。

迁移说明

从 v1.5 迁移到 PSS 不需要新密钥,因此很容易分阶段进行。

  1. 先让验证者支持 PSS:在 JOSE 中,为该密钥允许 PS256(“一个密钥只用一种算法”仍是规则,因此要么用新的 kid 和 alg PS256 发布同一份密钥材料,要么轮换密钥)。
  2. 显式固定参数:哈希 SHA-256、MGF1 SHA-256、盐长度 32 字节;不要依赖在不同版本间会变化的库默认值。
  3. 切换签名方,并留意提到盐长度或填充的验证失败。
  4. 旧签名过期后,停止为这些密钥接受 v1.5,只在证书或第三方有要求的地方保留 v1.5。
  5. 如果反正要轮换密钥,不妨考虑改用 ECDSA P-256 或 Ed25519,它们比任何 RSA 填充方式都更小,签名也更快。

参考资料

全部对比