Aller au contenu
ecdsa.com
Java · Kotlin

Java JCA (SunEC, JDK 25)

La Java Cryptography Architecture avec le fournisseur SunEC du JDK, telle qu’elle se présente dans le JDK 25 : DER par défaut avec une variante au format P1363, nonces aléatoires, uniquement les trois courbes NIST, ainsi qu’Ed25519, Ed448, ML-KEM et ML-DSA.
Licence
GPL-2.0-only WITH Classpath-exception-2.0
Version vérifiée
25.0.4.1 ·
Code source
github.com/openjdk/jdk
Documentation
docs.oracle.com/en/java/javase/25/security/java-cryptography-architecture-jca-reference-guide.html

Compatibilité en un coup d’œil

ECDSAEdDSAPost-quantique

Courbes ECDSA
P-256P-384P-521
EdDSA
Ed25519Ed448
Formats de clé privée
pkcs8-derraw-scalar

Le code Java signe via java.security.Signature, KeyPairGenerator et KeyFactory, et ce sont les fournisseurs propres au JDK qui font le travail : SunEC pour les courbes elliptiques et EdDSA, ainsi que les fournisseurs plus récents ML-KEM (javax.crypto.KEM, JDK 24) et ML-DSA. Android, Kotlin et tous les frameworks JVM reposent sur la même API : ses comportements par défaut se retrouvent donc dans Spring, Keycloak et d’innombrables SDK.

C’est le nom de l’algorithme qui détermine l’encodage. SHA256withECDSA produit et attend de l’ASN.1 DER ; SHA256withECDSAinP1363Format utilise le r‖s brut de 64 octets dont ont besoin JWS et WebCrypto, comme dans l’exemple. Les confondre produit un comportement asymétrique : une signature brute transmise à SHA256withECDSA lève SignatureException, tandis qu’une signature DER transmise à la variante P1363 renvoie simplement false. NONEwithECDSA signe un condensat que vous avez calculé (sa signature a été vérifiée avec SHA256withECDSA sur le message), et le nom de la fonction de hachage est indépendant de la courbe : SHA256withECDSA sur une clé P-384 est donc autorisé, mais échoue face aux pairs qui attendent SHA-384.

Dans le JDK 25, SunEC ne propose que secp256r1, secp384r1 et secp521r1 pour ECDSA : P-224, secp256k1 et les courbes Brainpool ont levé des erreurs, ce qui explique pourquoi le code Bitcoin et Ethereum sur la JVM utilise Bouncy Castle ou web3j. Les nonces sont aléatoires, s n’est pas normalisé et la vérification accepte les deux moitiés : pour des pairs qui appliquent strictement low-S, il faut donc remplacer s par n − s dans les signatures.

Les clés se chargent en X.509 SPKI DER (X509EncodedKeySpec), en PKCS#8 DER (PKCS8EncodedKeySpec) ou à partir de composantes brutes (ECPrivateKeySpec). La prise en charge de PEM est arrivée sous forme d’API en préversion dans le JDK 25 (java.security.PEMDecoder, JEP 470), qui n’a décodé une clé publique lors des essais qu’avec --enable-preview ; le code de production retire encore lui-même l’enveloppe PEM et décode le base64. La prise en charge post-quantique est bien réelle : ML-KEM-512/768/1024 a réussi l’encapsulation, ML-DSA-44/65/87 la signature et la vérification ; SLH-DSA est absent de ce JDK.

Comparez les encodages de signature avec le convertisseur DER ⇄ brut et inspectez les JAR signés avec l’inspecteur de signatures JAR.

Les faits et les vérifications qui les fondent

