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

Go crypto/ecdsa (standard library)

Пакеты стандартной библиотеки Go для подписей — crypto/ecdsa вместе с crypto/x509, ed25519, mlkem и mldsa: подписи DER от дайджеста, который вычисляете вы, случайные nonce, которые игнорируют ваш Reader, только кривые NIST.
Лицензия
BSD-3-Clause
Проверенная версия
1.27.3 ·
Исходный код
github.com/golang/go
Документация
pkg.go.dev/crypto/ecdsa

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

ECDSAEdDSAПостквантовая криптография

Кривые ECDSA
P-224P-256P-384P-521
EdDSA
Ed25519Ed25519phEd25519ctx

Go поставляет криптографию в стандартной библиотеке, а начиная с Go 1.24 основные примитивы находятся в Go Cryptographic Module, созданном для FIPS 140-3. crypto/ecdsa подписывает и проверяет, crypto/x509 читает ключи и сертификаты, а в Go 1.27.3 рядом с crypto/ed25519 появились crypto/mlkem (ML-KEM-768/1024) и crypto/mldsa (ML-DSA-44/65/87). Поддержки secp256k1 нет: код для Ethereum и Bitcoin использует пакеты go-ethereum или decred.

Всё в crypto/ecdsa работает с дайджестом, который вычислили вы: пакет никогда не хеширует данные сам. Хуже неверной подписи то, что длинные входные данные молча усекаются до размера кривой: подпись над 46-байтовыми входными данными прошла проверку для других данных с теми же первыми 32 байтами. Всегда сначала хешируйте, как это делает пример. SignASN1, VerifyASN1 и интерфейс crypto.Signer работают с ASN.1 DER; для WebCrypto, JWS и других сторон, использующих raw r‖s, нужны Sign/Verify, а целые числа нужно дополнять с помощью FillBytes: Bytes() отбрасывает ведущие нулевые байты, поэтому каждое r или s, которое начинается с нулевого байта, оказывается на один байт короче.

Подписи рандомизированы, и начиная с Go 1.26 переданный io.Reader игнорируется, если не задано GODEBUG=cryptocustomrand=1: две подписи, созданные с Reader из одних нулей, различались. Тесты, которые получали воспроизводимые подписи с фиксированным Reader, перестали работать; документированная замена — testing/cryptotest.SetGlobalRandom. Значение s не нормализуется, а VerifyASN1 принимает значения из обеих половин диапазона, поэтому для проверяющих сторон, которые строго требуют low-S, s в подписи нужно заменить на n − s.

Разбор ключей рассчитан прежде всего на DER. x509.ParsePKIXPublicKey, получив текст PEM, завершается ошибкой asn1: structure error: tags don't match, поэтому сначала вызовите pem.Decode. ecdsa.ParseUncompressedPublicKey и ecdsa.ParseRawPrivateKey читают голую точку SEC1 и скаляр; сжатые точки отклоняются, а функции чтения для PKCS#8, зашифрованного парольной фразой, нет. На некорректной подписи VerifyASN1 возвращает false и никогда не возвращает ошибку.

Чтобы сравнить подпись из Go с подписью другого стека, преобразуйте её в конвертере DER ⇄ raw и проверьте в инструменте проверки подписей; проблемы с разбором сертификатов проще всего увидеть в декодере сертификатов.

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

