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 (
"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)

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
}
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
}

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.
func TestINDCCA(t *testing.T) {
priv, err := GenerateKey(rand.Reader)

View File

@ -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 {

View File

@ -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)
}
}