Rien de ce qui suit n’est copié d’une documentation. La vérification content/libraries/verify/java-jca/run.sh a exécuté Java JCA (SunEC, JDK 25) 25.0.4.1 sur Eclipse Adoptium 25.0.4.1+1-LTS dans l’image de conteneur maven:3.9.16-eclipse-temurin-25 le 20 octobre 2026 ; la page doit correspondre à ce que la vérification a affiché, et chaque fait cite les lignes en question.

  • Encodage des signatures ECDSA

    der

    SHA256withECDSA renvoie de l’ASN.1 DER ; SHA256withECDSAinP1363Format renvoie 64 octets de r‖s brut.

    Relevé par la vérification
    fact ecdsa-signature-encoding = der
    evidence ecdsa-signature-encoding: SHA256withECDSAinP1363Format output is 64 bytes
  • Nonce ECDSA

    random

    Nonce aléatoire : la clé de test de la RFC 6979 a signé deux fois le même message avec des résultats différents.

    Relevé par la vérification
    fact ecdsa-nonce = random
  • Low-S à la signature

    either

    s n’est pas normalisé : des valeurs high-S sont apparues parmi 64 signatures.

    Relevé par la vérification
    fact ecdsa-low-s-sign = either
  • High-S à la vérification

    accepts-high-s

    Une signature DER dont s a été remplacé par n − s a été vérifiée.

    Relevé par la vérification
    fact ecdsa-low-s-verify = accepts-high-s
  • Ce que l’appel de signature hache

    hashes-message-or-digest

    SHA256withECDSA hache les données ; NONEwithECDSA signe un condensat précalculé, et cette signature a été vérifiée avec SHA256withECDSA sur le message.

    Relevé par la vérification
    fact ecdsa-prehash = hashes-message-or-digest
  • Hachage lié à la courbe

    none

    La fonction de hachage indiquée dans le nom de l’algorithme n’est pas liée à la courbe : SHA256withECDSA a signé et vérifié avec une clé P-384.

    Relevé par la vérification
    fact ecdsa-hash-curve-binding = none
  • Signature ECDSA mal formée

    throws

    Une signature brute transmise à SHA256withECDSA et un bloc de 3 octets lèvent SignatureException ; une signature DER transmise à la variante P1363 renvoie false.

    Relevé par la vérification
    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
  • Courbes ECDSA

    P-256P-384P-521

    secp256r1, secp384r1 et secp521r1 ; secp224r1, secp256k1 et brainpoolP256r1 n’étaient pas disponibles.

    Relevé par la vérification
    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 et Ed448 via KeyPairGenerator et Signature, sous ces noms.

    Relevé par la vérification
    fact eddsa = Ed25519, Ed448
  • Algorithmes post-quantiques

    ML-KEMML-DSA

    ML-KEM-512/768/1024 via javax.crypto.KEM et ML-DSA-44/65/87 via Signature ont fonctionné ; il n’existe pas de KeyPairGenerator SLH-DSA.

    Relevé par la vérification
    fact pqc = ML-KEM, ML-DSA
    evidence pqc: SLH-DSA KeyPairGenerator available: false
  • Formats de clé publique

    spki-der

    Les clés publiques se chargent depuis X.509 SPKI DER ; getEncoded() renvoie la même forme.

    Relevé par la vérification
    fact public-key-formats = spki-der
    evidence public-key-formats: getEncoded() of 256-bit key returns X.509 SPKI DER; format X.509
  • Formats de clé privée

    pkcs8-derraw-scalar

    Les clés privées se chargent depuis PKCS#8 DER et depuis le scalaire brut avec ECPrivateKeySpec.

    Relevé par la vérification
    fact private-key-formats = pkcs8-der, raw-scalar
  • API PEM

    preview

    java.security.PEMDecoder existe sous forme d’API en préversion (JEP 470) et n’a décodé une clé publique PEM qu’avec --enable-preview.

    Relevé par la vérification
    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

Utilisation

Signer et vérifier au format r‖s brut qu’utilisent JWS et WebCrypto, en choisissant la variante d’algorithme au format P1363.
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));
    }
}

Le banc de test des extraits de code exécute chaque exemple tel quel dans l’image de conteneur maven:3.9.16-eclipse-temurin-25, et l’exécution échoue si la sortie ne correspond pas.

Commande
java Usage.java
La sortie doit correspondre à cette expression régulière
raw bytes: 64, verified: true

Bibliothèques