Перейти к содержимому
ecdsa.com
Rust

ring

ring от Brian Smith — криптографическая библиотека для Rust, на которой работают rustls и многие веб-серверы: намеренно небольшой API, где константа алгоритма задаёт кодировку подписи, закрытые ключи принимаются только в PKCS#8, а из кривых доступны P-256, P-384 и Ed25519.
Лицензия
Apache-2.0 AND ISC
Проверенная версия
0.17.14 ·
Исходный код
github.com/briansmith/ring
Документация
docs.rs/ring/0.17.14/ring/

Совместимость вкратце

ECDSAEdDSA

EdDSA
Ed25519

ring предлагает короткий список алгоритмов с ассемблерным кодом, унаследованным от BoringSSL, и API, устойчивый к неправильному использованию: в нём нельзя выбрать nonce, подписать заранее вычисленный дайджест или загрузить ключ в необычном формате. Поэтому библиотека быстрая и её трудно применить неправильно, а основная работа уходит на преобразование ключей и подписей из других стеков.

Кодировка входит в алгоритм: ECDSA_P256_SHA256_FIXED_SIGNING создаёт 64-байтовую подпись raw r‖s, а ECDSA_P256_SHA256_ASN1_SIGNING — DER, и для проверки есть соответствующие константы _FIXED и _ASN1. Подпись, которую проверяют с другой константой, просто не проходит проверку с ошибкой Unspecified, поэтому подпись JWS, проверенная с константой ASN1, или подпись OpenSSL, проверенная с FIXED, выглядит точно так же, как подделка. Пример показывает оба результата. При подписи используется случайный nonce, s не нормализуется, а проверка принимает s из обеих половин диапазона.

Закрытые ключи читаются только в формате PKCS#8 DER, а ключи Ed25519 должны быть в PKCS#8 v2 с открытым ключом внутри: метод Ed25519KeyPair::from_pkcs8 отклонил 48-байтовую форму v1 (её записывают OpenSSL и большинство инструментов) с ошибкой VersionNotSupported, тогда как from_pkcs8_maybe_unchecked и from_seed_unchecked её приняли. Закрытые ключи PEM, SEC1 и JWK сначала нужно преобразовать, например в конвертере JWK. Открытые ключи — это точки SEC1 без обёртки: public_key() возвращает 65-байтовую несжатую форму, а сжатые точки не принимаются.

Список кривых ограничен P-256 и P-384, плюс Ed25519; P-521, secp256k1 и постквантовых алгоритмов нет. Проекты, которым они нужны или нужна сертификация FIPS, переходят на aws-lc-rs с API, совместимым с ring.

Чтобы узнать, в какой кодировке подпись из ring, вставьте её в конвертер DER ⇄ raw.

Факты и проверки, на которых они основаны

Ничего из приведённого ниже не скопировано из документации. 20 октября 2026 г. проверка content/libraries/verify/ring/run.sh запустила ring 0.17.14 в образе контейнера rust:1.99.1-trixie; содержимое страницы должно совпадать с выводом проверки, а у каждого факта приведены соответствующие строки вывода.

  • Кодировка подписи ECDSA

    der-or-raw

    Кодировку выбирает константа алгоритма: *_FIXED подписывает и проверяет 64-байтовые подписи raw r‖s, *_ASN1 — DER; с другой константой подпись не проходит проверку.

    Что записала проверка
    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 в ECDSA

    random

    Случайный nonce: две подписи одного сообщения различаются, а детерминированного режима нет.

    Что записала проверка
    fact ecdsa-nonce = random
  • Low-S при подписи

    either

    s не нормализуется: среди 64 подписей встретились значения high-S.

    Что записала проверка
    fact ecdsa-low-s-sign = either
  • High-S при проверке

    accepts-high-s

    Проверка с константой FIXED приняла подпись, в которой s заменили на n − s.

    Что записала проверка
    fact ecdsa-low-s-verify = accepts-high-s
  • Что хеширует вызов подписи

    hashes-message

    Сообщение хешируется так, как задаёт алгоритм; API для подписи заранее вычисленного дайджеста нет.

    Что записала проверка
    fact ecdsa-prehash = hashes-message
    evidence ecdsa-prehash: the API has no entry point for signing a precomputed digest
  • Некорректная подпись ECDSA

    returns-error

    Для некорректной или неверной подписи verify() возвращает Err(Unspecified) без подробностей.

    Что записала проверка
    fact ecdsa-malformed-signature = returns-error
    evidence ecdsa-malformed-signature: verify() of 3 bytes: Err(Unspecified)
  • Кривые ECDSA

    P-256P-384

    Только P-256 и P-384.

    Что записала проверка
    fact ecdsa-curves = P-256, P-384
    evidence ecdsa-curves: ring defines ECDSA_P256_* and ECDSA_P384_* algorithms only
  • EdDSA

    Ed25519

    Ed25519.

    Что записала проверка
    fact eddsa = Ed25519
  • Ключи Ed25519 в PKCS #8 v1

    rejected-by-from-pkcs8

    from_pkcs8 отклонил 48-байтовый ключ Ed25519 в формате PKCS#8 v1 с ошибкой VersionNotSupported; from_pkcs8_maybe_unchecked и from_seed_unchecked его приняли. generate_pkcs8 записывает 83-байтовые ключи v2.

    Что записала проверка
    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
  • Постквантовые алгоритмы

    none

    Постквантовых алгоритмов нет.

    Что записала проверка
    fact pqc = none
  • Форматы открытых ключей

    sec1-uncompressed

    Открытые ключи — несжатые точки SEC1 (65 байт для P-256); сжатые точки не приняты, а разбора SPKI, PEM и JWK нет.

    Что записала проверка
    fact public-key-formats = sec1-uncompressed
    evidence public-key-formats: public_key() returns 65 bytes (uncompressed SEC1); no SPKI, PEM or JWK parsing
  • Форматы закрытых ключей

    pkcs8-der

    Закрытые ключи загружаются только из PKCS#8 DER.

    Что записала проверка
    fact private-key-formats = pkcs8-der

Использование

Подпишите с константой FIXED (raw) и убедитесь, что только соответствующая константа проверки принимает эту подпись.
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());
}

Тестовый стенд для фрагментов кода запускает каждый пример без изменений в образе контейнера rust:1.99.1-trixie, и запуск завершается ошибкой, если вывод не совпадает.

Команда
cargo run --quiet --release
Вывод должен совпадать (регулярное выражение)
raw bytes: 64, verified: true, verified as DER: false

Библиотеки