Skip to content
ecdsa.com
Rust

aws-lc-rs

AWS's aws-lc-rs, a ring-compatible Rust API over the AWS-LC C library: the same FIXED/ASN1 encoding constants as ring, plus P-521, secp256k1, ML-KEM and ML-DSA in the stable API, and an optional FIPS 140-3 build.
License
ISC AND (Apache-2.0 OR ISC)
Checked version
1.18.2 ·
Source
github.com/aws/aws-lc-rs
Documentation
docs.rs/aws-lc-rs/1.18.2/aws_lc_rs/

Compatibility at a glance

ECDSAEdDSAPost-quantum

ECDSA curves
P-256P-384P-521secp256k1
EdDSA
Ed25519
Public key formats
spki-dersec1-uncompressedsec1-compressed
Private key formats
pkcs8-dersec1-derraw-scalar

aws-lc-rs exposes AWS-LC — Amazon's fork of BoringSSL — through an API modelled on ring, so most ring code compiles after changing the crate name. It is the default crypto provider of rustls and is what Rust services reach for when they need FIPS 140-3 validation (the fips feature) or post-quantum algorithms. The C library is compiled during the build; the check built 1.18.2 with default features in the plain rust image.

Signature encodings work exactly as in ring: the *_FIXED constants produce and accept 64-byte raw r‖s, the *_ASN1 constants DER, and a raw signature failed under the ASN1 verification constant. Nonces are random, s is not normalized and verification accepts both halves. Beyond ring's P-256 and P-384, it signs and verifies on P-521 and secp256k1, with SHA-2 and SHA-3 variants.

Post-quantum algorithms are part of the stable API in 1.18.2 — the check built without the unstable feature: ML-KEM-512, ML-KEM-768 and ML-KEM-1024 in aws_lc_rs::kem, and ML-DSA-44, ML-DSA-65 and ML-DSA-87 in aws_lc_rs::signature, which the sample uses. The sizes it prints — a 1952-byte public key and a 3309-byte signature for ML-DSA-65, a 1088-byte ciphertext for ML-KEM-768 — are what certificates, tokens and handshakes have to carry; the post-quantum signature playground and KEM playground compare them with classical algorithms.

Key loading is wider than ring's: ECDSA keys load from PKCS#8, SEC1 (RFC 5915) DER and a raw scalar with its public key; public keys from SPKI DER and SEC1 points, compressed or not. Unlike ring, Ed25519KeyPair::from_pkcs8 accepted the 48-byte PKCS#8 v1 form that OpenSSL writes.

Use the DER ⇄ raw converter when a FIXED/ASN1 mismatch is suspected, and the TLS configuration generator for hybrid post-quantum key exchange settings.

Facts and the checks behind them

