Saltar al contenido
ecdsa.com
Java · Kotlin

Java JCA (SunEC, JDK 25)

La Java Cryptography Architecture con el proveedor SunEC del JDK, tal como está en JDK 25: DER por defecto con una variante en formato P1363, nonces aleatorios, solo las tres curvas NIST, además de Ed25519, Ed448, ML-KEM y ML-DSA.
Licencia
GPL-2.0-only WITH Classpath-exception-2.0
Versión comprobada
25.0.4.1 ·
Código fuente
github.com/openjdk/jdk
Documentación
docs.oracle.com/en/java/javase/25/security/java-cryptography-architecture-jca-reference-guide.html

Compatibilidad de un vistazo

ECDSAEdDSACriptografía poscuántica

Curvas ECDSA
P-256P-384P-521
EdDSA
Ed25519Ed448
Formatos de clave privada
pkcs8-derraw-scalar

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.

Datos y las comprobaciones que los respaldan

Nada de lo que sigue está copiado de la documentación. La comprobación content/libraries/verify/java-jca/run.sh ejecutó Java JCA (SunEC, JDK 25) 25.0.4.1 en Eclipse Adoptium 25.0.4.1+1-LTS en la imagen de contenedor maven:3.9.16-eclipse-temurin-25 el 20 de octubre de 2026; la página debe coincidir con lo que imprimió, y cada dato cita esas líneas.

  • Codificación de la firma ECDSA

    der

    SHA256withECDSA devuelve DER ASN.1; SHA256withECDSAinP1363Format devuelve 64 bytes de r‖s en bruto.

    Registrado por la comprobación
    fact ecdsa-signature-encoding = der
    evidence ecdsa-signature-encoding: SHA256withECDSAinP1363Format output is 64 bytes
  • Nonce de ECDSA

    random

    Nonce aleatorio: la clave de prueba del RFC 6979 firmó dos veces el mismo mensaje con resultados distintos.

    Registrado por la comprobación
    fact ecdsa-nonce = random
  • Low-S al firmar

    either

    s no se normaliza: aparecieron valores high-S entre 64 firmas.

    Registrado por la comprobación
    fact ecdsa-low-s-sign = either
  • High-S al verificar

    accepts-high-s

    Una firma DER con s sustituido por n − s se verificó.

    Registrado por la comprobación
    fact ecdsa-low-s-verify = accepts-high-s
  • De qué calcula el hash la llamada de firma

    hashes-message-or-digest

    SHA256withECDSA calcula el hash de los datos; NONEwithECDSA firma un resumen ya calculado, y esa firma se verificó con SHA256withECDSA contra el mensaje.

    Registrado por la comprobación
    fact ecdsa-prehash = hashes-message-or-digest
  • Hash vinculado a la curva

    none

    El hash del nombre del algoritmo no está ligado a la curva: SHA256withECDSA firmó y verificó con una clave P-384.

    Registrado por la comprobación
    fact ecdsa-hash-curve-binding = none
  • Firma ECDSA mal formada

    throws

    Una firma en bruto pasada a SHA256withECDSA y un blob de 3 bytes lanzan SignatureException; una firma DER pasada a la variante P1363 devuelve false.

    Registrado por la comprobación
    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
  • Curvas ECDSA

    P-256P-384P-521

    secp256r1, secp384r1 y secp521r1; secp224r1, secp256k1 y brainpoolP256r1 no estaban disponibles.

    Registrado por la comprobación
    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 y Ed448 mediante KeyPairGenerator y Signature con esos nombres.

    Registrado por la comprobación
    fact eddsa = Ed25519, Ed448
  • Algoritmos poscuánticos

    ML-KEMML-DSA

    ML-KEM-512/768/1024 mediante javax.crypto.KEM y ML-DSA-44/65/87 mediante Signature funcionaron; no hay KeyPairGenerator para SLH-DSA.

    Registrado por la comprobación
    fact pqc = ML-KEM, ML-DSA
    evidence pqc: SLH-DSA KeyPairGenerator available: false
  • Formatos de clave pública

    spki-der

    Las claves públicas se cargan desde X.509 SPKI DER; getEncoded() devuelve la misma forma.

    Registrado por la comprobación
    fact public-key-formats = spki-der
    evidence public-key-formats: getEncoded() of 256-bit key returns X.509 SPKI DER; format X.509
  • Formatos de clave privada

    pkcs8-derraw-scalar

    Las claves privadas se cargan desde PKCS#8 DER y desde el escalar en bruto con ECPrivateKeySpec.

    Registrado por la comprobación
    fact private-key-formats = pkcs8-der, raw-scalar
  • API de PEM

    preview

    java.security.PEMDecoder existe como API en vista previa (JEP 470) y decodificó una clave pública PEM solo con --enable-preview.

    Registrado por la comprobación
    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

Uso

Firma y verifica en el formato r‖s en bruto que usan JWS y WebCrypto, eligiendo el nombre de algoritmo terminado en inP1363Format.
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));
    }
}

El arnés de pruebas de fragmentos de código ejecuta cada ejemplo tal cual en la imagen de contenedor maven:3.9.16-eclipse-temurin-25, y la ejecución falla si la salida no coincide.

Comando
java Usage.java
La salida debe coincidir (expresión regular)
raw bytes: 64, verified: true

Bibliotecas