错误信息
java.security.SignatureException: Invalid encoding for signature
`Caused by` 中的详细信息会随破坏 DER 解析的那个字节而变化(例如“Invalid lenByte”或“Not the correct tag”)。反过来的错误则悄无声息:把 DER 签名传给 `SHA256withECDSAinP1363Format` 不会抛出异常,验证结果只是 `false`。
Java (JDK) — Signature.verify with SHA256withECDSA (SunEC)
含义
`SHA256withECDSA` 按 ASN.1 DER(`30 … 02 r 02 s`)解析 ECDSA 签名,而你传入的字节不是 DER。几乎在所有情况下,它们都是另一种标准编码:JWS、WebCrypto 和许多非 Java 库使用的定长 r‖s 拼接格式(IEEE P1363)——P-256 为 64 字节。JDK 也能验证这种格式,对应的算法名称是 `SHA256withECDSAinP1363Format`。
原因
把原始 r‖s 签名当作 DER 验证
常见来自 JWT(ES256)、`crypto.subtle.sign`、手动拼接 r 和 s 的代码或区块链工具的签名,都是 64/96/132 字节的原始数据。Java 默认的 ECDSA 算法名称按 DER 解析签名。
签名字节解码有误
偶见用基本解码器解码 base64url 的 JWS 段(且字符替换不正确),或者把十六进制当作 base64 解码,得到的字节根本不是签名。
把 RSA 签名交给了 ECDSA 验证器
偶见256 字节的 RSA 签名同样不是 DER。选择验证器之前,先检查令牌或文档所用的算法。
修复方法
- 1.
用 P1363 算法验证原始签名
自 JDK 9 起,SunEC 提供程序为 SHA-1/224/256/384/512 与 ECDSA 的组合提供这类算法。无需转换。
java // JWS (ES256) and WebCrypto subtle.sign() produce raw r||s; WebAuthn and X.509 use DER. Signature verifier = Signature.getInstance("SHA256withECDSAinP1363Format"); verifier.initVerify(publicKey); verifier.update(signedBytes); boolean ok = verifier.verify(rawSignature); // 64 bytes for P-256 - 2.
或者把 r‖s 转换为 DER
如果某个 API 坚持要求 DER(例如 HSM 提供程序),就把 r 和 s 编码为最短形式的 DER INTEGER,放进一个 SEQUENCE 中。DER ⇄ 原始格式转换器可以检查你的转换结果。
java static byte[] p1363ToDer(byte[] raw) { int n = raw.length / 2; byte[] r = derInteger(java.util.Arrays.copyOfRange(raw, 0, n)); byte[] s = derInteger(java.util.Arrays.copyOfRange(raw, n, raw.length)); var out = new java.io.ByteArrayOutputStream(); out.write(0x30); out.write(r.length + s.length); // < 128 for P-256 and P-384 out.writeBytes(r); out.writeBytes(s); return out.toByteArray(); } static byte[] derInteger(byte[] v) { int i = 0; while (i < v.length - 1 && v[i] == 0) i++; boolean pad = (v[i] & 0x80) != 0; byte[] out = new byte[2 + (pad ? 1 : 0) + v.length - i]; out[0] = 0x02; out[1] = (byte) (out.length - 2); System.arraycopy(v, i, out, pad ? 3 : 2, v.length - i); return out; }