sm2: remove CSPRNG usage

This commit is contained in:
Sun Yimin 2023-06-27 08:57:31 +08:00 committed by GitHub
parent c1ea628282
commit 8041c5e310
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 60 additions and 112 deletions

View File

@ -11,11 +11,8 @@ package sm2
import ( import (
"crypto" "crypto"
"crypto/aes"
"crypto/cipher"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic" "crypto/elliptic"
"crypto/sha512"
_subtle "crypto/subtle" _subtle "crypto/subtle"
"errors" "errors"
"fmt" "fmt"
@ -203,11 +200,19 @@ var (
) )
// EncryptASN1 sm2 encrypt and output ASN.1 result, compliance with GB/T 32918.4-2016. // 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) { func EncryptASN1(random io.Reader, pub *ecdsa.PublicKey, msg []byte) ([]byte, error) {
return Encrypt(random, pub, msg, ASN1EncrypterOpts) return Encrypt(random, pub, msg, ASN1EncrypterOpts)
} }
// Encrypt sm2 encrypt implementation, compliance with GB/T 32918.4-2016. // 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) { 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 //A3, requirement is to check if h*P is infinite point, h is 1
if pub.X.Sign() == 0 && pub.Y.Sign() == 0 { 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() 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) { func GenerateKey(rand io.Reader) (*PrivateKey, error) {
randutil.MaybeReadByte(rand)
c := p256() c := p256()
k, Q, err := randomPoint(c, rand) k, Q, err := randomPoint(c, rand)
if err != nil { 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 // private key's curve order, the hash will be truncated to that length. It
// returns the ASN.1 encoded signature. // 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, // If the opts argument is instance of [*SM2SignerOption], and its ForceGMSign is true,
// then the hash will be treated as raw message. // then the hash will be treated as raw message.
func SignASN1(rand io.Reader, priv *PrivateKey, hash []byte, opts crypto.SignerOpts) ([]byte, error) { 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) randutil.MaybeReadByte(rand)
csprng, err := mixedCSPRNG(rand, &priv.PrivateKey, hash)
if err != nil {
return nil, err
}
switch priv.Curve.Params() { switch priv.Curve.Params() {
case P256().Params(): case P256().Params():
return signSM2EC(p256(), priv, csprng, hash) return signSM2EC(p256(), priv, rand, hash)
default: 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() e := bigmod.NewNat()
hashToNat(c, e, hash) hashToNat(c, e, hash)
var ( var (
@ -546,7 +557,7 @@ func signSM2EC(c *sm2Curve, priv *PrivateKey, csprng io.Reader, hash []byte) (si
for { for {
for { for {
k, R, err = randomPoint(c, csprng) k, R, err = randomPoint(c, rand)
if err != nil { if err != nil {
return nil, err 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 // IsSM2PublicKey check if given public key is a SM2 public key or not
func IsSM2PublicKey(publicKey interface{}) bool { func IsSM2PublicKey(publicKey interface{}) bool {
pub, ok := publicKey.(*ecdsa.PublicKey) pub, ok := publicKey.(*ecdsa.PublicKey)

View File

@ -81,7 +81,7 @@ func Sign(rand io.Reader, priv *ecdsa.PrivateKey, hash []byte) (r, s *big.Int, e
return r, s, nil 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 // See [NSA] 3.4.1
c := priv.PublicKey.Curve c := priv.PublicKey.Curve
N := c.Params().N N := c.Params().N
@ -92,7 +92,7 @@ func signLegacy(priv *PrivateKey, csprng io.Reader, hash []byte) (sig []byte, er
e := hashToInt(hash, c) e := hashToInt(hash, c)
for { for {
for { for {
k, err = randFieldElement(c, csprng) k, err = randFieldElement(c, rand)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -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. // Check that signatures remain non-deterministic with a functional entropy source.
func TestINDCCA(t *testing.T) { func TestINDCCA(t *testing.T) {
priv, err := GenerateKey(rand.Reader) priv, err := GenerateKey(rand.Reader)

View File

@ -10,6 +10,7 @@ import (
"math/big" "math/big"
"github.com/emmansun/gmsm/internal/bigmod" "github.com/emmansun/gmsm/internal/bigmod"
"github.com/emmansun/gmsm/internal/randutil"
"github.com/emmansun/gmsm/internal/subtle" "github.com/emmansun/gmsm/internal/subtle"
"github.com/emmansun/gmsm/kdf" "github.com/emmansun/gmsm/kdf"
"github.com/emmansun/gmsm/sm3" "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) // 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. // using the user dsa key. It returns the signature as a pair of h and s.
// Please use SignASN1 instead. // 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) { func Sign(rand io.Reader, priv *SignPrivateKey, hash []byte) (h *big.Int, s *bn256.G1, err error) {
sig, err := SignASN1(rand, priv, hash) sig, err := SignASN1(rand, priv, hash)
if err != nil { 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 // 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. // is not currently used but, in keeping with the crypto.Signer interface.
// The result is SM9Signature ASN.1 format. // 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) { func (priv *SignPrivateKey) Sign(rand io.Reader, hash []byte, opts crypto.SignerOpts) ([]byte, error) {
return SignASN1(rand, priv, hash) return SignASN1(rand, priv, hash)
} }
// SignASN1 signs a hash (which should be the result of hashing a larger message) // 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. // 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) { func SignASN1(rand io.Reader, priv *SignPrivateKey, hash []byte) ([]byte, error) {
var ( var (
hNat *bigmod.Nat hNat *bigmod.Nat
s *bn256.G1 s *bn256.G1
) )
randutil.MaybeReadByte(rand)
for { for {
r, err := randomScalar(rand) r, err := randomScalar(rand)
if err != nil { 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. // 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) { 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) q := pub.GenerateUserPublicKey(uid, hid)
var ( 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. // 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) { 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) key, cipher, err := WrapKey(rand, pub, uid, hid, kLen)
if err != nil { 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 // WrapKeyASN1 wraps key and converts the result of SM9KeyPackage as ASN1 format. according
// SM9 cryptographic algorithm application specification, SM9KeyPackage defnition. // 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) { func (pub *EncryptMasterPublicKey) WrapKeyASN1(rand io.Reader, uid []byte, hid byte, kLen int) ([]byte, error) {
key, cipher, err := WrapKey(rand, pub, uid, hid, kLen) key, cipher, err := WrapKey(rand, pub, uid, hid, kLen)
if err != nil { if err != nil {

View File

@ -497,22 +497,21 @@ func testVerify(t *testing.T, test verifyTest, useSystemRoots bool) {
return true 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 { for _, expectedChain := range test.expectedChains {
nChainMatched := 0 var match bool
for _, chain := range chains { for _, chain := range chains {
if doesMatch(expectedChain, chain) { if doesMatch(expectedChain, chain) {
nChainMatched++ match = true
break
} }
} }
if nChainMatched != 1 { if !match {
t.Errorf("Got %v matches instead of %v for expected chain %v", nChainMatched, 1, expectedChain) t.Errorf("No match found for %v", expectedChain)
for _, chain := range chains {
if doesMatch(expectedChain, chain) {
t.Errorf("\t matched %v", chainToDebugString(chain))
}
}
} }
} }