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.