Zum Inhalt springen
ecdsa.com
Java · Kotlin

Java JCA (SunEC, JDK 25)

Die Java Cryptography Architecture mit dem SunEC-Provider des JDK, Stand JDK 25: standardmäßig DER mit einer Variante im P1363-Format, zufällige Nonces, nur die drei NIST-Kurven, dazu Ed25519, Ed448, ML-KEM und ML-DSA.
Lizenz
GPL-2.0-only WITH Classpath-exception-2.0
Geprüfte Version
25.0.4.1 ·
Quellcode
github.com/openjdk/jdk
Dokumentation
docs.oracle.com/en/java/javase/25/security/java-cryptography-architecture-jca-reference-guide.html

Kompatibilität auf einen Blick

ECDSAEdDSAPost-Quanten-Verfahren

ECDSA-Kurven
P-256P-384P-521
EdDSA
Ed25519Ed448
Formate privater Schlüssel
pkcs8-derraw-scalar

Java-Code signiert über java.security.Signature, KeyPairGenerator und KeyFactory, und die Arbeit erledigen die Provider des JDK selbst: SunEC für elliptische Kurven und EdDSA, dazu die neueren Provider für ML-KEM (javax.crypto.KEM, JDK 24) und ML-DSA. Android, Kotlin und jedes JVM-Framework bauen auf derselben API auf, deshalb tauchen ihre Standardeinstellungen in Spring, Keycloak und unzähligen SDKs auf.

Der Algorithmusname bestimmt die Kodierung. SHA256withECDSA erzeugt und erwartet ASN.1-DER; SHA256withECDSAinP1363Format verwendet das 64 Byte lange rohe r‖s, das JWS und WebCrypto brauchen, wie im Beispiel. Eine Verwechslung wirkt sich asymmetrisch aus: Eine rohe Signatur, an SHA256withECDSA übergeben, löst SignatureException aus, während eine DER-Signatur, an die P1363-Variante übergeben, einfach false zurückgibt. NONEwithECDSA signiert einen Digest, den du selbst berechnet hast – die Signatur bestand mit SHA256withECDSA die Prüfung gegen die Nachricht –, und der Hashname ist unabhängig von der Kurve: SHA256withECDSA mit einem P-384-Schlüssel ist zulässig und scheitert bei Gegenstellen, die SHA-384 erwarten.

SunEC in JDK 25 bietet für ECDSA nur secp256r1, secp384r1 und secp521r1: P-224, secp256k1 und die Brainpool-Kurven lösten Fehler aus, weshalb Bitcoin- und Ethereum-Code auf der JVM Bouncy Castle oder web3j verwendet. Nonces sind zufällig, s wird nicht normalisiert, und die Prüfung akzeptiert beide Hälften; für Gegenstellen, die strikt Low-S verlangen, muss s in den Signaturen daher durch n − s ersetzt werden.

Schlüssel werden als X.509-SPKI in DER (X509EncodedKeySpec), als PKCS#8 in DER (PKCS8EncodedKeySpec) oder als rohe Komponenten (ECPrivateKeySpec) geladen. PEM-Unterstützung kam in JDK 25 als Preview-API (java.security.PEMDecoder, JEP 470), die in der Prüfung einen öffentlichen Schlüssel nur mit --enable-preview dekodierte; Produktionscode entfernt weiterhin selbst die PEM-Kopf- und -Fußzeilen und dekodiert den base64-Inhalt. Die Post-Quanten-Unterstützung ist tatsächlich nutzbar: Kapselung mit ML-KEM-512/768/1024 sowie Signieren und Prüfen mit ML-DSA-44/65/87 funktionierten; SLH-DSA ist in diesem JDK nicht enthalten.

Vergleiche Signaturkodierungen mit dem Konverter DER ⇄ Raw und untersuche signierte JARs mit dem JAR-Signaturinspektor.

Fakten und die Prüfungen dahinter

