Skip to content

Commit

Permalink
crypto/ecdsa: implement deterministic and hedged signatures
Browse files Browse the repository at this point in the history
For the future, some test vectors we should generate and then share
through Wycheproof or CCTV:
 - A private key with a leading zero byte.
 - A hash longer than the modulus.
 - A hash longer than the P-521 modulus by a few bits.
 - Reductions happening in hashToNat and bits2octets.

Fixes #64802

Change-Id: Ia0f89781b2c78eedd5103cf0e9720630711c37ad
Reviewed-on: https://go-review.googlesource.com/c/go/+/628681
TryBot-Bypass: Filippo Valsorda <[email protected]>
Reviewed-by: Russ Cox <[email protected]>
Reviewed-by: Dmitri Shuralyov <[email protected]>
Auto-Submit: Filippo Valsorda <[email protected]>
  • Loading branch information
FiloSottile authored and gopherbot committed Nov 19, 2024
1 parent 5321fc2 commit 9776d02
Show file tree
Hide file tree
Showing 18 changed files with 896 additions and 638 deletions.
133 changes: 59 additions & 74 deletions src/crypto/ecdsa/ecdsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ package ecdsa

import (
"crypto"
"crypto/aes"
"crypto/cipher"
"crypto/ecdh"
"crypto/elliptic"
"crypto/internal/boring"
Expand Down Expand Up @@ -131,14 +129,24 @@ func bigIntEqual(a, b *big.Int) bool {
return subtle.ConstantTimeCompare(a.Bytes(), b.Bytes()) == 1
}

// Sign signs digest with priv, reading randomness from rand. The opts argument
// is not currently used but, in keeping with the crypto.Signer interface,
// should be the hash function used to digest the message.
// Sign signs a hash (which should be the result of hashing a larger message
// with opts.HashFunc()) using the private key, priv. If the hash is longer than
// the bit-length of the private key's curve order, the hash will be truncated
// to that length. It returns the ASN.1 encoded signature, like [SignASN1].
//
// This method implements crypto.Signer, which is an interface to support keys
// where the private part is kept in, for example, a hardware module. Common
// uses can use the [SignASN1] function in this package directly.
// If rand is not nil, the signature is randomized. Most applications should use
// [crypto/rand.Reader] as rand. Note that the returned signature does not
// depend deterministically on the bytes read from rand, and may change between
// calls and/or between versions.
//
// If rand is nil, Sign will produce a deterministic signature according to RFC
// 6979. When producing a deterministic signature, opts.HashFunc() must be the
// function used to produce digest and priv.Curve must be one of
// [elliptic.P224], [elliptic.P256], [elliptic.P384], or [elliptic.P521].
func (priv *PrivateKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
if rand == nil {
return signRFC6979(priv, digest, opts)
}
return SignASN1(rand, priv, digest)
}

Expand Down Expand Up @@ -205,34 +213,66 @@ func SignASN1(rand io.Reader, priv *PrivateKey, hash []byte) ([]byte, error) {
}
boring.UnreachableExceptTests()

csprng, err := mixedCSPRNG(rand, priv, hash)
switch priv.Curve.Params() {
case elliptic.P224().Params():
return signFIPS(ecdsa.P224(), priv, rand, hash)
case elliptic.P256().Params():
return signFIPS(ecdsa.P256(), priv, rand, hash)
case elliptic.P384().Params():
return signFIPS(ecdsa.P384(), priv, rand, hash)
case elliptic.P521().Params():
return signFIPS(ecdsa.P521(), priv, rand, hash)
default:
return signLegacy(priv, rand, hash)
}
}

func signFIPS[P ecdsa.Point[P]](c *ecdsa.Curve[P], priv *PrivateKey, rand io.Reader, hash []byte) ([]byte, error) {
// privateKeyToFIPS is very slow in FIPS mode because it performs a
// Sign+Verify cycle per FIPS 140-3 IG 10.3.A. We should find a way to cache
// it or attach it to the PrivateKey.
k, err := privateKeyToFIPS(c, priv)
if err != nil {
return nil, err
}
// Always using SHA-512 instead of the hash that computed hash is
// technically a violation of draft-irtf-cfrg-det-sigs-with-noise-04 but in
// our API we don't get to know what it was, and this has no security impact.
sig, err := ecdsa.Sign(c, sha512.New, k, rand, hash)
if err != nil {
return nil, err
}
return encodeSignature(sig.R, sig.S)
}

