ecdh: sm2 ECDH initial version

This commit is contained in:
Sun Yimin 2022-08-26 13:25:56 +08:00 committed by GitHub
parent d1e4806e06
commit 3f9e1d5bd9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 461 additions and 0 deletions

148
ecdh/ecdh.go Normal file
View File

@ -0,0 +1,148 @@
// Package ecdh implements Elliptic Curve Diffie-Hellman / SM2-MQV over
// SM2 curve.
package ecdh
import (
"crypto"
"crypto/subtle"
"io"
"sync"
)
type Curve interface {
// ECDH performs a ECDH exchange and returns the shared secret.
//
// For NIST curves, this performs ECDH as specified in SEC 1, Version 2.0,
// Section 3.3.1, and returns the x-coordinate encoded according to SEC 1,
// Version 2.0, Section 2.3.5. In particular, if the result is the point at
// infinity, ECDH returns an error. (Note that for NIST curves, that's only
// possible if the private key is the all-zero value.)
//
// For X25519, this performs ECDH as specified in RFC 7748, Section 6.1. If
// the result is the all-zero value, ECDH returns an error.
ECDH(local *PrivateKey, remote *PublicKey) ([]byte, error)
// SM2MQV performs a SM2 specific style ECMQV exchange and return the shared secret.
//SM2MQV(sLocal, eLocal *PrivateKey, sRemote, eRemote *PublicKey) (*PublicKey, error)
// GenerateKey generates a new PrivateKey from rand.
GenerateKey(rand io.Reader) (*PrivateKey, error)
// NewPrivateKey checks that key is valid and returns a PrivateKey.
//
// For NIST curves, this follows SEC 1, Version 2.0, Section 2.3.6, which
// amounts to decoding the bytes as a fixed length big endian integer and
// checking that the result is lower than the order of the curve. The zero
// private key is also rejected, as the encoding of the corresponding public
// key would be irregular.
//
// For X25519, this only checks the scalar length. Adversarially selected
// private keys can cause ECDH to return an error.
NewPrivateKey(key []byte) (*PrivateKey, error)
// NewPublicKey checks that key is valid and returns a PublicKey.
//
// For NIST curves, this decodes an uncompressed point according to SEC 1,
// Version 2.0, Section 2.3.4. Compressed encodings and the point at
// infinity are rejected.
//
// For X25519, this only checks the u-coordinate length. Adversarially
// selected public keys can cause ECDH to return an error.
NewPublicKey(key []byte) (*PublicKey, error)
// privateKeyToPublicKey converts a PrivateKey to a PublicKey. It's exposed
// as the PrivateKey.PublicKey method.
//
// This method always succeeds: for X25519, it might output the all-zeroes
// value (unlike the ECDH method); for NIST curves, it would only fail for
// the zero private key, which is rejected by NewPrivateKey.
//
// The private method also allow us to expand the ECDH interface with more
// methods in the future without breaking backwards compatibility.
privateKeyToPublicKey(*PrivateKey) *PublicKey
}
// PublicKey is an ECDH public key, usually a peer's ECDH share sent over the wire.
type PublicKey struct {
curve Curve
publicKey []byte
}
// Bytes returns a copy of the encoding of the public key.
func (k *PublicKey) Bytes() []byte {
// Copy the public key to a fixed size buffer that can get allocated on the
// caller's stack after inlining.
var buf [133]byte
return append(buf[:0], k.publicKey...)
}
// Equal returns whether x represents the same public key as k.
//
// Note that there can be equivalent public keys with different encodings which
// would return false from this check but behave the same way as inputs to ECDH.
//
// This check is performed in constant time as long as the key types and their
// curve match.
func (k *PublicKey) Equal(x crypto.PublicKey) bool {
xx, ok := x.(*PublicKey)
if !ok {
return false
}
return k.curve == xx.curve &&
subtle.ConstantTimeCompare(k.publicKey, xx.publicKey) == 1
}
func (k *PublicKey) Curve() Curve {
return k.curve
}
// PrivateKey is an ECDH private key, usually kept secret.
type PrivateKey struct {
curve Curve
privateKey []byte
// publicKey is set under publicKeyOnce, to allow loading private keys with
// NewPrivateKey without having to perform a scalar multiplication.
publicKey *PublicKey
publicKeyOnce sync.Once
}
// Bytes returns a copy of the encoding of the private key.
func (k *PrivateKey) Bytes() []byte {
// Copy the private key to a fixed size buffer that can get allocated on the
// caller's stack after inlining.
var buf [66]byte
return append(buf[:0], k.privateKey...)
}
// Equal returns whether x represents the same private key as k.
//
// Note that there can be equivalent private keys with different encodings which
// would return false from this check but behave the same way as inputs to ECDH.
//
// This check is performed in constant time as long as the key types and their
// curve match.
func (k *PrivateKey) Equal(x crypto.PrivateKey) bool {
xx, ok := x.(*PrivateKey)
if !ok {
return false
}
return k.curve == xx.curve &&
subtle.ConstantTimeCompare(k.privateKey, xx.privateKey) == 1
}
func (k *PrivateKey) Curve() Curve {
return k.curve
}
func (k *PrivateKey) PublicKey() *PublicKey {
k.publicKeyOnce.Do(func() {
k.publicKey = k.curve.privateKeyToPublicKey(k)
})
return k.publicKey
}
// Public implements the implicit interface of all standard library private
// keys. See the docs of crypto.PrivateKey.
func (k *PrivateKey) Public() crypto.PublicKey {
return k.PublicKey()
}