Ничего из приведённого ниже не скопировано из документации. 20 октября 2026 г. проверка content/libraries/verify/go-crypto-ecdsa/run.sh запустила Go crypto/ecdsa (standard library) 1.27.3 в образе контейнера golang:1.27.3-trixie; содержимое страницы должно совпадать с выводом проверки, а у каждого факта приведены соответствующие строки вывода.

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

    der

    SignASN1, VerifyASN1 и PrivateKey.Sign (путь через crypto.Signer) используют ASN.1 DER; Sign и Verify принимают r и s как *big.Int.

    Что записала проверка
    fact ecdsa-signature-encoding = der
  • Nonce в ECDSA

    random

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

    Что записала проверка
    fact ecdsa-nonce = random
    evidence ecdsa-nonce: two signatures with an all-zero Reader are identical: false
  • Аргумент источника случайности

    ignored

    Начиная с Go 1.26 аргумент io.Reader игнорируется: две подписи, созданные с Reader из одних нулей, различались. Пока что GODEBUG=cryptocustomrand=1 возвращает прежнее поведение.

    Что записала проверка
    fact go-rand-reader-argument = ignored
  • Low-S при подписи

    either

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

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

    accepts-high-s

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

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

    takes-digest

    Все функции принимают дайджест; ничто в crypto/ecdsa не хеширует сообщение.

    Что записала проверка
    fact ecdsa-prehash = takes-digest
  • Дайджест длиннее порядка кривой

    truncated

    Входные данные длиннее порядка кривой усекаются: подпись над 46-байтовыми входными данными прошла проверку для других данных с теми же первыми 32 байтами.

    Что записала проверка
    fact ecdsa-long-input = truncated
    evidence ecdsa-long-input: a signature over a 46-byte input verified for a different input with the same first 32 bytes: true
  • Некорректная подпись ECDSA

    returns-false

    VerifyASN1 вернул false для подписи raw r‖s и для 3-байтового фрагмента данных.

    Что записала проверка
    fact ecdsa-malformed-signature = returns-false
  • Кривые ECDSA

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

    P-224, P-256, P-384 и P-521; кривых secp256k1 и Brainpool в crypto/elliptic нет.

    Что записала проверка
    fact ecdsa-curves = P-224, P-256, P-384, P-521
    evidence ecdsa-curves: crypto/elliptic exposes P224, P256, P384 and P521 only
  • EdDSA

    Ed25519Ed25519phEd25519ctx

    Ed25519, а также варианты Ed25519ph и Ed25519ctx из RFC 8032 через ed25519.Options.

    Что записала проверка
    fact eddsa = Ed25519, Ed25519ph, Ed25519ctx
  • Структура закрытого ключа Ed25519

    expanded-64

    ed25519.PrivateKey занимает 64 байта: seed, за которым следует открытый ключ; NewKeyFromSeed строит его из 32-байтового seed.

    Что записала проверка
    fact ed25519-private-key = expanded-64
  • Постквантовые алгоритмы

    ML-KEMML-DSA

    В Go 1.27.3 инкапсуляция и декапсуляция работают для ML-KEM-768 и ML-KEM-1024, а подпись и проверка — для ML-DSA-44/65/87.

    Что записала проверка
    fact pqc = ML-KEM, ML-DSA
    evidence pqc: crypto/mlkem offers ML-KEM-768 and ML-KEM-1024; crypto/mldsa offers ML-DSA-44, ML-DSA-65 and ML-DSA-87
  • Форматы открытых ключей

    spki-derx509sec1-uncompressed

    SPKI DER через x509.ParsePKIXPublicKey, сертификаты, а также несжатые точки SEC1 через ecdsa.ParseUncompressedPublicKey; PEM сначала нужно обработать pem.Decode, а сжатые точки отклоняются.

    Что записала проверка
    fact public-key-formats = spki-der, x509, sec1-uncompressed
    evidence public-key-formats: ParsePKIXPublicKey on PEM text: asn1: structure error: tags don't match (16 vs {class:0 tag:13 length:45 isCompound:true}) {optional:false explicit:false application:false private:false defaultValue:<nil> tag:<nil> stringType:0 timeType:0 set:false omitEmpty:false} publicKeyInfo @2
    evidence public-key-formats: ParseUncompressedPublicKey on a compressed point: ecdsa: invalid uncompressed public key
    evidence public-key-formats: ecdh.P256().NewPublicKey on a compressed point: crypto/ecdh: invalid public key
  • Форматы закрытых ключей

    pkcs8-dersec1-derraw-scalar

    PKCS#8 DER, SEC1 DER и скаляр в формате raw через ecdsa.ParseRawPrivateKey; парсера для PKCS#8, зашифрованного парольной фразой, нет.

    Что записала проверка
    fact private-key-formats = pkcs8-der, sec1-der, raw-scalar
    evidence private-key-formats: no parser for passphrase-encrypted PKCS#8 in the standard library

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

Сначала захешируйте сообщение, подпишите его в DER для сторон в стиле OpenSSL и соберите правильно дополненную подпись raw r‖s для WebCrypto и JOSE.
package main

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/sha256"
	"fmt"
	"math/big"
)

func main() {
	key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	// crypto/ecdsa never hashes: pass a digest, never the message itself.
	digest := sha256.Sum256([]byte("order #1042 approved"))

	der, _ := ecdsa.SignASN1(rand.Reader, key, digest[:]) // ASN.1 DER for OpenSSL, Java, .NET peers
	fmt.Println("DER verified:", ecdsa.VerifyASN1(&key.PublicKey, digest[:], der))

	// Raw r||s for WebCrypto and JOSE: FillBytes pads to 32 bytes; Bytes() would not.
	r, s, _ := ecdsa.Sign(rand.Reader, key, digest[:])
	raw := make([]byte, 64)
	r.FillBytes(raw[:32])
	s.FillBytes(raw[32:])
	ok := ecdsa.Verify(&key.PublicKey, digest[:], new(big.Int).SetBytes(raw[:32]), new(big.Int).SetBytes(raw[32:]))
	fmt.Println("raw verified:", ok, len(raw))
}

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

Команда
go run .
Вывод должен совпадать (регулярное выражение)
DER verified: true\nraw verified: true 64

Инструменты

Термины

Библиотеки

Статьи

Рецепты

Ошибки

Библиотеки