Pular para o conteúdo
ecdsa.com
Rust

ring

O ring, de Brian Smith, a biblioteca criptográfica Rust por trás do rustls e de muitos servidores web: uma API deliberadamente pequena, em que a constante de algoritmo define a codificação da assinatura, as chaves privadas são apenas PKCS#8 e as curvas são P-256, P-384 e Ed25519.
Licença
Apache-2.0 AND ISC
Versão verificada
0.17.14 ·
Código-fonte
github.com/briansmith/ring
Documentação
docs.rs/ring/0.17.14/ring/

Visão geral da compatibilidade

ECDSAEdDSA

Curvas ECDSA
P-256P-384
EdDSA
Ed25519

O ring oferece uma lista curta de algoritmos, com assembly derivado do BoringSSL e uma API resistente a uso indevido: não há como escolher um nonce, assinar um digest pré-calculado nem carregar uma chave em um formato incomum. Isso o torna rápido e difícil de usar errado, e é por isso que o trabalho fica na conversão de chaves e assinaturas vindas de outras stacks.

A codificação faz parte do algoritmo: ECDSA_P256_SHA256_FIXED_SIGNING produz r‖s bruto de 64 bytes e ECDSA_P256_SHA256_ASN1_SIGNING, DER, com as constantes de verificação _FIXED e _ASN1 correspondentes. Uma assinatura verificada com a outra constante simplesmente falha com Unspecified, então uma assinatura JWS verificada com a constante ASN1 — ou uma assinatura do OpenSSL com a FIXED — parece exatamente uma assinatura forjada. O exemplo mostra os dois resultados. As assinaturas usam um nonce aleatório, o s não é normalizado e a verificação aceita as duas metades.

As chaves privadas são lidas apenas como PKCS#8 DER, e as chaves Ed25519 precisam ser PKCS#8 v2 com a chave pública incluída: a forma v1 de 48 bytes, que o OpenSSL e a maioria das ferramentas gravam, foi rejeitada por Ed25519KeyPair::from_pkcs8 com VersionNotSupported, enquanto from_pkcs8_maybe_unchecked e from_seed_unchecked a aceitaram. Chaves privadas PEM, SEC1 e JWK precisam ser convertidas antes, por exemplo com o conversor de JWK. As chaves públicas são pontos SEC1 isolados: public_key() retorna a forma não comprimida de 65 bytes, e pontos comprimidos não são aceitos.

A lista de curvas se resume à P-256 e à P-384, além do Ed25519; não há P-521, secp256k1 nem algoritmo pós-quântico. Projetos que precisam deles, ou de validação FIPS, migram para o aws-lc-rs, que oferece uma API compatível com o ring.

Para conferir em que codificação está uma assinatura do ring, cole-a no conversor DER ⇄ bruto.

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