160
ecdh/ecdh_test.go Normal file
View File

@ -0,0 +1,160 @@
package ecdh_test
import (
"bytes"
"crypto"
"crypto/cipher"
"crypto/rand"
"fmt"
"io"
"testing"
"github.com/emmansun/gmsm/ecdh"
"golang.org/x/crypto/chacha20"
)
// Check that PublicKey and PrivateKey implement the interfaces documented in
// crypto.PublicKey and crypto.PrivateKey.
var _ interface {
Equal(x crypto.PublicKey) bool
} = &ecdh.PublicKey{}
var _ interface {
Public() crypto.PublicKey
Equal(x crypto.PrivateKey) bool
} = &ecdh.PrivateKey{}
func TestECDH(t *testing.T) {
aliceKey, err := ecdh.P256().GenerateKey(rand.Reader)
if err != nil {
t.Fatal(err)
}
bobKey, err := ecdh.P256().GenerateKey(rand.Reader)
if err != nil {
t.Fatal(err)
}
alicePubKey, err := ecdh.P256().NewPublicKey(aliceKey.PublicKey().Bytes())
if err != nil {
t.Error(err)
}
if !bytes.Equal(aliceKey.PublicKey().Bytes(), alicePubKey.Bytes()) {
t.Error("encoded and decoded public keys are different")
}
if !aliceKey.PublicKey().Equal(alicePubKey) {
t.Error("encoded and decoded public keys are different")
}
alicePrivKey, err := ecdh.P256().NewPrivateKey(aliceKey.Bytes())
if err != nil {
t.Error(err)
}
if !bytes.Equal(aliceKey.Bytes(), alicePrivKey.Bytes()) {
t.Error("encoded and decoded private keys are different")
}
if !aliceKey.Equal(alicePrivKey) {
t.Error("encoded and decoded private keys are different")
}
bobSecret, err := ecdh.P256().ECDH(bobKey, aliceKey.PublicKey())
if err != nil {
t.Fatal(err)
}
aliceSecret, err := ecdh.P256().ECDH(aliceKey, bobKey.PublicKey())
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(bobSecret, aliceSecret) {
t.Error("two ECDH computations came out different")
}
}
type countingReader struct {
r io.Reader
n int
}
func (r *countingReader) Read(p []byte) (int, error) {
n, err := r.r.Read(p)
r.n += n
return n, err
}
func TestGenerateKey(t *testing.T) {
r := &countingReader{r: rand.Reader}
k, err := ecdh.P256().GenerateKey(r)
if err != nil {
t.Fatal(err)
}
// GenerateKey does rejection sampling. If the masking works correctly,
// the probability of a rejection is 1-ord(G)/2^ceil(log2(ord(G))),
// which for all curves is small enough (at most 2^-32, for P-256) that
// a bit flip is more likely to make this test fail than bad luck.
// Account for the extra MaybeReadByte byte, too.
if got, expected := r.n, len(k.Bytes())+1; got > expected {
t.Errorf("expected GenerateKey to consume at most %v bytes, got %v", expected, got)
}
}
func TestString(t *testing.T) {
s := fmt.Sprintf("%s", ecdh.P256())
if s != "sm2p256v1" {
t.Errorf("unexpected Curve string encoding: %q", s)
}
}
func BenchmarkECDH(b *testing.B) {
benchmarkAllCurves(b, func(b *testing.B, curve ecdh.Curve) {
c, err := chacha20.NewUnauthenticatedCipher(make([]byte, 32), make([]byte, 12))
if err != nil {
b.Fatal(err)
}
rand := cipher.StreamReader{
S: c, R: zeroReader,
}
peerKey, err := curve.GenerateKey(rand)
if err != nil {
b.Fatal(err)
}
peerShare := peerKey.PublicKey().Bytes()
b.ResetTimer()
b.ReportAllocs()
var allocationsSink byte
for i := 0; i < b.N; i++ {
key, err := curve.GenerateKey(rand)
if err != nil {
b.Fatal(err)
}
share := key.PublicKey().Bytes()
peerPubKey, err := curve.NewPublicKey(peerShare)
if err != nil {
b.Fatal(err)
}
secret, err := curve.ECDH(key, peerPubKey)
if err != nil {
b.Fatal(err)
}
allocationsSink ^= secret[0] ^ share[0]
}
})
}
func benchmarkAllCurves(b *testing.B, f func(b *testing.B, curve ecdh.Curve)) {
b.Run("SM2P256", func(b *testing.B) { f(b, ecdh.P256()) })
}
type zr struct{}
// Read replaces the contents of dst with zeros. It is safe for concurrent use.
func (zr) Read(dst []byte) (n int, err error) {
for i := range dst {
dst[i] = 0
}
return len(dst), nil
}
var zeroReader = zr{}

