Pular para o conteúdo
ecdsa.com
Java · Kotlin

Java JCA (SunEC, JDK 25)

A Java Cryptography Architecture com o provedor SunEC do JDK, como no JDK 25: DER por padrão, com uma variante no formato P1363, nonces aleatórios, apenas as três curvas do NIST, além de Ed25519, Ed448, ML-KEM e ML-DSA.
Licença
GPL-2.0-only WITH Classpath-exception-2.0
Versão verificada
25.0.4.1 ·
Código-fonte
github.com/openjdk/jdk
Documentação
docs.oracle.com/en/java/javase/25/security/java-cryptography-architecture-jca-reference-guide.html

Visão geral da compatibilidade

ECDSAEdDSAPós-quântica

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

O código Java assina por meio de java.security.Signature, KeyPairGenerator e KeyFactory, e os próprios provedores do JDK fazem o trabalho: o SunEC para curvas elípticas e EdDSA, além dos provedores mais novos de ML-KEM (javax.crypto.KEM, JDK 24) e de ML-DSA. Android, Kotlin e todos os frameworks da JVM se apoiam na mesma API, então os padrões dela aparecem no Spring, no Keycloak e em inúmeros SDKs.

O nome do algoritmo decide a codificação. SHA256withECDSA produz e espera ASN.1 DER; SHA256withECDSAinP1363Format usa o r‖s bruto de 64 bytes de que o JWS e o WebCrypto precisam, como no exemplo. Trocar um pelo outro tem efeitos assimétricos: uma assinatura bruta passada a SHA256withECDSA lança SignatureException, enquanto uma assinatura DER passada à variante P1363 simplesmente retorna false. NONEwithECDSA assina um digest que você calculou — e essa assinatura foi verificada com SHA256withECDSA contra a mensagem —, e o nome do hash é independente da curva; por isso, SHA256withECDSA com uma chave P-384 é permitido e falha com contrapartes que esperam SHA-384.

O SunEC do JDK 25 oferece apenas secp256r1, secp384r1 e secp521r1 para ECDSA: P-224, secp256k1 e as curvas Brainpool geraram erros, e é por isso que o código de Bitcoin e Ethereum na JVM usa o Bouncy Castle ou o web3j. Os nonces são aleatórios, o s não é normalizado e a verificação aceita s em qualquer uma das duas metades; por isso, as assinaturas destinadas a contrapartes que exigem low-S estrito precisam ter o s substituído por n − s.

As chaves entram como X.509 SPKI DER (X509EncodedKeySpec), PKCS#8 DER (PKCS8EncodedKeySpec) ou componentes brutos (ECPrivateKeySpec). O suporte a PEM chegou como API em versão prévia no JDK 25 (java.security.PEMDecoder, JEP 470), que no teste só decodificou uma chave pública com --enable-preview; o código de produção ainda remove a armadura e decodifica o base64. O suporte pós-quântico é real: o encapsulamento com ML-KEM-512/768/1024 e a assinatura e a verificação com ML-DSA-44/65/87 funcionaram; o SLH-DSA não está neste JDK.

Compare codificações de assinatura com o conversor DER ⇄ bruto e inspecione JARs assinados com o inspetor de assinaturas JAR.

Os fatos e as verificações por trás deles

Nada abaixo foi copiado da documentação. A verificação content/libraries/verify/java-jca/run.sh executou Java JCA (SunEC, JDK 25) 25.0.4.1 em Eclipse Adoptium 25.0.4.1+1-LTS na imagem de contêiner maven:3.9.16-eclipse-temurin-25 em 20 de outubro de 2026; a página precisa corresponder ao que a verificação imprimiu, e cada fato lista essas linhas.

  • Codificação da assinatura ECDSA

    der

    SHA256withECDSA retorna ASN.1 DER; SHA256withECDSAinP1363Format retorna 64 bytes de r‖s brutos.

    Registrado pela verificação
    fact ecdsa-signature-encoding = der
    evidence ecdsa-signature-encoding: SHA256withECDSAinP1363Format output is 64 bytes
  • Nonce do ECDSA

    random

    Nonce aleatório: a chave de teste da RFC 6979 assinou a mesma mensagem duas vezes com resultados diferentes.

    Registrado pela verificação
    fact ecdsa-nonce = random
  • Low-S ao assinar

    either

    O s não é normalizado: apareceram valores high-S entre 64 assinaturas.

    Registrado pela verificação
    fact ecdsa-low-s-sign = either
  • High-S ao verificar

    accepts-high-s

    Uma assinatura DER com s substituído por n − s foi verificada.

    Registrado pela verificação
    fact ecdsa-low-s-verify = accepts-high-s
  • Entrada do hash na chamada de assinatura

    hashes-message-or-digest

    SHA256withECDSA calcula o hash dos dados; NONEwithECDSA assina um digest pré-calculado, e essa assinatura foi verificada com SHA256withECDSA contra a mensagem.

    Registrado pela verificação
    fact ecdsa-prehash = hashes-message-or-digest
  • Hash vinculado à curva

    none

    O hash no nome do algoritmo não está atrelado à curva: SHA256withECDSA assinou e verificou com uma chave P-384.

    Registrado pela verificação
    fact ecdsa-hash-curve-binding = none
  • Assinatura ECDSA malformada

    throws

    Uma assinatura bruta passada a SHA256withECDSA e um blob de 3 bytes lançam SignatureException; uma assinatura DER passada à variante P1363 retorna false.

    Registrado pela verificação
    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 e secp521r1; secp224r1, secp256k1 e brainpoolP256r1 não estavam disponíveis.

    Registrado pela verificação
    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 e Ed448 por meio de KeyPairGenerator e Signature com esses nomes.

    Registrado pela verificação
    fact eddsa = Ed25519, Ed448
  • Algoritmos pós-quânticos

    ML-KEMML-DSA

    ML-KEM-512/768/1024 por meio de javax.crypto.KEM e ML-DSA-44/65/87 por meio de Signature funcionaram; não há KeyPairGenerator para SLH-DSA.

    Registrado pela verificação
    fact pqc = ML-KEM, ML-DSA
    evidence pqc: SLH-DSA KeyPairGenerator available: false
  • Formatos de chave pública

    spki-der

    As chaves públicas são carregadas de X.509 SPKI DER; getEncoded() retorna a mesma forma.

    Registrado pela verificação
    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 chave privada

    pkcs8-derraw-scalar

    As chaves privadas são carregadas de PKCS#8 DER e do escalar bruto com ECPrivateKeySpec.

    Registrado pela verificação
    fact private-key-formats = pkcs8-der, raw-scalar
  • API de PEM

    preview

    java.security.PEMDecoder existe como API em versão prévia (JEP 470) e só decodificou uma chave pública PEM com --enable-preview.

    Registrado pela verificação
    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

Assine e verifique no formato r‖s bruto que o JWS e o WebCrypto usam, escolhendo o nome de algoritmo da variante no formato 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));
    }
}

O executor de trechos de código roda cada exemplo literalmente na imagem de contêiner maven:3.9.16-eclipse-temurin-25, e a execução falha se a saída não corresponder.

Comando
java Usage.java
A saída deve corresponder a (expressão regular)
raw bytes: 64, verified: true

Bibliotecas