From 8041c5e3104cc692d27ecf3f51ec574d9db18e2f Mon Sep 17 00:00:00 2001 From: Sun Yimin Date: Tue, 27 Jun 2023 08:57:31 +0800 Subject: [PATCH] sm2: remove CSPRNG usage --- sm2/sm2.go | 92 +++++++++++-------------------------------- sm2/sm2_legacy.go | 4 +- sm2/sm2_test.go | 31 --------------- sm9/sm9.go | 26 ++++++++++++ smx509/verify_test.go | 19 +++++---- 5 files changed, 60 insertions(+), 112 deletions(-) diff --git a/sm2/sm2.go b/sm2/sm2.go index 12d0e69..6f513de 100644 --- a/sm2/sm2.go +++ b/sm2/sm2.go @@ -11,11 +11,8 @@ package sm2 import ( "crypto" - "crypto/aes" - "crypto/cipher" "crypto/ecdsa" "crypto/elliptic" - "crypto/sha512" _subtle "crypto/subtle" "errors" "fmt" @@ -203,11 +200,19 @@ var ( ) // EncryptASN1 sm2 encrypt and output ASN.1 result, compliance with GB/T 32918.4-2016. +// +// The random parameter is used as a source of entropy to ensure that +// encrypting the same message twice doesn't result in the same ciphertext. +// Most applications should use [crypto/rand.Reader] as random. func EncryptASN1(random io.Reader, pub *ecdsa.PublicKey, msg []byte) ([]byte, error) { return Encrypt(random, pub, msg, ASN1EncrypterOpts) } // Encrypt sm2 encrypt implementation, compliance with GB/T 32918.4-2016. +// +// The random parameter is used as a source of entropy to ensure that +// encrypting the same message twice doesn't result in the same ciphertext. +// Most applications should use [crypto/rand.Reader] as random. func Encrypt(random io.Reader, pub *ecdsa.PublicKey, msg []byte, opts *EncrypterOpts) ([]byte, error) { //A3, requirement is to check if h*P is infinite point, h is 1 if pub.X.Sign() == 0 && pub.Y.Sign() == 0 { @@ -297,8 +302,14 @@ func encodingCiphertextASN1(C1 *_sm2ec.SM2P256Point, c2, c3 []byte) ([]byte, err return b.Bytes() } -// GenerateKey generates a public and private key pair. +// GenerateKey generates a new SM2 private key. +// +// Most applications should use [crypto/rand.Reader] as rand. Note that the +// returned key does not depend deterministically on the bytes read from rand, +// and may change between calls and/or between versions. func GenerateKey(rand io.Reader) (*PrivateKey, error) { + randutil.MaybeReadByte(rand) + c := p256() k, Q, err := randomPoint(c, rand) if err != nil { @@ -493,6 +504,10 @@ func calculateSM2Hash(pub *ecdsa.PublicKey, data, uid []byte) ([]byte, error) { // private key's curve order, the hash will be truncated to that length. It // returns the ASN.1 encoded signature. // +// 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 the opts argument is instance of [*SM2SignerOption], and its ForceGMSign is true, // then the hash will be treated as raw message. func SignASN1(rand io.Reader, priv *PrivateKey, hash []byte, opts crypto.SignerOpts) ([]byte, error) { @@ -505,20 +520,16 @@ func SignASN1(rand io.Reader, priv *PrivateKey, hash []byte, opts crypto.SignerO } randutil.MaybeReadByte(rand) - csprng, err := mixedCSPRNG(rand, &priv.PrivateKey, hash) - if err != nil { - return nil, err - } switch priv.Curve.Params() { case P256().Params(): - return signSM2EC(p256(), priv, csprng, hash) + return signSM2EC(p256(), priv, rand, hash) default: - return signLegacy(priv, csprng, hash) + return signLegacy(priv, rand, hash) } } -func signSM2EC(c *sm2Curve, priv *PrivateKey, csprng io.Reader, hash []byte) (sig []byte, err error) { +func signSM2EC(c *sm2Curve, priv *PrivateKey, rand io.Reader, hash []byte) (sig []byte, err error) { e := bigmod.NewNat() hashToNat(c, e, hash) var ( @@ -546,7 +557,7 @@ func signSM2EC(c *sm2Curve, priv *PrivateKey, csprng io.Reader, hash []byte) (si for { for { - k, R, err = randomPoint(c, csprng) + k, R, err = randomPoint(c, rand) if err != nil { return nil, err } @@ -726,63 +737,6 @@ func hashToNat(c *sm2Curve, e *bigmod.Nat, hash []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 *ecdsa.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. -func (zr) Read(dst []byte) (n int, err error) { - for i := range dst { - dst[i] = 0 - } - return len(dst), nil -} - // IsSM2PublicKey check if given public key is a SM2 public key or not func IsSM2PublicKey(publicKey interface{}) bool { pub, ok := publicKey.(*ecdsa.PublicKey) diff --git a/sm2/sm2_legacy.go b/sm2/sm2_legacy.go index 5f6993e..6acac65 100644 --- a/sm2/sm2_legacy.go +++ b/sm2/sm2_legacy.go @@ -81,7 +81,7 @@ func Sign(rand io.Reader, priv *ecdsa.PrivateKey, hash []byte) (r, s *big.Int, e return r, s, nil } -func signLegacy(priv *PrivateKey, csprng io.Reader, hash []byte) (sig []byte, err error) { +func signLegacy(priv *PrivateKey, rand io.Reader, hash []byte) (sig []byte, err error) { // See [NSA] 3.4.1 c := priv.PublicKey.Curve N := c.Params().N @@ -92,7 +92,7 @@ func signLegacy(priv *PrivateKey, csprng io.Reader, hash []byte) (sig []byte, er e := hashToInt(hash, c) for { for { - k, err = randFieldElement(c, csprng) + k, err = randFieldElement(c, rand) if err != nil { return nil, err } diff --git a/sm2/sm2_test.go b/sm2/sm2_test.go index c2f3cdc..803719e 100644 --- a/sm2/sm2_test.go +++ b/sm2/sm2_test.go @@ -395,37 +395,6 @@ func TestSignVerifyLegacy(t *testing.T) { } } -// Check that signatures are safe even with a broken entropy source. -func TestNonceSafety(t *testing.T) { - priv, err := GenerateKey(rand.Reader) - if err != nil { - t.Errorf("failed to generate key") - } - - hashed := []byte("testing") - r0, s0, err := Sign(zeroReader, &priv.PrivateKey, hashed) - if err != nil { - t.Errorf("SM2: error signing: %s", err) - return - } - - hashed = []byte("testing...") - r1, s1, err := Sign(zeroReader, &priv.PrivateKey, hashed) - if err != nil { - t.Errorf("SM2: error signing: %s", err) - return - } - - if s0.Cmp(s1) == 0 { - // This should never happen. - t.Error("SM2: the signatures on two different messages were the same") - } - - if r0.Cmp(r1) == 0 { - t.Error("SM2: the nonce used for two different messages was the same") - } -} - // Check that signatures remain non-deterministic with a functional entropy source. func TestINDCCA(t *testing.T) { priv, err := GenerateKey(rand.Reader) diff --git a/sm9/sm9.go b/sm9/sm9.go index c9dd290..18b87e1 100644 --- a/sm9/sm9.go +++ b/sm9/sm9.go @@ -10,6 +10,7 @@ import ( "math/big" "github.com/emmansun/gmsm/internal/bigmod" + "github.com/emmansun/gmsm/internal/randutil" "github.com/emmansun/gmsm/internal/subtle" "github.com/emmansun/gmsm/kdf" "github.com/emmansun/gmsm/sm3" @@ -120,6 +121,10 @@ func randomScalar(rand io.Reader) (k *bigmod.Nat, err error) { // Sign signs a hash (which should be the result of hashing a larger message) // using the user dsa key. It returns the signature as a pair of h and s. // Please use SignASN1 instead. +// +// 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. func Sign(rand io.Reader, priv *SignPrivateKey, hash []byte) (h *big.Int, s *bn256.G1, err error) { sig, err := SignASN1(rand, priv, hash) if err != nil { @@ -131,17 +136,26 @@ func Sign(rand io.Reader, priv *SignPrivateKey, hash []byte) (h *big.Int, s *bn2 // Sign signs digest with user's DSA key, reading randomness from rand. The opts argument // is not currently used but, in keeping with the crypto.Signer interface. // The result is SM9Signature ASN.1 format. +// +// 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. func (priv *SignPrivateKey) Sign(rand io.Reader, hash []byte, opts crypto.SignerOpts) ([]byte, error) { return SignASN1(rand, priv, hash) } // SignASN1 signs a hash (which should be the result of hashing a larger message) // using the private key, priv. It returns the ASN.1 encoded signature of type SM9Signature. +// +// 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. func SignASN1(rand io.Reader, priv *SignPrivateKey, hash []byte) ([]byte, error) { var ( hNat *bigmod.Nat s *bn256.G1 ) + randutil.MaybeReadByte(rand) for { r, err := randomScalar(rand) if err != nil { @@ -272,6 +286,10 @@ func (pub *SignMasterPublicKey) Verify(uid []byte, hid byte, hash, sig []byte) b } // WrapKey generates and wraps key with reciever's uid and system hid, returns generated key and cipher. +// +// The rand parameter is used as a source of entropy to ensure that +// calls this function twice doesn't result in the same key. +// Most applications should use [crypto/rand.Reader] as random. func WrapKey(rand io.Reader, pub *EncryptMasterPublicKey, uid []byte, hid byte, kLen int) (key []byte, cipher *bn256.G1, err error) { q := pub.GenerateUserPublicKey(uid, hid) var ( @@ -308,6 +326,10 @@ func WrapKey(rand io.Reader, pub *EncryptMasterPublicKey, uid []byte, hid byte, } // WrapKey wraps key and converts the cipher as ASN1 format, SM9PublicKey1 definition. +// +// The rand parameter is used as a source of entropy to ensure that +// calls this function twice doesn't result in the same key. +// Most applications should use [crypto/rand.Reader] as random. func (pub *EncryptMasterPublicKey) WrapKey(rand io.Reader, uid []byte, hid byte, kLen int) ([]byte, []byte, error) { key, cipher, err := WrapKey(rand, pub, uid, hid, kLen) if err != nil { @@ -322,6 +344,10 @@ func (pub *EncryptMasterPublicKey) WrapKey(rand io.Reader, uid []byte, hid byte, // WrapKeyASN1 wraps key and converts the result of SM9KeyPackage as ASN1 format. according // SM9 cryptographic algorithm application specification, SM9KeyPackage defnition. +// +// The rand parameter is used as a source of entropy to ensure that +// calls this function twice doesn't result in the same key. +// Most applications should use [crypto/rand.Reader] as random. func (pub *EncryptMasterPublicKey) WrapKeyASN1(rand io.Reader, uid []byte, hid byte, kLen int) ([]byte, error) { key, cipher, err := WrapKey(rand, pub, uid, hid, kLen) if err != nil { diff --git a/smx509/verify_test.go b/smx509/verify_test.go index 26ea73b..2e45ec3 100644 --- a/smx509/verify_test.go +++ b/smx509/verify_test.go @@ -497,22 +497,21 @@ func testVerify(t *testing.T, test verifyTest, useSystemRoots bool) { return true } - // Every expected chain should match 1 returned chain + // Every expected chain should match one (or more) returned chain. We tolerate multiple + // matches, as due to root store semantics it is plausible that (at least on the system + // verifiers) multiple identical (looking) chains may be returned when two roots with the + // same subject are present. for _, expectedChain := range test.expectedChains { - nChainMatched := 0 + var match bool for _, chain := range chains { if doesMatch(expectedChain, chain) { - nChainMatched++ + match = true + break } } - if nChainMatched != 1 { - t.Errorf("Got %v matches instead of %v for expected chain %v", nChainMatched, 1, expectedChain) - for _, chain := range chains { - if doesMatch(expectedChain, chain) { - t.Errorf("\t matched %v", chainToDebugString(chain)) - } - } + if !match { + t.Errorf("No match found for %v", expectedChain) } }