本文へスキップ
ecdsa.com
Java · Kotlin

Java JCA (SunEC, JDK 25)

JDK 25 時点の、SunEC プロバイダーを使った Java 暗号化アーキテクチャ(JCA)です。ECDSA 署名はデフォルトで DER(P1363 形式のバリアントもあり)、nonce はランダム、曲線は NIST の 3 曲線のみで、ほかに 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
秘密鍵の形式
pkcs8-derraw-scalar

Java のコードは java.security.Signature、KeyPairGenerator、KeyFactory を通じて署名し、実際の処理は JDK 自身のプロバイダーが行います。楕円曲線と EdDSA は SunEC が担い、さらに新しい ML-KEM(javax.crypto.KEM、JDK 24)と ML-DSA のプロバイダーもあります。Android、Kotlin、そしてあらゆる JVM フレームワークが同じ API の上に成り立っているため、そのデフォルトの挙動は Spring や Keycloak、無数の SDK に現れます。

エンコーディングはアルゴリズム名で決まります。SHA256withECDSA は ASN.1 DER の署名を生成し、検証でも 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 上の Bitcoin や Ethereum のコードが Bouncy Castle や web3j を使うのはこのためです。nonce はランダムで、s は正規化されず、検証は low-S と high-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 を付けたときにだけ公開鍵をデコードできたため、本番のコードでは今もアーマー(ヘッダー行とフッター行)を取り除いてから base64 デコードしています。ポスト量子暗号への対応は実用レベルです。ML-KEM-512/768/1024 でカプセル化ができ、ML-DSA-44/65/87 で署名と検証ができました。SLH-DSA はこの JDK にはありません。

署名のエンコーディングの比較には DER ⇄ 生形式の変換ツールを、署名付き JAR の解析には JAR 署名インスペクターを使ってください。

事実とその根拠となるチェック

以下の内容はドキュメントからの転記ではありません。2026年10月20日に、チェック content/libraries/verify/java-jca/run.sh がコンテナイメージ 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 のテスト鍵で同じメッセージに 2 回署名すると、異なる結果になりました。

    チェックによる記録
    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

    SHA256withECDSA に生の署名や 3 バイトのデータを渡すと SignatureException がスローされます。P1363 のバリアントに DER の署名を渡すと 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

使い方

P1363 形式のアルゴリズム名を選び、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

ライブラリ