Nothing below is copied from documentation. The check content/libraries/verify/aws-lc-rs/run.sh ran aws-lc-rs 1.18.2 in the container image rust:1.99.1-trixie on October 20, 2026; the page must match what it printed, and each fact lists those lines.

  • ECDSA signature encoding

    der-or-raw

    *_FIXED constants sign 64-byte raw r‖s, *_ASN1 constants DER; a raw signature did not verify under the ASN1 constant.

    Recorded by the check
    fact ecdsa-signature-encoding = der-or-raw
    evidence ecdsa-signature-encoding: FIXED signs 64 bytes; the same bytes verify under the ASN1 constant: false
  • ECDSA nonce

    random

    Random nonce: two signatures of the same message differ.

    Recorded by the check
    fact ecdsa-nonce = random
  • Low-S when signing

    either

    s is not normalized: high-S values appeared among 64 signatures.

    Recorded by the check
    fact ecdsa-low-s-sign = either
  • High-S when verifying

    accepts-high-s

    The FIXED verifier accepted the signature with s replaced by n − s.

    Recorded by the check
    fact ecdsa-low-s-verify = accepts-high-s
  • What the signing call hashes

    hashes-message

    The message is hashed by the algorithm named in the constant (SHA-2 or SHA-3 variants).

    Recorded by the check
    fact ecdsa-prehash = hashes-message
  • Malformed ECDSA signature

    returns-error

    Verification returns an error value for malformed or wrong signatures.

    Recorded by the check
    fact ecdsa-malformed-signature = returns-error
  • ECDSA curves

    P-256P-384P-521secp256k1

    P-256, P-384, P-521 and secp256k1 signed and verified.

    Recorded by the check
    fact ecdsa-curves = P-256, P-384, P-521, secp256k1
  • EdDSA

    Ed25519

    Ed25519.

    Recorded by the check
    fact eddsa = Ed25519
  • Ed25519 PKCS #8 v1 keys

    accepted

    Ed25519KeyPair::from_pkcs8 accepted a 48-byte PKCS#8 v1 Ed25519 key, which ring rejects.

    Recorded by the check
    fact ed25519-pkcs8-v1 = accepted
    evidence ed25519-pkcs8-v1: 48-byte PKCS#8 v1 Ed25519 key with from_pkcs8: Ok("ok")
  • Post-quantum algorithms

    ML-KEMML-DSA

    ML-KEM-512/768/1024 encapsulated and decapsulated and ML-DSA-44/65/87 signed and verified, built without the unstable feature.

    Recorded by the check
    fact pqc = ML-KEM, ML-DSA
    evidence pqc: ML-KEM-512/768/1024 via aws_lc_rs::kem; ML-DSA-44/65/87 via aws_lc_rs::signature
  • Public key formats

    spki-dersec1-uncompressedsec1-compressed

    SPKI DER through ParsedPublicKey, and SEC1 points compressed or uncompressed.

    Recorded by the check
    fact public-key-formats = spki-der, sec1-uncompressed, sec1-compressed
  • Private key formats

    pkcs8-dersec1-derraw-scalar

    PKCS#8 DER, SEC1 (RFC 5915) DER and a raw scalar together with its public key.

    Recorded by the check
    fact private-key-formats = pkcs8-der, sec1-der, raw-scalar

Usage

Sign and verify with ML-DSA-65 and run an ML-KEM-768 encapsulation, printing the key, signature and ciphertext sizes.
use aws_lc_rs::kem::{DecapsulationKey, ML_KEM_768};
use aws_lc_rs::signature::{self, KeyPair, PqdsaKeyPair, UnparsedPublicKey};

fn main() {
    // ML-DSA-65 (FIPS 204) through the stable signature API.
    let key = PqdsaKeyPair::generate(&signature::ML_DSA_65_SIGNING).unwrap();
    let message = b"release v2.0 manifest";
    let mut sig = vec![0u8; signature::ML_DSA_65_SIGNING.signature_len()];
    let len = key.sign(message, &mut sig).unwrap();
    let ok = UnparsedPublicKey::new(&signature::ML_DSA_65, key.public_key().as_ref()).verify(message, &sig[..len]);
    println!("ML-DSA-65: public key {} bytes, signature {len} bytes, verified {}", key.public_key().as_ref().len(), ok.is_ok());

    // ML-KEM-768 (FIPS 203): the receiver publishes the encapsulation key.
    let receiver = DecapsulationKey::generate(&ML_KEM_768).unwrap();
    let (ciphertext, sender_secret) = receiver.encapsulation_key().unwrap().encapsulate().unwrap();
    let receiver_secret = receiver.decapsulate(ciphertext.as_ref().into()).unwrap();
    println!("ML-KEM-768: ciphertext {} bytes, shared secrets match {}", ciphertext.as_ref().len(), sender_secret.as_ref() == receiver_secret.as_ref());
}

The snippet harness runs every sample verbatim in the container image rust:1.99.1-trixie, and the run fails unless the output matches.

Command
cargo run --quiet --release
Output must match (regular expression)
ML-DSA-65: public key 1952 bytes, signature 3309 bytes, verified true\nML-KEM-768: ciphertext 1088 bytes, shared secrets match true

Libraries