153
ecdh/sm2ec.go Normal file
View File

@ -0,0 +1,153 @@
package ecdh
import (
"encoding/binary"
"errors"
"io"
"math/bits"
"github.com/emmansun/gmsm/internal/randutil"
sm2ec "github.com/emmansun/gmsm/internal/sm2ec"
"github.com/emmansun/gmsm/internal/subtle"
)
type sm2Curve struct {
name string
newPoint func() *sm2ec.SM2P256Point
scalarOrder []byte
}
func (c *sm2Curve) String() string {
return c.name
}
func (c *sm2Curve) GenerateKey(rand io.Reader) (*PrivateKey, error) {
key := make([]byte, len(c.scalarOrder))
randutil.MaybeReadByte(rand)
for {
if _, err := io.ReadFull(rand, key); err != nil {
return nil, err
}
// In tests, rand will return all zeros and NewPrivateKey will reject
// the zero key as it generates the identity as a public key. This also
// makes this function consistent with crypto/elliptic.GenerateKey.
key[1] ^= 0x42
k, err := c.NewPrivateKey(key)
if err == errInvalidPrivateKey {
continue
}
return k, err
}
}
func (c *sm2Curve) NewPrivateKey(key []byte) (*PrivateKey, error) {
if len(key) != len(c.scalarOrder) {
return nil, errors.New("ecdh: invalid private key size")
}
if subtle.ConstantTimeAllZero(key) || !isLess(key, c.scalarOrder) {
return nil, errInvalidPrivateKey
}
return &PrivateKey{
curve: c,
privateKey: append([]byte{}, key...),
}, nil
}
func (c *sm2Curve) privateKeyToPublicKey(key *PrivateKey) *PublicKey {
if key.curve != c {
panic("ecdh: internal error: converting the wrong key type")
}
p, err := c.newPoint().ScalarBaseMult(key.privateKey)
if err != nil {
// This is unreachable because the only error condition of
// ScalarBaseMult is if the input is not the right size.
panic("ecdh: internal error: sm2ec ScalarBaseMult failed for a fixed-size input")
}
publicKey := p.Bytes()
if len(publicKey) == 1 {
// The encoding of the identity is a single 0x00 byte. This is
// unreachable because the only scalar that generates the identity is
// zero, which is rejected by NewPrivateKey.
panic("ecdh: internal error: sm2ec ScalarBaseMult returned the identity")
}
return &PublicKey{
curve: key.curve,
publicKey: publicKey,
}
}
func (c *sm2Curve) NewPublicKey(key []byte) (*PublicKey, error) {
// Reject the point at infinity and compressed encodings.
if len(key) == 0 || key[0] != 4 {
return nil, errors.New("ecdh: invalid public key")
}
// SetBytes also checks that the point is on the curve.
if _, err := c.newPoint().SetBytes(key); err != nil {
return nil, err
}
return &PublicKey{
curve: c,
publicKey: append([]byte{}, key...),
}, nil
}
func (c *sm2Curve) ECDH(local *PrivateKey, remote *PublicKey) ([]byte, error) {
p, err := c.newPoint().SetBytes(remote.publicKey)
if err != nil {
return nil, err
}
if _, err := p.ScalarMult(p, local.privateKey); err != nil {
return nil, err
}
// BytesX will return an error if p is the point at infinity.
return p.BytesX()
}
// P256 returns a Curve which implements SM2, also known as sm2p256v1
//
// Multiple invocations of this function will return the same value, so it can
// be used for equality checks and switch statements.
func P256() Curve { return sm2P256 }
var sm2P256 = &sm2Curve{
name: "sm2p256v1",
newPoint: sm2ec.NewSM2P256Point,
scalarOrder: sm2P256Order,
}
var sm2P256Order = []byte{0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x72, 0x03, 0xdf, 0x6b, 0x21, 0xc6, 0x05, 0x2b, 0x53, 0xbb, 0xf4, 0x09, 0x39, 0xd5, 0x41, 0x23}
// isLess returns whether a < b, where a and b are big-endian buffers of the
// same length and shorter than 72 bytes.
func isLess(a, b []byte) bool {
if len(a) != len(b) {
panic("ecdh: internal error: mismatched isLess inputs")
}
// Copy the values into a fixed-size preallocated little-endian buffer.
// 72 bytes is enough for every scalar in this package, and having a fixed
// size lets us avoid heap allocations.
if len(a) > 72 {
panic("ecdh: internal error: isLess input too large")
}
bufA, bufB := make([]byte, 72), make([]byte, 72)
for i := range a {
bufA[i], bufB[i] = a[len(a)-i-1], b[len(b)-i-1]
}
// Perform a subtraction with borrow.
var borrow uint64
for i := 0; i < len(bufA); i += 8 {
limbA, limbB := binary.LittleEndian.Uint64(bufA[i:]), binary.LittleEndian.Uint64(bufB[i:])
_, borrow = bits.Sub64(limbA, limbB, borrow)
}
// If there is a borrow at the end of the operation, then a < b.
return borrow == 1
}
var errInvalidPrivateKey = errors.New("ecdh: invalid private key")