Skip to content

Commit 9776d02

Browse files
FiloSottilegopherbot
authored andcommitted
crypto/ecdsa: implement deterministic and hedged signatures
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]>
1 parent 5321fc2 commit 9776d02

18 files changed

+896
-638
lines changed

src/crypto/ecdsa/ecdsa.go

+59-74
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ package ecdsa
1616

1717
import (
1818
"crypto"
19-
"crypto/aes"
20-
"crypto/cipher"
2119
"crypto/ecdh"
2220
"crypto/elliptic"
2321
"crypto/internal/boring"
@@ -131,14 +129,24 @@ func bigIntEqual(a, b *big.Int) bool {
131129
return subtle.ConstantTimeCompare(a.Bytes(), b.Bytes()) == 1
132130
}
133131

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

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

208-
csprng, err := mixedCSPRNG(rand, priv, hash)
216+
switch priv.Curve.Params() {
217+
case elliptic.P224().Params():
218+
return signFIPS(ecdsa.P224(), priv, rand, hash)
219+
case elliptic.P256().Params():
220+
return signFIPS(ecdsa.P256(), priv, rand, hash)
221+
case elliptic.P384().Params():
222+
return signFIPS(ecdsa.P384(), priv, rand, hash)
223+
case elliptic.P521().Params():
224+
return signFIPS(ecdsa.P521(), priv, rand, hash)
225+
default:
226+
return signLegacy(priv, rand, hash)
227+
}
228+
}
229+
230+
func signFIPS[P ecdsa.Point[P]](c *ecdsa.Curve[P], priv *PrivateKey, rand io.Reader, hash []byte) ([]byte, error) {
231+
// privateKeyToFIPS is very slow in FIPS mode because it performs a
232+
// Sign+Verify cycle per FIPS 140-3 IG 10.3.A. We should find a way to cache
233+
// it or attach it to the PrivateKey.
234+
k, err := privateKeyToFIPS(c, priv)
209235
if err != nil {
210236
return nil, err
211237
}
238+
// Always using SHA-512 instead of the hash that computed hash is
239+
// technically a violation of draft-irtf-cfrg-det-sigs-with-noise-04 but in
240+
// our API we don't get to know what it was, and this has no security impact.
241+
sig, err := ecdsa.Sign(c, sha512.New, k, rand, hash)
242+
if err != nil {
243+
return nil, err
244+
}
245+
return encodeSignature(sig.R, sig.S)
246+
}
212247

248+
func signRFC6979(priv *PrivateKey, hash []byte, opts crypto.SignerOpts) ([]byte, error) {
249+
if opts == nil {
250+
return nil, errors.New("ecdsa: Sign called with nil opts")
251+
}
252+
h := opts.HashFunc()
253+
if h.Size() != len(hash) {
254+
return nil, errors.New("ecdsa: hash length does not match hash function")
255+
}
213256
switch priv.Curve.Params() {
214257
case elliptic.P224().Params():
215-
return signFIPS(ecdsa.P224(), priv, csprng, hash)
258+
return signFIPSDeterministic(ecdsa.P224(), h, priv, hash)
216259
case elliptic.P256().Params():
217-
return signFIPS(ecdsa.P256(), priv, csprng, hash)
260+
return signFIPSDeterministic(ecdsa.P256(), h, priv, hash)
218261
case elliptic.P384().Params():
219-
return signFIPS(ecdsa.P384(), priv, csprng, hash)
262+
return signFIPSDeterministic(ecdsa.P384(), h, priv, hash)
220263
case elliptic.P521().Params():
221-
return signFIPS(ecdsa.P521(), priv, csprng, hash)
264+
return signFIPSDeterministic(ecdsa.P521(), h, priv, hash)
222265
default:
223-
return signLegacy(priv, csprng, hash)
266+
return nil, errors.New("ecdsa: curve not supported by deterministic signatures")
224267
}
225268
}
226269

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

