Skip to content
ecdsa.com
C · C++

BoringSSL

Google's BoringSSL, the OpenSSL fork used in Chrome and Android: DER ECDSA on the NIST curves only, Ed25519, and ML-KEM, ML-DSA and SLH-DSA with dedicated APIs — with no releases and no stability promise.
License
Apache-2.0
Checked version
2026-10-19-b50cad27ee80 ·
Source
boringssl.googlesource.com/boringssl
Documentation
commondatastorage.googleapis.com/chromium-boringssl-docs/headers.html

Compatibility at a glance

ECDSAEdDSAPost-quantum

ECDSA curves
P-224P-256P-384P-521
EdDSA
Ed25519
Post-quantum algorithms
ML-KEMML-DSASLH-DSA

BoringSSL is Google's fork of OpenSSL, used in Chrome and Android; AWS-LC, the library under aws-lc-rs, is in turn a fork of it, and ring reuses its assembly. It has no releases and makes no API stability promise — consumers pin a commit. The facts here were checked by building commit b50cad27ee80 (19 October 2026) from source.

ECDSA keeps the OpenSSL conventions: DER signatures, random nonces, no low-S normalization, and verification that accepts high-S; ECDSA_sign signs a digest you computed. Unlike OpenSSL, malformed input is just a failed verification — EVP_DigestVerify returned 0 for a raw r‖s signature and for a 3-byte blob, so the error queue is the only place a format problem shows up. Check encodings with the DER ⇄ raw converter.

Curves are deliberately limited to P-224, P-256, P-384 and P-521 — EC_GROUP_new_by_curve_name(NID_secp256k1) returned NULL — and Ed25519 is the only working EdDSA variant: EVP_PKEY_ED448 is defined for compatibility, but generating an Ed448 key through EVP failed. Code that moves from OpenSSL to BoringSSL loses secp256k1, Brainpool and Ed448 without a compile error until the algorithm is requested.

Post-quantum support is extensive and comes as separate, typed APIs rather than EVP: MLKEM768_* and MLKEM1024_*, MLDSA65_* and related sizes, and SLHDSA_SHA2_128S_*, all of which round-tripped in the check; ssl.h also defines the X25519MLKEM768 hybrid TLS group (0x11EC). The sample uses ML-KEM-768 and ML-DSA-65 directly. See what your browser negotiates with the browser PQ check.

Facts and the checks behind them

Nothing below is copied from documentation. The check content/libraries/verify/boringssl/run.sh ran BoringSSL 2026-10-19-b50cad27ee80 in the container image debian:trixie-20261005-slim on October 20, 2026; the page must match what it printed, and each fact lists those lines.

  • ECDSA signature encoding

    der

    EVP_DigestSign produces ASN.1 DER.

    Recorded by the check
    fact ecdsa-signature-encoding = der
  • 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

    A DER signature with s replaced by n − s verified.

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

    hashes-message-or-digest

    EVP_DigestSign hashes; ECDSA_sign signs a digest, and that signature verified against the message.

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

    returns-false

    EVP_DigestVerify returned 0 for a raw r‖s signature and for a 3-byte blob.

    Recorded by the check
    fact ecdsa-malformed-signature = returns-false
    evidence ecdsa-malformed-signature: EVP_DigestVerify returned 0 for raw r||s and 0 for 3 bytes (1 = valid, 0 = invalid)
  • ECDSA curves

    P-224P-256P-384P-521

    P-224, P-256, P-384 and P-521; there is no secp256k1 group.

    Recorded by the check
    fact ecdsa-curves = P-224, P-256, P-384, P-521
    evidence ecdsa-curves: EC_GROUP_new_by_curve_name(NID_secp256k1): NULL
  • EdDSA

    Ed25519

    Ed25519 through ED25519_sign and ED25519_verify; EVP_PKEY_ED448 is defined, but generating an Ed448 key failed.

    Recorded by the check
    fact eddsa = Ed25519
    evidence eddsa: EVP_PKEY_ED448 is defined; generating an Ed448 key through EVP: fails
  • Post-quantum algorithms

    ML-KEMML-DSASLH-DSA

    ML-KEM-768 encapsulated and decapsulated, and ML-DSA-65 and SLH-DSA-SHA2-128s signed and verified, through their dedicated headers.

    Recorded by the check
    fact pqc = ML-KEM, ML-DSA, SLH-DSA
    evidence pqc: ssl.h defines SSL_GROUP_X25519_MLKEM768 = 0x11ec
    evidence pqc: ML-DSA-65 signature 3309 bytes

Usage

Run an ML-KEM-768 encapsulation and an ML-DSA-65 signature with BoringSSL's dedicated post-quantum APIs.
#include <openssl/mldsa.h>
#include <openssl/mlkem.h>
#include <openssl/bytestring.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
    /* ML-KEM-768: the receiver publishes the encoded public key. */
    uint8_t pub[MLKEM768_PUBLIC_KEY_BYTES], ct[MLKEM768_CIPHERTEXT_BYTES];
    uint8_t ss_sender[MLKEM_SHARED_SECRET_BYTES], ss_receiver[MLKEM_SHARED_SECRET_BYTES];
    struct MLKEM768_private_key priv;
    struct MLKEM768_public_key pk;
    MLKEM768_generate_key(pub, NULL, &priv);
    CBS cbs;
    CBS_init(&cbs, pub, sizeof pub);
    if (!MLKEM768_parse_public_key(&pk, &cbs)) return 1;
    MLKEM768_encap(ct, ss_sender, &pk);
    MLKEM768_decap(ss_receiver, ct, sizeof ct, &priv);
    printf("ML-KEM-768 secrets match: %d\n", memcmp(ss_sender, ss_receiver, sizeof ss_sender) == 0);

    /* ML-DSA-65 with an empty context string. */
    static const uint8_t msg[] = "release manifest";
    uint8_t dpub[MLDSA65_PUBLIC_KEY_BYTES], seed[MLDSA_SEED_BYTES];
    struct MLDSA65_private_key *dpriv = malloc(sizeof *dpriv);
    struct MLDSA65_public_key *dpk = malloc(sizeof *dpk);
    uint8_t *sig = malloc(MLDSA65_SIGNATURE_BYTES);
    MLDSA65_generate_key(dpub, seed, dpriv);
    MLDSA65_public_from_private(dpk, dpriv);
    MLDSA65_sign(sig, dpriv, msg, sizeof msg - 1, NULL, 0);
    printf("ML-DSA-65 verified: %d\n", MLDSA65_verify(dpk, sig, MLDSA65_SIGNATURE_BYTES, msg, sizeof msg - 1, NULL, 0));
    free(dpriv); free(dpk); free(sig);
    return 0;
}

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

Setup
apt-get -qq update >/dev/null && apt-get -qq install -y --no-install-recommends build-essential cmake ninja-build git ca-certificates perl golang >/dev/null 2>&1 && git clone -q --filter=blob:none https://boringssl.googlesource.com/boringssl /bssl && git -C /bssl checkout -q b50cad27ee8013f241648ef6a7adf7f6f6b3d25b && cmake -S /bssl -B /bssl/build -G Ninja -DCMAKE_BUILD_TYPE=Release >/dev/null 2>&1 && ninja -C /bssl/build crypto >/dev/null
Command
cc -I/bssl/include -o usage usage.c /bssl/build/libcrypto.a -lstdc++ -lpthread && ./usage
Output must match (regular expression)
ML-KEM-768 secrets match: 1\nML-DSA-65 verified: 1

Libraries