Java 代码通过 java.security.Signature、KeyPairGenerator 和 KeyFactory 签名,实际工作由 JDK 自带的提供程序完成:SunEC 负责椭圆曲线和 EdDSA,此外还有较新的 ML-KEM(javax.crypto.KEM,JDK 24)和 ML-DSA 提供程序。Android、Kotlin 和所有 JVM 框架都建立在同一套 API 之上,因此它的默认行为会出现在 Spring、Keycloak 和无数 SDK 中。
算法名称决定编码。SHA256withECDSA 生成并要求 ASN.1 DER;SHA256withECDSAinP1363Format 则像示例那样,使用 JWS 和 WebCrypto 所需的 64 字节原始 r‖s。两者混用时表现并不对称:把原始签名交给 SHA256withECDSA 会抛出 SignatureException,而把 DER 签名交给 P1363 变体只会返回 false。NONEwithECDSA 对你计算好的摘要签名——它生成的签名用 SHA256withECDSA 针对原消息验证通过——而且哈希名称与曲线无关,因此在 P-384 密钥上使用 SHA256withECDSA 是允许的,但要求 SHA-384 的对端会验证失败。
JDK 25 中的 SunEC 只为 ECDSA 提供 secp256r1、secp384r1 和 secp521r1:P-224、secp256k1 和 Brainpool 曲线都会报错,这也是 JVM 上的比特币和以太坊代码使用 Bouncy Castle 或 web3j 的原因。nonce 是随机的,s 没有经过规范化,验证时处于高半区和低半区的 s 都接受,因此面向严格要求 low-S 的对端时,需要把签名中的 s 替换为 n − s。
密钥可以以 X.509 SPKI DER(X509EncodedKeySpec)、PKCS#8 DER(PKCS8EncodedKeySpec)或原始分量(ECPrivateKeySpec)的形式导入。PEM 支持在 JDK 25 中以预览 API 的形式出现(java.security.PEMDecoder,JEP 470),在检查中只有加上 --enable-preview 才能解码公钥;生产代码仍需自行去掉 PEM 首尾标记行并做 base64 解码。后量子支持确实可用:ML-KEM-512/768/1024 封装成功,ML-DSA-44/65/87 的签名与验证均成功;这个 JDK 中没有 SLH-DSA。
可以用 DER ⇄ 原始格式转换器比较签名编码,用 JAR 签名查看器查看已签名的 JAR。