269-
// mixedCSPRNG returns a CSPRNG that mixes entropy from rand with the message
270-
// and the private key, to protect the key in case rand fails. This is
271-
// equivalent in security to RFC 6979 deterministic nonce generation, but still
272-
// produces randomized signatures.
273-
func mixedCSPRNG(rand io.Reader, priv *PrivateKey, hash []byte) (io.Reader, error) {
274-
// This implementation derives the nonce from an AES-CTR CSPRNG keyed by:
275-
//
276-
// SHA2-512(priv.D || entropy || hash)[:32]
277-
//
278-
// The CSPRNG key is indifferentiable from a random oracle as shown in
279-
// [Coron], the AES-CTR stream is indifferentiable from a random oracle
280-
// under standard cryptographic assumptions (see [Larsson] for examples).
281-
//
282-
// [Coron]: https://cs.nyu.edu/~dodis/ps/merkle.pdf
283-
// [Larsson]: https://web.archive.org/web/20040719170906/https://www.nada.kth.se/kurser/kth/2D1441/semteo03/lecturenotes/assump.pdf
284-
285-
// Get 256 bits of entropy from rand.
286-
entropy := make([]byte, 32)
287-
if _, err := io.ReadFull(rand, entropy); err != nil {
288-
return nil, err
289-
}
290-
291-
// Initialize an SHA-512 hash context; digest...
292-
md := sha512.New()
293-
md.Write(priv.D.Bytes()) // the private key,
294-
md.Write(entropy) // the entropy,
295-
md.Write(hash) // and the input hash;
296-
key := md.Sum(nil)[:32] // and compute ChopMD-256(SHA-512),
297-
// which is an indifferentiable MAC.
298-
299-
// Create an AES-CTR instance to use as a CSPRNG.
300-
block, err := aes.NewCipher(key)
301-
if err != nil {
302-
return nil, err
303-
}
304-
305-
// Create a CSPRNG that xors a stream of zeros with
306-
// the output of the AES-CTR instance.
307-
const aesIV = "IV for ECDSA CTR"
308-
return &cipher.StreamReader{
309-
R: zeroReader,
310-
S: cipher.NewCTR(block, []byte(aesIV)),
311-
}, nil
312-
}
313-
314-
type zr struct{}
315-
316-
var zeroReader = zr{}
317-
318-
// Read replaces the contents of dst with zeros. It is safe for concurrent use.
319-
func (zr) Read(dst []byte) (n int, err error) {
320-
clear(dst)
321-
return len(dst), nil
322-
}
323-
324309
// VerifyASN1 verifies the ASN.1 encoded signature, sig, of hash using the
325310
// public key, pub. Its return value records whether the signature is valid.
326311
//

src/crypto/ecdsa/ecdsa_legacy.go

+14
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"errors"
1010
"io"
1111
"math/big"
12+
"math/rand/v2"
1213

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

81+
// A cheap version of hedged signatures, for the deprecated path.
82+
var seed [32]byte
83+
if _, err := io.ReadFull(csprng, seed[:]); err != nil {
84+
return nil, err
85+
}
86+
for i, b := range priv.D.Bytes() {
87+
seed[i%32] ^= b
88+
}
89+
for i, b := range hash {
90+
seed[i%32] ^= b
91+
}
92+
csprng = rand.NewChaCha8(seed)
93+
8094
// SEC 1, Version 2.0, Section 4.1.3
8195
N := c.Params().N
8296
if N.Sign() == 0 {

src/crypto/ecdsa/ecdsa_test.go

+121
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ package ecdsa
66

77
import (
88
"bufio"
9+
"bytes"
910
"compress/bzip2"
11+
"crypto"
1012
"crypto/elliptic"
1113
"crypto/internal/cryptotest"
1214
"crypto/rand"
@@ -149,6 +151,15 @@ func testNonceSafety(t *testing.T, c elliptic.Curve) {
149151
}
150152
}
151153

154+
type readerFunc func([]byte) (int, error)
155+
156+
func (f readerFunc) Read(b []byte) (int, error) { return f(b) }
157+
158+
var zeroReader = readerFunc(func(b []byte) (int, error) {
159+
clear(b)
160+
return len(b), nil
161+
})
162+
152163
func TestINDCCA(t *testing.T) {
153164
testAllCurves(t, testINDCCA)
154165
}
@@ -425,6 +436,116 @@ func testRMinusNSignature(t *testing.T, curve elliptic.Curve) {
425436
}
426437
}
427438

