bigywater.tech
Java · 409

RSASSA-PSS:带随机盐的 RSA 签名

RSASSA-PSS 是 RSA 签名的推荐填充方式,但 Java 里它的参数、盐长度与密钥类型处处是坑。这篇在 JDK 21 上逐项量出:API 的要求、盐的作用与上限、验证时参数不一致的后果、PSS 类型密钥的限制、与 OpenSSL 的互通,以及性能。

222
RSA 2048 + SHA-256 时 PSS 的最大盐长度(223 抛 InvalidKeyException: Key is too short)
1 vs 20
盐 0 与盐 32 时,同一消息签 20 次的不同签名个数
872 µs
RSA 2048 的 PKCS#1 v1.5 与 PSS 签名耗时(完全相同)

API 与必须的参数

实测 · 本机执行输出
  RSASSA-PSS 不 setParameter 直接 initSign / sign:SignatureException:Parameters required for RSASSA-PSS signatures
  Signature.getInstance("SHA256withRSA/PSS"):NoSuchAlgorithmException:SHA256withRSA/PSS Signature not available
  Signature.getInstance("SHA256withRSAandMGF1"):NoSuchAlgorithmException:SHA256withRSAandMGF1 Signature not available
  Signature.getInstance("RSASSA-PSS"):SignatureException:Parameters required for RSASSA-PSS signatures
  Signature.getInstance("SHA1withRSA/PSS"):NoSuchAlgorithmException:SHA1withRSA/PSS Signature not available
  签名参数:getParameters() = RSASSA-PSS SHA-256/32
  RSA 2048 + SHA-256 + 盐 32:签名 256 字节(与密钥的模长相同)

盐

实测 · 本机执行输出
  盐长度 0:同一消息签 20 次,不同的签名个数 = 1
  盐长度 20:同一消息签 20 次,不同的签名个数 = 20
  盐长度 32:同一消息签 20 次,不同的签名个数 = 20
  RSA 2048 + SHA-256 + 盐 222:签名成功
  RSA 2048 + SHA-256 + 盐 223:InvalidKeyException:Key is too short, need min 257 bytes
  RSA 2048 + SHA-512 + 盐 190:签名成功
  RSA 2048 + SHA-512 + 盐 191:InvalidKeyException:Key is too short, need min 257 bytes
  RSA 2048 + SHA-1 + 盐 234:签名成功
  RSA 2048 + SHA-1 + 盐 235:InvalidKeyException:Key is too short, need min 257 bytes
  上限的计算:emBits = 2048 - 1 = 2047,emLen = ceil(2047 / 8) = 256 字节,最大盐 = emLen - hLen - 2(SHA-256 为 256 - 32 - 2 = 222;SHA-512 为 256 - 64 - 2 = 190;SHA-1 为 256 - 20 - 2 = 234)

验证参数不一致

实测 · 本机执行输出
  同样的参数验证:true
  验证时盐长度写 20(签名用 32):false
  验证时摘要写 SHA-384(签名用 SHA-256):false
  验证时 MGF1 用 SHA-1、摘要用 SHA-256:false
  验证时用 PKCS#1 v1.5(SHA256withRSA):false
  签名翻转 1 位:false
  消息改一个字节:false
  PKCS#1 v1.5 的签名用 PSS 验证:false
  盐 0 签名,验证时写盐 32:false;写盐 0:true

PSS 类型的密钥

实测 · 本机执行输出
  RSASSA-PSS 类型的密钥:算法 RSASSA-PSS,公钥编码 346 字节(普通 RSA 公钥是 294 字节,多了 PSS 参数),参数 PSSParameterSpec SHA-256/盐 32
  用与密钥参数一致的 PSS 参数签名:成功;验证 true
  摘要 SHA-512(密钥限定 SHA-256)签名:InvalidKeyException:Key contains incompatible PSS parameter values
  盐 16(密钥限定最小盐 32)签名:InvalidKeyException:Key contains incompatible PSS parameter values
  盐 40(密钥限定最小盐 32)签名:成功
  PSS 密钥不 setParameter 签名:SignatureException:Parameters required for RSASSA-PSS signatures
  把 PSS 密钥用于 SHA256withRSA(PKCS#1 v1.5):InvalidKeyException:Invalid key for RSA signatures
  普通 RSA 私钥配 PSS 参数:成功

OpenSSL 互通

实测 · 本机执行输出
  openssl 验证 Java 盐 32 的签名(指定盐 32):Signature Verified Successfully
  openssl 验证 Java 盐 0 的签名(指定盐 0):Signature Verified Successfully
  openssl 验证 Java 盐 32 的签名(指定盐 0):Signature Verification Failure
  openssl 验证 Java 盐 32 的签名(rsa_pss_saltlen:auto):Signature Verified Successfully
  openssl 用 PSS 签名(盐 32):退出码 0,256 字节
  openssl 用 PSS 签名(盐 默认(不指定)):退出码 0,256 字节
  openssl 用 PSS 签名(盐 max):退出码 0,256 字节
  Java 验证 ossl_salt32.sig(OpenSSL 生成)盐 32:true;盐 222(验证时写成最大盐):false;写盐 20:false
  Java 验证 ossl_default.sig(OpenSSL 生成)盐 32:true;盐 222(验证时写成最大盐):false;写盐 20:false
  Java 验证 ossl_max.sig(OpenSSL 生成)盐 32:false;盐 222(验证时写成最大盐):true;写盐 20:false

性能

实测 · 本机执行输出
  RSA 2048 PKCS#1 v1.5(SHA256withRSA)    签名 872 µs/次,验证 37.5 µs/次
  RSA 2048 PSS(SHA-256,盐 32)             签名 872 µs/次,验证 36.3 µs/次
  RSA 3072 PKCS#1 v1.5                   签名 2539 µs/次,验证 68.4 µs/次
  RSA 3072 PSS(SHA-256,盐 32)             签名 2544 µs/次,验证 68.4 µs/次

分步演示

RSASSA-PSS
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12(SunRsaSign);OpenSSL 4.0.3(pkeyutl 的摘要要用 -digest,不能用 -pkeyopt digest:);性能是自写的 nanoTime 循环、单线程、5 轮中位数
    • 没有验证:证书里的 RSASSA-PSS 签名算法标识(X.509 / CMS)、JDK 17 / 27 的差异、更大的密钥(4096)、TLS 1.3 里 rsa_pss_rsae / rsa_pss_pss 的使用、其它提供者(BouncyCastle)
    京ICP备2026067502号-1京公网安备11010502063487号