mirror of
https://github.com/emmansun/gmsm.git
synced 2025-04-27 04:36:19 +08:00
ecdh: sm2 ECDH initial version
This commit is contained in:
parent
d1e4806e06
commit
3f9e1d5bd9
148
ecdh/ecdh.go
Normal file
148
ecdh/ecdh.go
Normal 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
160
ecdh/ecdh_test.go
Normal 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
153
ecdh/sm2ec.go
Normal 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")
|
Loading…
x
Reference in New Issue
Block a user