439+
func TestRFC6979(t *testing.T) {
440+
t.Run("P-224", func(t *testing.T) {
441+
testRFC6979(t, elliptic.P224(),
442+
"F220266E1105BFE3083E03EC7A3A654651F45E37167E88600BF257C1",
443+
"00CF08DA5AD719E42707FA431292DEA11244D64FC51610D94B130D6C",
444+
"EEAB6F3DEBE455E3DBF85416F7030CBD94F34F2D6F232C69F3C1385A",
445+
"sample",
446+
"61AA3DA010E8E8406C656BC477A7A7189895E7E840CDFE8FF42307BA",
447+
"BC814050DAB5D23770879494F9E0A680DC1AF7161991BDE692B10101")
448+
testRFC6979(t, elliptic.P224(),
449+
"F220266E1105BFE3083E03EC7A3A654651F45E37167E88600BF257C1",
450+
"00CF08DA5AD719E42707FA431292DEA11244D64FC51610D94B130D6C",
451+
"EEAB6F3DEBE455E3DBF85416F7030CBD94F34F2D6F232C69F3C1385A",
452+
"test",
453+
"AD04DDE87B84747A243A631EA47A1BA6D1FAA059149AD2440DE6FBA6",
454+
"178D49B1AE90E3D8B629BE3DB5683915F4E8C99FDF6E666CF37ADCFD")
455+
})
456+
t.Run("P-256", func(t *testing.T) {
457+
// This vector was bruteforced to find a message that causes the
458+
// generation of k to loop. It was checked against
459+
// github.com/codahale/rfc6979 (https://go.dev/play/p/FK5-fmKf7eK),
460+
// OpenSSL 3.2.0 (https://github.com/openssl/openssl/pull/23130),
461+
// and python-ecdsa:
462+
//
463+
// ecdsa.keys.SigningKey.from_secret_exponent(
464+
// 0xC9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721,
465+
// ecdsa.curves.curve_by_name("NIST256p"), hashlib.sha256).sign_deterministic(
466+
// b"wv[vnX", hashlib.sha256, lambda r, s, order: print(hex(r), hex(s)))
467+
//
468+
testRFC6979(t, elliptic.P256(),
469+
"C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721",
470+
"60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6",
471+
"7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299",
472+
"wv[vnX",
473+
"EFD9073B652E76DA1B5A019C0E4A2E3FA529B035A6ABB91EF67F0ED7A1F21234",
474+
"3DB4706C9D9F4A4FE13BB5E08EF0FAB53A57DBAB2061C83A35FA411C68D2BA33")
475+
476+
// The remaining vectors are from RFC 6979.
477+
testRFC6979(t, elliptic.P256(),
478+
"C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721",
479+
"60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6",
480+
"7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299",
481+
"sample",
482+
"EFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716",
483+
"F7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8")
484+
testRFC6979(t, elliptic.P256(),
485+
"C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721",
486+
"60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6",
487+
"7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299",
488+
"test",
489+
"F1ABB023518351CD71D881567B1EA663ED3EFCF6C5132B354F28D3B0B7D38367",
490+
"019F4113742A2B14BD25926B49C649155F267E60D3814B4C0CC84250E46F0083")
491+
})
492+
t.Run("P-384", func(t *testing.T) {
493+
testRFC6979(t, elliptic.P384(),
494+
"6B9D3DAD2E1B8C1C05B19875B6659F4DE23C3B667BF297BA9AA47740787137D896D5724E4C70A825F872C9EA60D2EDF5",
495+
"EC3A4E415B4E19A4568618029F427FA5DA9A8BC4AE92E02E06AAE5286B300C64DEF8F0EA9055866064A254515480BC13",
496+
"8015D9B72D7D57244EA8EF9AC0C621896708A59367F9DFB9F54CA84B3F1C9DB1288B231C3AE0D4FE7344FD2533264720",
497+
"sample",
498+
"21B13D1E013C7FA1392D03C5F99AF8B30C570C6F98D4EA8E354B63A21D3DAA33BDE1E888E63355D92FA2B3C36D8FB2CD",
499+
"F3AA443FB107745BF4BD77CB3891674632068A10CA67E3D45DB2266FA7D1FEEBEFDC63ECCD1AC42EC0CB8668A4FA0AB0")
500+
testRFC6979(t, elliptic.P384(),
501+
"6B9D3DAD2E1B8C1C05B19875B6659F4DE23C3B667BF297BA9AA47740787137D896D5724E4C70A825F872C9EA60D2EDF5",
502+
"EC3A4E415B4E19A4568618029F427FA5DA9A8BC4AE92E02E06AAE5286B300C64DEF8F0EA9055866064A254515480BC13",
503+
"8015D9B72D7D57244EA8EF9AC0C621896708A59367F9DFB9F54CA84B3F1C9DB1288B231C3AE0D4FE7344FD2533264720",
504+
"test",
505+
"6D6DEFAC9AB64DABAFE36C6BF510352A4CC27001263638E5B16D9BB51D451559F918EEDAF2293BE5B475CC8F0188636B",
506+
"2D46F3BECBCC523D5F1A1256BF0C9B024D879BA9E838144C8BA6BAEB4B53B47D51AB373F9845C0514EEFB14024787265")
507+
})
508+
t.Run("P-521", func(t *testing.T) {
509+
testRFC6979(t, elliptic.P521(),
510+
"0FAD06DAA62BA3B25D2FB40133DA757205DE67F5BB0018FEE8C86E1B68C7E75CAA896EB32F1F47C70855836A6D16FCC1466F6D8FBEC67DB89EC0C08B0E996B83538",
511+
"1894550D0785932E00EAA23B694F213F8C3121F86DC97A04E5A7167DB4E5BCD371123D46E45DB6B5D5370A7F20FB633155D38FFA16D2BD761DCAC474B9A2F5023A4",
512+
"0493101C962CD4D2FDDF782285E64584139C2F91B47F87FF82354D6630F746A28A0DB25741B5B34A828008B22ACC23F924FAAFBD4D33F81EA66956DFEAA2BFDFCF5",
513+
"sample",
514+
"1511BB4D675114FE266FC4372B87682BAECC01D3CC62CF2303C92B3526012659D16876E25C7C1E57648F23B73564D67F61C6F14D527D54972810421E7D87589E1A7",
515+
"04A171143A83163D6DF460AAF61522695F207A58B95C0644D87E52AA1A347916E4F7A72930B1BC06DBE22CE3F58264AFD23704CBB63B29B931F7DE6C9D949A7ECFC")
516+
testRFC6979(t, elliptic.P521(),
517+
"0FAD06DAA62BA3B25D2FB40133DA757205DE67F5BB0018FEE8C86E1B68C7E75CAA896EB32F1F47C70855836A6D16FCC1466F6D8FBEC67DB89EC0C08B0E996B83538",
518+
"1894550D0785932E00EAA23B694F213F8C3121F86DC97A04E5A7167DB4E5BCD371123D46E45DB6B5D5370A7F20FB633155D38FFA16D2BD761DCAC474B9A2F5023A4",
519+
"0493101C962CD4D2FDDF782285E64584139C2F91B47F87FF82354D6630F746A28A0DB25741B5B34A828008B22ACC23F924FAAFBD4D33F81EA66956DFEAA2BFDFCF5",
520+
"test",
521+
"00E871C4A14F993C6C7369501900C4BC1E9C7B0B4BA44E04868B30B41D8071042EB28C4C250411D0CE08CD197E4188EA4876F279F90B3D8D74A3C76E6F1E4656AA8",
522+
"0CD52DBAA33B063C3A6CD8058A1FB0A46A4754B034FCC644766CA14DA8CA5CA9FDE00E88C1AD60CCBA759025299079D7A427EC3CC5B619BFBC828E7769BCD694E86")
523+
})
524+
}
525+
526+
func testRFC6979(t *testing.T, curve elliptic.Curve, D, X, Y, msg, r, s string) {
527+
priv := &PrivateKey{
528+
D: fromHex(D),
529+
PublicKey: PublicKey{
530+
Curve: curve,
531+
X: fromHex(X),
532+
Y: fromHex(Y),
533+
},
534+
}
535+
h := sha256.Sum256([]byte(msg))
536+
sig, err := priv.Sign(nil, h[:], crypto.SHA256)
537+
if err != nil {
538+
t.Fatal(err)
539+
}
540+
expected, err := encodeSignature(fromHex(r).Bytes(), fromHex(s).Bytes())
541+
if err != nil {
542+
t.Fatal(err)
543+
}
544+
if !bytes.Equal(sig, expected) {
545+
t.Errorf("signature mismatch:\n got: %x\nwant: %x", sig, expected)
546+
}
547+
}
548+
428549
func benchmarkAllCurves(b *testing.B, f func(*testing.B, elliptic.Curve)) {
429550
tests := []struct {
430551
name string

0 commit comments

Comments
 (0)