func signRFC6979(priv *PrivateKey, hash []byte, opts crypto.SignerOpts) ([]byte, error) {
if opts == nil {
return nil, errors.New("ecdsa: Sign called with nil opts")
}
h := opts.HashFunc()
if h.Size() != len(hash) {
return nil, errors.New("ecdsa: hash length does not match hash function")
}
switch priv.Curve.Params() {
case elliptic.P224().Params():
return signFIPS(ecdsa.P224(), priv, csprng, hash)
return signFIPSDeterministic(ecdsa.P224(), h, priv, hash)
case elliptic.P256().Params():
return signFIPS(ecdsa.P256(), priv, csprng, hash)
return signFIPSDeterministic(ecdsa.P256(), h, priv, hash)
case elliptic.P384().Params():
return signFIPS(ecdsa.P384(), priv, csprng, hash)
return signFIPSDeterministic(ecdsa.P384(), h, priv, hash)
case elliptic.P521().Params():
return signFIPS(ecdsa.P521(), priv, csprng, hash)
return signFIPSDeterministic(ecdsa.P521(), h, priv, hash)
default:
return signLegacy(priv, csprng, hash)
return nil, errors.New("ecdsa: curve not supported by deterministic signatures")
}
}

func signFIPS[P ecdsa.Point[P]](c *ecdsa.Curve[P], priv *PrivateKey, csprng io.Reader, hash []byte) ([]byte, error) {
// privateKeyToFIPS is very slow in FIPS mode because it performs a
// Sign+Verify cycle per FIPS 140-3 IG 10.3.A. We should find a way to cache
// it or attach it to the PrivateKey.
func signFIPSDeterministic[P ecdsa.Point[P]](c *ecdsa.Curve[P], hashFunc crypto.Hash, priv *PrivateKey, hash []byte) ([]byte, error) {
k, err := privateKeyToFIPS(c, priv)
if err != nil {
return nil, err
}
sig, err := ecdsa.Sign(c, k, csprng, hash)
sig, err := ecdsa.SignDeterministic(c, hashFunc.New, k, hash)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -266,61 +306,6 @@ func addASN1IntBytes(b *cryptobyte.Builder, bytes []byte) {
})
}

// mixedCSPRNG returns a CSPRNG that mixes entropy from rand with the message
// and the private key, to protect the key in case rand fails. This is
// equivalent in security to RFC 6979 deterministic nonce generation, but still
// produces randomized signatures.
func mixedCSPRNG(rand io.Reader, priv *PrivateKey, hash []byte) (io.Reader, error) {
// This implementation derives the nonce from an AES-CTR CSPRNG keyed by:
//
// SHA2-512(priv.D || entropy || hash)[:32]
//
// The CSPRNG key is indifferentiable from a random oracle as shown in
// [Coron], the AES-CTR stream is indifferentiable from a random oracle
// under standard cryptographic assumptions (see [Larsson] for examples).
//
// [Coron]: https://cs.nyu.edu/~dodis/ps/merkle.pdf
// [Larsson]: https://web.archive.org/web/20040719170906/https://www.nada.kth.se/kurser/kth/2D1441/semteo03/lecturenotes/assump.pdf

// Get 256 bits of entropy from rand.
entropy := make([]byte, 32)
if _, err := io.ReadFull(rand, entropy); err != nil {
return nil, err
}

// Initialize an SHA-512 hash context; digest...
md := sha512.New()
md.Write(priv.D.Bytes()) // the private key,
md.Write(entropy) // the entropy,
md.Write(hash) // and the input hash;
key := md.Sum(nil)[:32] // and compute ChopMD-256(SHA-512),
// which is an indifferentiable MAC.

// Create an AES-CTR instance to use as a CSPRNG.
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}

// Create a CSPRNG that xors a stream of zeros with
// the output of the AES-CTR instance.
const aesIV = "IV for ECDSA CTR"
return &cipher.StreamReader{
R: zeroReader,
S: cipher.NewCTR(block, []byte(aesIV)),
}, nil
}

type zr struct{}

var zeroReader = zr{}

// Read replaces the contents of dst with zeros. It is safe for concurrent use.
func (zr) Read(dst []byte) (n int, err error) {
clear(dst)
return len(dst), nil
}

// VerifyASN1 verifies the ASN.1 encoded signature, sig, of hash using the
// public key, pub. Its return value records whether the signature is valid.
//
Expand Down
14 changes: 14 additions & 0 deletions src/crypto/ecdsa/ecdsa_legacy.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"errors"
"io"
"math/big"
"math/rand/v2"

