跳到主要内容
ecdsa.com
Java · Kotlin

Java JCA (SunEC, JDK 25)

Java 密码体系结构(JCA)及 JDK 自带的 SunEC 提供程序,以 JDK 25 为准:默认使用 DER,另有 P1363 格式变体;nonce 随机;仅支持三条 NIST 曲线,另外还有 Ed25519、Ed448、ML-KEM 和 ML-DSA。
许可证
GPL-2.0-only WITH Classpath-exception-2.0
检查的版本
25.0.4.1 ·
源代码
github.com/openjdk/jdk
文档
docs.oracle.com/en/java/javase/25/security/java-cryptography-architecture-jca-reference-guide.html

兼容性概览

ECDSAEdDSA后量子

ECDSA 曲线
P-256P-384P-521
EdDSA
Ed25519Ed448
后量子算法
ML-KEMML-DSA
公钥格式
spki-der
私钥格式
pkcs8-derraw-scalar

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。

事实及其依据的检查

以下内容均非抄自文档。检查脚本 content/libraries/verify/java-jca/run.sh 于2026年10月20日在容器镜像 maven:3.9.16-eclipse-temurin-25 中运行了 Java JCA (SunEC, JDK 25) 25.0.4.1(运行时为 Eclipse Adoptium 25.0.4.1+1-LTS);本页内容必须与它的输出一致,每条事实都列出了对应的输出行。

  • ECDSA 签名编码

    der

    SHA256withECDSA 返回 ASN.1 DER;SHA256withECDSAinP1363Format 返回 64 字节的原始 r‖s。

    检查记录
    fact ecdsa-signature-encoding = der
    evidence ecdsa-signature-encoding: SHA256withECDSAinP1363Format output is 64 bytes
  • ECDSA 的 nonce

    random

    nonce 随机:用 RFC 6979 的测试密钥对同一消息签名两次,结果不同。

    检查记录
    fact ecdsa-nonce = random
  • 签名时的 low-S

    either

    s 没有经过规范化:64 个签名中出现了 high-S 值。

    检查记录
    fact ecdsa-low-s-sign = either
  • 验证时的 high-S

    accepts-high-s

    把 s 替换为 n − s 的 DER 签名验证通过。

    检查记录
    fact ecdsa-low-s-verify = accepts-high-s
  • 签名调用对哪些内容做哈希

    hashes-message-or-digest

    SHA256withECDSA 会先对数据计算哈希;NONEwithECDSA 对预先计算好的摘要签名,该签名用 SHA256withECDSA 针对原消息验证通过。

    检查记录
    fact ecdsa-prehash = hashes-message-or-digest
  • 哈希与曲线绑定

    none

    算法名称中的哈希与曲线不绑定:SHA256withECDSA 用 P-384 密钥签名和验证均成功。

    检查记录
    fact ecdsa-hash-curve-binding = none
  • 格式错误的 ECDSA 签名

    throws

    把原始签名或 3 字节的数据块传给 SHA256withECDSA 时都会抛出 SignatureException;把 DER 签名传给 P1363 变体则返回 false。

    检查记录
    fact ecdsa-malformed-signature = throws
    evidence ecdsa-malformed-signature: raw r||s to SHA256withECDSA: throws SignatureException; DER to the P1363 algorithm: false; 3 bytes: throws SignatureException
  • ECDSA 曲线

    P-256P-384P-521

    secp256r1、secp384r1 和 secp521r1;secp224r1、secp256k1 和 brainpoolP256r1 不可用。

    检查记录
    fact ecdsa-curves = P-256, P-384, P-521
    evidence ecdsa-curves: P-224 (secp224r1) not available
    evidence ecdsa-curves: secp256k1 (secp256k1) not available
    evidence ecdsa-curves: brainpoolP256r1 (brainpoolP256r1) not available
  • EdDSA

    Ed25519Ed448

    Ed25519 和 Ed448,以这两个名称通过 KeyPairGenerator 和 Signature 使用。

    检查记录
    fact eddsa = Ed25519, Ed448
  • 后量子算法

    ML-KEMML-DSA

    通过 javax.crypto.KEM 使用的 ML-KEM-512/768/1024 和通过 Signature 使用的 ML-DSA-44/65/87 均可正常工作;没有 SLH-DSA 的 KeyPairGenerator。

    检查记录
    fact pqc = ML-KEM, ML-DSA
    evidence pqc: SLH-DSA KeyPairGenerator available: false
  • 公钥格式

    spki-der

    公钥从 X.509 SPKI DER 加载;getEncoded() 返回同样的格式。

    检查记录
    fact public-key-formats = spki-der
    evidence public-key-formats: getEncoded() of 256-bit key returns X.509 SPKI DER; format X.509
  • 私钥格式

    pkcs8-derraw-scalar

    私钥可从 PKCS#8 DER 加载,也可通过 ECPrivateKeySpec 从原始标量加载。

    检查记录
    fact private-key-formats = pkcs8-der, raw-scalar
  • PEM API

    preview

    java.security.PEMDecoder 以预览 API 的形式存在(JEP 470),只有加上 --enable-preview 才能解码 PEM 公钥。

    检查记录
    fact pem-api = preview
    evidence pem-api: java.security.PEMDecoder present: true (JEP 470, preview in JDK 25; needs --enable-preview)
    evidence pem-api: PEMDecoder.of().decode(PEM text) with --enable-preview: true

用法

选用带 inP1363Format 的算法名称,以 JWS 和 WebCrypto 使用的原始 r‖s 格式签名和验证。
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.security.spec.ECGenParameterSpec;

public class Usage {
    public static void main(String[] args) throws Exception {
        KeyPairGenerator generator = KeyPairGenerator.getInstance("EC");
        generator.initialize(new ECGenParameterSpec("secp256r1"));
        KeyPair pair = generator.generateKeyPair();
        byte[] message = "order #1042 approved".getBytes(StandardCharsets.UTF_8);

        // The algorithm name picks the hash AND the encoding:
        // SHA256withECDSA = DER, SHA256withECDSAinP1363Format = raw r||s (JWS, WebCrypto).
        Signature signer = Signature.getInstance("SHA256withECDSAinP1363Format");
        signer.initSign(pair.getPrivate());
        signer.update(message);
        byte[] raw = signer.sign();

        Signature verifier = Signature.getInstance("SHA256withECDSAinP1363Format");
        verifier.initVerify(pair.getPublic());
        verifier.update(message);
        System.out.println("raw bytes: " + raw.length + ", verified: " + verifier.verify(raw));
    }
}

代码片段测试工具会在容器镜像 maven:3.9.16-eclipse-temurin-25 中原样运行每个示例,只要输出不匹配,运行就会失败。

命令
java Usage.java
输出必须匹配(正则表达式)
raw bytes: 64, verified: true

库