Nichts im Folgenden ist aus einer Dokumentation abgeschrieben. Das Prüfskript content/libraries/verify/java-jca/run.sh hat Java JCA (SunEC, JDK 25) 25.0.4.1 unter Eclipse Adoptium 25.0.4.1+1-LTS am 20. Oktober 2026 im Container-Image maven:3.9.16-eclipse-temurin-25 ausgeführt; die Seite muss mit seiner Ausgabe übereinstimmen, und jeder Fakt führt die zugehörigen Zeilen auf.

  • Kodierung von ECDSA-Signaturen

    der

    SHA256withECDSA liefert ASN.1-DER; SHA256withECDSAinP1363Format liefert 64 Byte rohes r‖s.

    Von der Prüfung aufgezeichnet
    fact ecdsa-signature-encoding = der
    evidence ecdsa-signature-encoding: SHA256withECDSAinP1363Format output is 64 bytes
  • ECDSA-Nonce

    random

    Zufällige Nonce: Der Testschlüssel aus RFC 6979 signierte dieselbe Nachricht zweimal mit unterschiedlichem Ergebnis.

    Von der Prüfung aufgezeichnet
    fact ecdsa-nonce = random
  • Low-S beim Signieren

    either

    s wird nicht normalisiert: Unter 64 Signaturen traten High-S-Werte auf.

    Von der Prüfung aufgezeichnet
    fact ecdsa-low-s-sign = either
  • High-S bei der Prüfung

    accepts-high-s

    Eine DER-Signatur, in der s durch n − s ersetzt war, bestand die Prüfung.

    Von der Prüfung aufgezeichnet
    fact ecdsa-low-s-verify = accepts-high-s
  • Was der Signaturaufruf hasht

    hashes-message-or-digest

    SHA256withECDSA hasht die Daten; NONEwithECDSA signiert einen vorab berechneten Digest, und diese Signatur bestand mit SHA256withECDSA die Prüfung gegen die Nachricht.

    Von der Prüfung aufgezeichnet
    fact ecdsa-prehash = hashes-message-or-digest
  • An die Kurve gebundener Hash

    none

    Der Hash im Algorithmusnamen ist nicht an die Kurve gebunden: Signieren und Prüfen mit SHA256withECDSA funktionierten mit einem P-384-Schlüssel.

    Von der Prüfung aufgezeichnet
    fact ecdsa-hash-curve-binding = none
  • Fehlerhafte ECDSA-Signatur

    throws

    Eine rohe Signatur und ein 3-Byte-Blob, an SHA256withECDSA übergeben, lösen SignatureException aus; eine DER-Signatur, an die P1363-Variante übergeben, gibt false zurück.

    Von der Prüfung aufgezeichnet
    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-Kurven

    P-256P-384P-521

    secp256r1, secp384r1 und secp521r1; secp224r1, secp256k1 und brainpoolP256r1 waren nicht verfügbar.

    Von der Prüfung aufgezeichnet
    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 und Ed448 über KeyPairGenerator und Signature mit genau diesen Namen.

    Von der Prüfung aufgezeichnet
    fact eddsa = Ed25519, Ed448
  • Post-Quanten-Algorithmen

    ML-KEMML-DSA

    ML-KEM-512/768/1024 über javax.crypto.KEM und ML-DSA-44/65/87 über Signature funktionierten; einen KeyPairGenerator für SLH-DSA gibt es nicht.

    Von der Prüfung aufgezeichnet
    fact pqc = ML-KEM, ML-DSA
    evidence pqc: SLH-DSA KeyPairGenerator available: false
  • Formate öffentlicher Schlüssel

    spki-der

    Öffentliche Schlüssel werden aus X.509-SPKI in DER geladen; getEncoded() liefert dieselbe Form.

    Von der Prüfung aufgezeichnet
    fact public-key-formats = spki-der
    evidence public-key-formats: getEncoded() of 256-bit key returns X.509 SPKI DER; format X.509
  • Formate privater Schlüssel

    pkcs8-derraw-scalar

    Private Schlüssel werden aus PKCS#8 in DER und mit ECPrivateKeySpec aus dem rohen Skalar geladen.

    Von der Prüfung aufgezeichnet
    fact private-key-formats = pkcs8-der, raw-scalar
  • PEM-API

    preview

    java.security.PEMDecoder existiert als Preview-API (JEP 470) und dekodierte einen öffentlichen Schlüssel im PEM-Format nur mit --enable-preview.

    Von der Prüfung aufgezeichnet
    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

Verwendung

Signiere und prüfe im rohen r‖s-Format, das JWS und WebCrypto verwenden, indem du den Algorithmusnamen der Variante im P1363-Format wählst.
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));
    }
}

Die Snippet-Testumgebung führt jedes Beispiel unverändert im Container-Image maven:3.9.16-eclipse-temurin-25 aus, und der Lauf schlägt fehl, wenn die Ausgabe nicht übereinstimmt.

Befehl
java Usage.java
Ausgabe muss passen (regulärer Ausdruck)
raw bytes: 64, verified: true

Bibliotheken