Nada abaixo foi copiado da documentação. A verificação content/libraries/verify/ring/run.sh executou ring 0.17.14 na imagem de contêiner rust:1.99.1-trixie 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-or-raw

    A constante de algoritmo escolhe a codificação: *_FIXED assina e verifica r‖s bruto de 64 bytes, e *_ASN1, DER; uma assinatura não passa na verificação com a outra constante.

    Registrado pela verificação
    fact ecdsa-signature-encoding = der-or-raw
    evidence ecdsa-signature-encoding: FIXED signs 64 bytes; ASN1 output starts with 0x30 (a DER SEQUENCE)
    evidence ecdsa-signature-encoding: a signature verifies under the other encoding's constant: false
  • Nonce do ECDSA

    random

    Nonce aleatório: duas assinaturas da mesma mensagem são diferentes, e não há opção determinística.

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

    either

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

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

    accepts-high-s

    O verificador FIXED aceitou a assinatura com s substituído por n − s.

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

    hashes-message

    O hash da mensagem é calculado conforme indica o algoritmo; não há API para assinar um digest pré-calculado.

    Registrado pela verificação
    fact ecdsa-prehash = hashes-message
    evidence ecdsa-prehash: the API has no entry point for signing a precomputed digest
  • Assinatura ECDSA malformada

    returns-error

    verify() retorna Err(Unspecified) para uma assinatura malformada ou incorreta, sem nenhum detalhe.

    Registrado pela verificação
    fact ecdsa-malformed-signature = returns-error
    evidence ecdsa-malformed-signature: verify() of 3 bytes: Err(Unspecified)
  • Curvas ECDSA

    P-256P-384

    Apenas P-256 e P-384.

    Registrado pela verificação
    fact ecdsa-curves = P-256, P-384
    evidence ecdsa-curves: ring defines ECDSA_P256_* and ECDSA_P384_* algorithms only
  • EdDSA

    Ed25519

    Ed25519.

    Registrado pela verificação
    fact eddsa = Ed25519
  • Chaves Ed25519 PKCS #8 v1

    rejected-by-from-pkcs8

    Uma chave Ed25519 PKCS#8 v1 de 48 bytes foi recusada por from_pkcs8 com VersionNotSupported; from_pkcs8_maybe_unchecked e from_seed_unchecked a aceitaram. generate_pkcs8 grava chaves v2 de 83 bytes.

    Registrado pela verificação
    fact ed25519-pkcs8-v1 = rejected-by-from-pkcs8
    evidence ed25519-pkcs8-v1: generate_pkcs8 writes 83 bytes (PKCS#8 v2 with the public key); a 48-byte v1 key: from_pkcs8 Err(KeyRejected("VersionNotSupported")), from_pkcs8_maybe_unchecked ok true, from_seed_unchecked ok true
  • Algoritmos pós-quânticos

    none

    Sem algoritmos pós-quânticos.

    Registrado pela verificação
    fact pqc = none
  • Formatos de chave pública

    sec1-uncompressed

    As chaves públicas são pontos SEC1 na forma não comprimida (65 bytes na P-256); pontos comprimidos falharam, e não há leitura de SPKI, PEM nem JWK.

    Registrado pela verificação
    fact public-key-formats = sec1-uncompressed
    evidence public-key-formats: public_key() returns 65 bytes (uncompressed SEC1); no SPKI, PEM or JWK parsing
  • Formatos de chave privada

    pkcs8-der

    As chaves privadas são carregadas apenas de PKCS#8 DER.

    Registrado pela verificação
    fact private-key-formats = pkcs8-der

Uso

Assine com a constante FIXED (bruta) e veja que só a constante de verificação correspondente aceita a assinatura.
use ring::rand::SystemRandom;
use ring::signature::{self, EcdsaKeyPair, KeyPair, UnparsedPublicKey};

fn main() {
    let rng = SystemRandom::new();
    // ring only reads PKCS#8 DER private keys.
    let pkcs8 = EcdsaKeyPair::generate_pkcs8(&signature::ECDSA_P256_SHA256_FIXED_SIGNING, &rng).unwrap();
    let message = b"order #1042 approved";

    // The algorithm constant fixes the encoding: FIXED = raw r||s (JWS, WebCrypto),
    // ASN1 = DER (OpenSSL, Java). Verify with the matching constant.
    let fixed = EcdsaKeyPair::from_pkcs8(&signature::ECDSA_P256_SHA256_FIXED_SIGNING, pkcs8.as_ref(), &rng).unwrap();
    let raw = fixed.sign(&rng, message).unwrap();
    let public = fixed.public_key().as_ref(); // 65-byte uncompressed SEC1 point

    let ok = UnparsedPublicKey::new(&signature::ECDSA_P256_SHA256_FIXED, public).verify(message, raw.as_ref()).is_ok();
    let wrong = UnparsedPublicKey::new(&signature::ECDSA_P256_SHA256_ASN1, public).verify(message, raw.as_ref()).is_ok();
    println!("raw bytes: {}, verified: {ok}, verified as DER: {wrong}", raw.as_ref().len());
}

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

Comando
cargo run --quiet --release
A saída deve corresponder a (expressão regular)
raw bytes: 64, verified: true, verified as DER: false

Bibliotecas