El código Java firma mediante java.security.Signature, KeyPairGenerator y KeyFactory, y el trabajo lo hacen los propios proveedores del JDK: SunEC para las curvas elípticas y EdDSA, además de los proveedores más recientes de ML-KEM (javax.crypto.KEM, JDK 24) y ML-DSA. Android, Kotlin y todos los frameworks de la JVM se apoyan en la misma API, así que sus valores predeterminados aparecen en Spring, Keycloak e innumerables SDK.
El nombre del algoritmo determina la codificación. SHA256withECDSA produce y espera DER ASN.1; SHA256withECDSAinP1363Format usa el r‖s en bruto de 64 bytes que necesitan JWS y WebCrypto, como en el ejemplo. Confundirlos tiene un efecto asimétrico: una firma en bruto pasada a SHA256withECDSA lanza SignatureException, mientras que una firma DER pasada a la variante P1363 simplemente devuelve false. NONEwithECDSA firma un resumen que calculas tú —su firma se verificó con SHA256withECDSA contra el mensaje— y el nombre del hash es independiente de la curva, así que SHA256withECDSA con una clave P-384 está permitido y falla con las implementaciones que esperan SHA-384.
SunEC en JDK 25 solo ofrece secp256r1, secp384r1 y secp521r1 para ECDSA: P-224, secp256k1 y las curvas Brainpool dieron errores, y por eso el código de Bitcoin y Ethereum en la JVM usa Bouncy Castle o web3j. Los nonces son aleatorios, s no se normaliza y la verificación acepta ambas mitades, así que, para los verificadores low-S estrictos, hay que sustituir s por n − s en las firmas.
Las claves se cargan como X.509 SPKI DER (X509EncodedKeySpec), PKCS#8 DER (PKCS8EncodedKeySpec) o componentes en bruto (ECPrivateKeySpec). La compatibilidad con PEM llegó como API en vista previa en JDK 25 (java.security.PEMDecoder, JEP 470), que en la comprobación decodificó una clave pública solo con --enable-preview; el código de producción sigue quitando la armadura y decodificando el base64. La compatibilidad poscuántica es real: con ML-KEM-512/768/1024 se encapsuló y con ML-DSA-44/65/87 se firmó y se verificó; SLH-DSA no está en este JDK.
Compara codificaciones de firma con el conversor DER ⇄ en bruto e inspecciona los JAR firmados con el inspector de firmas JAR.