本文へスキップ
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
公開鍵の形式
spki-derx509sec1-uncompressed
秘密鍵の形式
pkcs8-dersec1-derraw-scalar

Go は暗号機能を標準ライブラリで提供しており、Go 1.24 以降、中核となるプリミティブは FIPS 140-3 向けにビルドされた Go 暗号モジュール(Go Cryptographic Module)に含まれています。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 など生の r‖s を使う相手には、FillBytes で整数をパディングしたうえで Sign/Verify を使う必要があります。Bytes() は先頭のゼロバイトを落とすため、たまたまゼロバイトで始まる r や s は 1 バイト短くなります。

署名はランダム化されており、Go 1.26 以降は GODEBUG=cryptocustomrand=1 を設定しない限り、渡した io.Reader は無視されます。すべてゼロを返す Reader で作成した 2 つの署名は異なっていました。固定の Reader で再現可能な署名を得ていたテストは動かなくなり、ドキュメントに記載された代替手段は testing/cryptotest.SetGlobalRandom です。s は正規化されず、VerifyASN1 は low-S と high-S のどちらも受け入れるため、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 ⇄ 生形式の変換ツールで変換し、署名検証ツールで検証してください。証明書の解析の問題は、証明書デコーダーで見るのが最も簡単です。

事実とその根拠となるチェック

以下の内容はドキュメントからの転記ではありません。2026年10月20日に、チェック content/libraries/verify/go-crypto-ecdsa/run.sh がコンテナイメージ golang:1.27.3-trixie 内で Go crypto/ecdsa (standard library) 1.27.3 を実行しました。このページの内容はその出力と一致していなければならず、各事実には該当する出力行を示しています。

  • ECDSA 署名のエンコーディング

    der

    SignASN1、VerifyASN1、PrivateKey.Sign(crypto.Signer 経由)は ASN.1 DER を使います。Sign と Verify は r と s を *big.Int で受け取ります。

    チェックによる記録
    fact ecdsa-signature-encoding = der
  • ECDSA の nonce

    random

    ランダム化されています。パッケージのドキュメントにあるとおり、同じダイジェストへの 2 つの署名は異なります(エントロピーに鍵とメッセージが混ぜ合わされます)。

    チェックによる記録
    fact ecdsa-nonce = random
    evidence ecdsa-nonce: two signatures with an all-zero Reader are identical: false
  • 乱数源の引数

    ignored

    Go 1.26 以降、io.Reader 引数は無視されます。すべてゼロを返す Reader で作成した 2 つの署名は異なっていました。現時点では、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 は、生の r‖s 署名と 3 バイトのデータに対して false を返しました。

    チェックによる記録
    fact ecdsa-malformed-signature = returns-false
  • ECDSA の曲線

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

    P-224、P-256、P-384、P-521 です。crypto/elliptic には secp256k1 も Brainpool 曲線もありません。

    チェックによる記録
    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 に加えて、ed25519.Options で RFC 8032 の Ed25519ph と Ed25519ctx の各方式に対応しています。

    チェックによる記録
    fact eddsa = Ed25519, Ed25519ph, Ed25519ctx
  • Ed25519 秘密鍵の構造

    expanded-64

    ed25519.PrivateKey は 64 バイトで、シードの後に公開鍵が続きます。NewKeyFromSeed は 32 バイトのシードからこれを構築します。

    チェックによる記録
    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

    x509.ParsePKIXPublicKey による SPKI DER、証明書、ecdsa.ParseUncompressedPublicKey による非圧縮形式の SEC1 の点を読み込めます。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、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

使い方

先にハッシュを計算し、OpenSSL 系の相手向けに DER で署名し、WebCrypto と JOSE 向けに正しくパディングした生の r‖s を組み立てます。
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

ライブラリ