"golang.org/x/crypto/cryptobyte"
"golang.org/x/crypto/cryptobyte/asn1"
Expand Down Expand Up @@ -77,6 +78,19 @@ func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err err
func signLegacy(priv *PrivateKey, csprng io.Reader, hash []byte) (sig []byte, err error) {
c := priv.Curve

// A cheap version of hedged signatures, for the deprecated path.
var seed [32]byte
if _, err := io.ReadFull(csprng, seed[:]); err != nil {
return nil, err
}
for i, b := range priv.D.Bytes() {
seed[i%32] ^= b
}
for i, b := range hash {
seed[i%32] ^= b
}
csprng = rand.NewChaCha8(seed)

// SEC 1, Version 2.0, Section 4.1.3
N := c.Params().N
if N.Sign() == 0 {
Expand Down
121 changes: 121 additions & 0 deletions src/crypto/ecdsa/ecdsa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ package ecdsa

import (
"bufio"
"bytes"
"compress/bzip2"
"crypto"
"crypto/elliptic"
"crypto/internal/cryptotest"
"crypto/rand"
Expand Down Expand Up @@ -149,6 +151,15 @@ func testNonceSafety(t *testing.T, c elliptic.Curve) {
}
}

type readerFunc func([]byte) (int, error)

func (f readerFunc) Read(b []byte) (int, error) { return f(b) }

var zeroReader = readerFunc(func(b []byte) (int, error) {
clear(b)
return len(b), nil
})

func TestINDCCA(t *testing.T) {
testAllCurves(t, testINDCCA)
}
Expand Down Expand Up @@ -425,6 +436,116 @@ func testRMinusNSignature(t *testing.T, curve elliptic.Curve) {
}
}

func TestRFC6979(t *testing.T) {
t.Run("P-224", func(t *testing.T) {
testRFC6979(t, elliptic.P224(),
"F220266E1105BFE3083E03EC7A3A654651F45E37167E88600BF257C1",
"00CF08DA5AD719E42707FA431292DEA11244D64FC51610D94B130D6C",
"EEAB6F3DEBE455E3DBF85416F7030CBD94F34F2D6F232C69F3C1385A",
"sample",
"61AA3DA010E8E8406C656BC477A7A7189895E7E840CDFE8FF42307BA",
"BC814050DAB5D23770879494F9E0A680DC1AF7161991BDE692B10101")
testRFC6979(t, elliptic.P224(),
"F220266E1105BFE3083E03EC7A3A654651F45E37167E88600BF257C1",
"00CF08DA5AD719E42707FA431292DEA11244D64FC51610D94B130D6C",
"EEAB6F3DEBE455E3DBF85416F7030CBD94F34F2D6F232C69F3C1385A",
"test",
"AD04DDE87B84747A243A631EA47A1BA6D1FAA059149AD2440DE6FBA6",
"178D49B1AE90E3D8B629BE3DB5683915F4E8C99FDF6E666CF37ADCFD")
})
t.Run("P-256", func(t *testing.T) {
// This vector was bruteforced to find a message that causes the
// generation of k to loop. It was checked against
// github.com/codahale/rfc6979 (https://go.dev/play/p/FK5-fmKf7eK),
// OpenSSL 3.2.0 (https://github.com/openssl/openssl/pull/23130),
// and python-ecdsa:
//
// ecdsa.keys.SigningKey.from_secret_exponent(
// 0xC9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721,
// ecdsa.curves.curve_by_name("NIST256p"), hashlib.sha256).sign_deterministic(
// b"wv[vnX", hashlib.sha256, lambda r, s, order: print(hex(r), hex(s)))
//
testRFC6979(t, elliptic.P256(),
"C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721",
"60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6",
"7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299",
"wv[vnX",
"EFD9073B652E76DA1B5A019C0E4A2E3FA529B035A6ABB91EF67F0ED7A1F21234",
"3DB4706C9D9F4A4FE13BB5E08EF0FAB53A57DBAB2061C83A35FA411C68D2BA33")

// The remaining vectors are from RFC 6979.
testRFC6979(t, elliptic.P256(),
"C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721",
"60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6",
"7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299",
"sample",
"EFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716",
"F7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8")
testRFC6979(t, elliptic.P256(),
"C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721",
"60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6",
"7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299",
"test",
"F1ABB023518351CD71D881567B1EA663ED3EFCF6C5132B354F28D3B0B7D38367",
"019F4113742A2B14BD25926B49C649155F267E60D3814B4C0CC84250E46F0083")
})
t.Run("P-384", func(t *testing.T) {
testRFC6979(t, elliptic.P384(),
"6B9D3DAD2E1B8C1C05B19875B6659F4DE23C3B667BF297BA9AA47740787137D896D5724E4C70A825F872C9EA60D2EDF5",
"EC3A4E415B4E19A4568618029F427FA5DA9A8BC4AE92E02E06AAE5286B300C64DEF8F0EA9055866064A254515480BC13",
"8015D9B72D7D57244EA8EF9AC0C621896708A59367F9DFB9F54CA84B3F1C9DB1288B231C3AE0D4FE7344FD2533264720",
"sample",
"21B13D1E013C7FA1392D03C5F99AF8B30C570C6F98D4EA8E354B63A21D3DAA33BDE1E888E63355D92FA2B3C36D8FB2CD",
"F3AA443FB107745BF4BD77CB3891674632068A10CA67E3D45DB2266FA7D1FEEBEFDC63ECCD1AC42EC0CB8668A4FA0AB0")
testRFC6979(t, elliptic.P384(),
"6B9D3DAD2E1B8C1C05B19875B6659F4DE23C3B667BF297BA9AA47740787137D896D5724E4C70A825F872C9EA60D2EDF5",
"EC3A4E415B4E19A4568618029F427FA5DA9A8BC4AE92E02E06AAE5286B300C64DEF8F0EA9055866064A254515480BC13",
"8015D9B72D7D57244EA8EF9AC0C621896708A59367F9DFB9F54CA84B3F1C9DB1288B231C3AE0D4FE7344FD2533264720",
"test",
"6D6DEFAC9AB64DABAFE36C6BF510352A4CC27001263638E5B16D9BB51D451559F918EEDAF2293BE5B475CC8F0188636B",
"2D46F3BECBCC523D5F1A1256BF0C9B024D879BA9E838144C8BA6BAEB4B53B47D51AB373F9845C0514EEFB14024787265")
})
t.Run("P-521", func(t *testing.T) {
testRFC6979(t, elliptic.P521(),
"0FAD06DAA62BA3B25D2FB40133DA757205DE67F5BB0018FEE8C86E1B68C7E75CAA896EB32F1F47C70855836A6D16FCC1466F6D8FBEC67DB89EC0C08B0E996B83538",
"1894550D0785932E00EAA23B694F213F8C3121F86DC97A04E5A7167DB4E5BCD371123D46E45DB6B5D5370A7F20FB633155D38FFA16D2BD761DCAC474B9A2F5023A4",
"0493101C962CD4D2FDDF782285E64584139C2F91B47F87FF82354D6630F746A28A0DB25741B5B34A828008B22ACC23F924FAAFBD4D33F81EA66956DFEAA2BFDFCF5",
"sample",
"1511BB4D675114FE266FC4372B87682BAECC01D3CC62CF2303C92B3526012659D16876E25C7C1E57648F23B73564D67F61C6F14D527D54972810421E7D87589E1A7",
"04A171143A83163D6DF460AAF61522695F207A58B95C0644D87E52AA1A347916E4F7A72930B1BC06DBE22CE3F58264AFD23704CBB63B29B931F7DE6C9D949A7ECFC")
testRFC6979(t, elliptic.P521(),
"0FAD06DAA62BA3B25D2FB40133DA757205DE67F5BB0018FEE8C86E1B68C7E75CAA896EB32F1F47C70855836A6D16FCC1466F6D8FBEC67DB89EC0C08B0E996B83538",
"1894550D0785932E00EAA23B694F213F8C3121F86DC97A04E5A7167DB4E5BCD371123D46E45DB6B5D5370A7F20FB633155D38FFA16D2BD761DCAC474B9A2F5023A4",
"0493101C962CD4D2FDDF782285E64584139C2F91B47F87FF82354D6630F746A28A0DB25741B5B34A828008B22ACC23F924FAAFBD4D33F81EA66956DFEAA2BFDFCF5",
"test",
"00E871C4A14F993C6C7369501900C4BC1E9C7B0B4BA44E04868B30B41D8071042EB28C4C250411D0CE08CD197E4188EA4876F279F90B3D8D74A3C76E6F1E4656AA8",
"0CD52DBAA33B063C3A6CD8058A1FB0A46A4754B034FCC644766CA14DA8CA5CA9FDE00E88C1AD60CCBA759025299079D7A427EC3CC5B619BFBC828E7769BCD694E86")
})
}

func testRFC6979(t *testing.T, curve elliptic.Curve, D, X, Y, msg, r, s string) {
priv := &PrivateKey{
D: fromHex(D),
PublicKey: PublicKey{
Curve: curve,
X: fromHex(X),
Y: fromHex(Y),
},
}
h := sha256.Sum256([]byte(msg))
sig, err := priv.Sign(nil, h[:], crypto.SHA256)
if err != nil {
t.Fatal(err)
}
expected, err := encodeSignature(fromHex(r).Bytes(), fromHex(s).Bytes())
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(sig, expected) {
t.Errorf("signature mismatch:\n got: %x\nwant: %x", sig, expected)
}
}

func benchmarkAllCurves(b *testing.B, f func(*testing.B, elliptic.Curve)) {
tests := []struct {
name string
Expand Down
Loading

0 comments on commit 9776d02

Please sign in to comment.