From 5b6118e7f2945de32c4d21723797cdc1b04c3983 Mon Sep 17 00:00:00 2001 From: Luke Champine Date: Mon, 30 Mar 2020 10:57:36 -0400 Subject: [PATCH 01/17] ecies: Fix reps calculation NIST SP 800-56 specifies that reps should equal: ceil(keydatalen / hashlen) where hashlen is the length, in bits, of the output block of the hash function. In the hash.Hash interface, this value is given by Size() * 8, not BlockSize() * 8. (BlockSize is a confusingly-named method that relates to hash *input*, not output.) --- crypto/ecies/ecies.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crypto/ecies/ecies.go b/crypto/ecies/ecies.go index 1474181482b6..7df3d2618a41 100644 --- a/crypto/ecies/ecies.go +++ b/crypto/ecies/ecies.go @@ -169,9 +169,8 @@ func concatKDF(hash hash.Hash, z, s1 []byte, kdLen int) (k []byte, err error) { s1 = make([]byte, 0) } - reps := ((kdLen + 7) * 8) / (hash.BlockSize() * 8) + reps := ((kdLen + 7) * 8) / (hash.Size() * 8) if big.NewInt(int64(reps)).Cmp(big2To32M1) > 0 { - fmt.Println(big2To32M1) return nil, ErrKeyDataTooLong } From 753f927e31a059b7d42a4cb66204f85a932113b9 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 31 Mar 2020 14:51:51 +0200 Subject: [PATCH 02/17] crypto/ecies: avoid big integer math in overflow check --- crypto/ecies/ecies.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/crypto/ecies/ecies.go b/crypto/ecies/ecies.go index 7df3d2618a41..90d5d66cebbb 100644 --- a/crypto/ecies/ecies.go +++ b/crypto/ecies/ecies.go @@ -143,11 +143,6 @@ var ( ErrInvalidMessage = fmt.Errorf("ecies: invalid message") ) -var ( - big2To32 = new(big.Int).Exp(big.NewInt(2), big.NewInt(32), nil) - big2To32M1 = new(big.Int).Sub(big2To32, big.NewInt(1)) -) - func incCounter(ctr []byte) { if ctr[3]++; ctr[3] != 0 { return @@ -169,8 +164,11 @@ func concatKDF(hash hash.Hash, z, s1 []byte, kdLen int) (k []byte, err error) { s1 = make([]byte, 0) } - reps := ((kdLen + 7) * 8) / (hash.Size() * 8) - if big.NewInt(int64(reps)).Cmp(big2To32M1) > 0 { + // reps is the maximum number of iterations of the + // counter hashing loop. This is capped to 32 bits to + // prevent overflow of the counter. + reps := (int64(kdLen) + 7) * 8 / int64(hash.Size()*8) + if reps > int64(^uint32(0)) { return nil, ErrKeyDataTooLong } From 5b70f574fd3131dbb956c95679d929c777976e69 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 31 Mar 2020 14:57:24 +0200 Subject: [PATCH 03/17] crypto/ecies: fix build --- crypto/ecies/ecies.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/ecies/ecies.go b/crypto/ecies/ecies.go index 90d5d66cebbb..24f246cfd690 100644 --- a/crypto/ecies/ecies.go +++ b/crypto/ecies/ecies.go @@ -175,7 +175,7 @@ func concatKDF(hash hash.Hash, z, s1 []byte, kdLen int) (k []byte, err error) { counter := []byte{0, 0, 0, 1} k = make([]byte, 0) - for i := 0; i <= reps; i++ { + for i := int64(0); i <= reps; i++ { hash.Write(counter) hash.Write(z) hash.Write(s1) From 830d1382f1e0fdbe972839c0e484abc5da467095 Mon Sep 17 00:00:00 2001 From: Luke Champine Date: Tue, 31 Mar 2020 13:13:55 -0400 Subject: [PATCH 04/17] ecies: overhaul concatKDF - Eliminate overflow error case - Simplify and inline incCounter - Preallocate k --- crypto/ecies/ecies.go | 57 +++++++++---------------------------------- 1 file changed, 11 insertions(+), 46 deletions(-) diff --git a/crypto/ecies/ecies.go b/crypto/ecies/ecies.go index 24f246cfd690..3dd95441e0ae 100644 --- a/crypto/ecies/ecies.go +++ b/crypto/ecies/ecies.go @@ -138,54 +138,26 @@ func (prv *PrivateKey) GenerateShared(pub *PublicKey, skLen, macLen int) (sk []b } var ( - ErrKeyDataTooLong = fmt.Errorf("ecies: can't supply requested key data") ErrSharedTooLong = fmt.Errorf("ecies: shared secret is too long") ErrInvalidMessage = fmt.Errorf("ecies: invalid message") ) -func incCounter(ctr []byte) { - if ctr[3]++; ctr[3] != 0 { - return - } - if ctr[2]++; ctr[2] != 0 { - return - } - if ctr[1]++; ctr[1] != 0 { - return - } - if ctr[0]++; ctr[0] != 0 { - return - } -} // NIST SP 800-56 Concatenation Key Derivation Function (see section 5.8.1). -func concatKDF(hash hash.Hash, z, s1 []byte, kdLen int) (k []byte, err error) { - if s1 == nil { - s1 = make([]byte, 0) - } - - // reps is the maximum number of iterations of the - // counter hashing loop. This is capped to 32 bits to - // prevent overflow of the counter. - reps := (int64(kdLen) + 7) * 8 / int64(hash.Size()*8) - if reps > int64(^uint32(0)) { - return nil, ErrKeyDataTooLong - } - +func concatKDF(hash hash.Hash, z, s1 []byte, kdLen int) []byte { counter := []byte{0, 0, 0, 1} - k = make([]byte, 0) - - for i := int64(0); i <= reps; i++ { + k := make([]byte, 0, kdLen+hash.Size()) + for len(k) < kdLen { + hash.Reset() hash.Write(counter) hash.Write(z) hash.Write(s1) - k = append(k, hash.Sum(nil)...) - hash.Reset() - incCounter(counter) + k = k[:len(k)+hash.Size()] + hash.Sum(k[:len(k)-hash.Size()]) + // increment counter + binary.BigEndian.PutUint32(counter, binary.BigEndian.Uint32(counter)+1) } - - k = k[:kdLen] - return + return k[:kdLen] } // messageTag computes the MAC of a message (called the tag) as per @@ -263,10 +235,7 @@ func Encrypt(rand io.Reader, pub *PublicKey, m, s1, s2 []byte) (ct []byte, err e if err != nil { return } - K, err := concatKDF(hash, z, s1, params.KeyLen+params.KeyLen) - if err != nil { - return - } + K := concatKDF(hash, z, s1, params.KeyLen+params.KeyLen) Ke := K[:params.KeyLen] Km := K[params.KeyLen:] hash.Write(Km) @@ -341,11 +310,7 @@ func (prv *PrivateKey) Decrypt(c, s1, s2 []byte) (m []byte, err error) { return } - K, err := concatKDF(hash, z, s1, params.KeyLen+params.KeyLen) - if err != nil { - return - } - + K := concatKDF(hash, z, s1, params.KeyLen+params.KeyLen) Ke := K[:params.KeyLen] Km := K[params.KeyLen:] hash.Write(Km) From c863c9749ea31274a5b24b52df56a4e4b305f65f Mon Sep 17 00:00:00 2001 From: Luke Champine Date: Tue, 31 Mar 2020 16:13:16 -0400 Subject: [PATCH 05/17] ecies: Append hash.Sum to k directly --- crypto/ecies/ecies.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/crypto/ecies/ecies.go b/crypto/ecies/ecies.go index 3dd95441e0ae..110ee7fde19f 100644 --- a/crypto/ecies/ecies.go +++ b/crypto/ecies/ecies.go @@ -145,17 +145,15 @@ var ( // NIST SP 800-56 Concatenation Key Derivation Function (see section 5.8.1). func concatKDF(hash hash.Hash, z, s1 []byte, kdLen int) []byte { - counter := []byte{0, 0, 0, 1} + counterBytes := make([]byte, 4) k := make([]byte, 0, kdLen+hash.Size()) - for len(k) < kdLen { + for counter := uint32(1); len(k) < kdLen; counter++ { hash.Reset() - hash.Write(counter) + binary.BigEndian.PutUint32(counterBytes, counter) + hash.Write(counterBytes) hash.Write(z) hash.Write(s1) - k = k[:len(k)+hash.Size()] - hash.Sum(k[:len(k)-hash.Size()]) - // increment counter - binary.BigEndian.PutUint32(counter, binary.BigEndian.Uint32(counter)+1) + k = hash.Sum(k) } return k[:kdLen] } From 9c5b79b7c1555a91cf2c153caf5330d74e51aa7a Mon Sep 17 00:00:00 2001 From: Luke Champine Date: Tue, 31 Mar 2020 17:54:31 -0400 Subject: [PATCH 06/17] ecies: Add missing binary import --- crypto/ecies/ecies.go | 1 + 1 file changed, 1 insertion(+) diff --git a/crypto/ecies/ecies.go b/crypto/ecies/ecies.go index 110ee7fde19f..fffb0eb5a396 100644 --- a/crypto/ecies/ecies.go +++ b/crypto/ecies/ecies.go @@ -35,6 +35,7 @@ import ( "crypto/elliptic" "crypto/hmac" "crypto/subtle" + "encoding/binary" "fmt" "hash" "io" From b921ff6f070b807fa1e7fb7879ecd824c58694ff Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 1 Apr 2020 10:43:33 +0200 Subject: [PATCH 07/17] crypto/ecies: fix test --- crypto/ecies/ecies_test.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crypto/ecies/ecies_test.go b/crypto/ecies/ecies_test.go index b465f076f4eb..4f94aca28d7d 100644 --- a/crypto/ecies/ecies_test.go +++ b/crypto/ecies/ecies_test.go @@ -47,10 +47,7 @@ func TestKDF(t *testing.T) { msg := []byte("Hello, world") h := sha256.New() - k, err := concatKDF(h, msg, nil, 64) - if err != nil { - t.Fatal(err) - } + k := concatKDF(h, msg, nil, 64) if len(k) != 64 { t.Fatalf("KDF: generated key is the wrong size (%d instead of 64\n", len(k)) } From 870f40f7ec28f1720e45ba8f997ef4b3e6f58c14 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 1 Apr 2020 12:03:53 +0200 Subject: [PATCH 08/17] crypto/ecies: improve KDF test --- crypto/ecies/ecies_test.go | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/crypto/ecies/ecies_test.go b/crypto/ecies/ecies_test.go index 4f94aca28d7d..f0fd8734c870 100644 --- a/crypto/ecies/ecies_test.go +++ b/crypto/ecies/ecies_test.go @@ -42,14 +42,23 @@ import ( "github.com/ethereum/go-ethereum/crypto" ) -// Ensure the KDF generates appropriately sized keys. func TestKDF(t *testing.T) { - msg := []byte("Hello, world") - h := sha256.New() - - k := concatKDF(h, msg, nil, 64) - if len(k) != 64 { - t.Fatalf("KDF: generated key is the wrong size (%d instead of 64\n", len(k)) + tests := []struct { + length int + output []byte + }{ + {6, decode("858b192fa2ed")}, + {32, decode("858b192fa2ed4395e2bf88dd8d5770d67dc284ee539f12da8bceaa45d06ebae0")}, + {48, decode("858b192fa2ed4395e2bf88dd8d5770d67dc284ee539f12da8bceaa45d06ebae0700f1ab918a5f0413b8140f9940d6955")}, + {64, decode("858b192fa2ed4395e2bf88dd8d5770d67dc284ee539f12da8bceaa45d06ebae0700f1ab918a5f0413b8140f9940d6955f3467fd6672cce1024c5b1effccc0f61")}, + } + + for _, test := range tests { + h := sha256.New() + k := concatKDF(h, []byte("input"), nil, test.length) + if !bytes.Equal(k, test.output) { + t.Fatalf("KDF: generated key %x does not match expected output %x", k, test.output) + } } } @@ -398,7 +407,7 @@ func TestSharedKeyStatic(t *testing.T) { t.Fatal(ErrBadSharedKeys) } - sk, _ := hex.DecodeString("167ccc13ac5e8a26b131c3446030c60fbfac6aa8e31149d0869f93626a4cdf62") + sk := decode("167ccc13ac5e8a26b131c3446030c60fbfac6aa8e31149d0869f93626a4cdf62") if !bytes.Equal(sk1, sk) { t.Fatalf("shared secret mismatch: want: %x have: %x", sk, sk1) } @@ -411,3 +420,11 @@ func hexKey(prv string) *PrivateKey { } return ImportECDSA(key) } + +func decode(s string) []byte { + bytes, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + return bytes +} From 3b81617e943ee223ca4e82c089f1d8a3543eeb38 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 1 Apr 2020 12:04:11 +0200 Subject: [PATCH 09/17] crypto/ecies: move hash.Reset call to end of loop This makes a difference because the hash is re-used in Encrypt and Decrypt without an additional Reset. --- crypto/ecies/ecies.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crypto/ecies/ecies.go b/crypto/ecies/ecies.go index fffb0eb5a396..a86c735f07a2 100644 --- a/crypto/ecies/ecies.go +++ b/crypto/ecies/ecies.go @@ -143,18 +143,17 @@ var ( ErrInvalidMessage = fmt.Errorf("ecies: invalid message") ) - // NIST SP 800-56 Concatenation Key Derivation Function (see section 5.8.1). func concatKDF(hash hash.Hash, z, s1 []byte, kdLen int) []byte { counterBytes := make([]byte, 4) k := make([]byte, 0, kdLen+hash.Size()) for counter := uint32(1); len(k) < kdLen; counter++ { - hash.Reset() binary.BigEndian.PutUint32(counterBytes, counter) hash.Write(counterBytes) hash.Write(z) hash.Write(s1) k = hash.Sum(k) + hash.Reset() } return k[:kdLen] } From 81823d4757b535b2c3e429c7c1afa1fbdcae6139 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 1 Apr 2020 12:42:23 +0200 Subject: [PATCH 10/17] crypto/ecies: avoid overallocation of key buffer --- crypto/ecies/ecies.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crypto/ecies/ecies.go b/crypto/ecies/ecies.go index a86c735f07a2..c7169a770e2a 100644 --- a/crypto/ecies/ecies.go +++ b/crypto/ecies/ecies.go @@ -146,7 +146,7 @@ var ( // NIST SP 800-56 Concatenation Key Derivation Function (see section 5.8.1). func concatKDF(hash hash.Hash, z, s1 []byte, kdLen int) []byte { counterBytes := make([]byte, 4) - k := make([]byte, 0, kdLen+hash.Size()) + k := make([]byte, 0, roundup(kdLen, hash.Size())) for counter := uint32(1); len(k) < kdLen; counter++ { binary.BigEndian.PutUint32(counterBytes, counter) hash.Write(counterBytes) @@ -158,6 +158,11 @@ func concatKDF(hash hash.Hash, z, s1 []byte, kdLen int) []byte { return k[:kdLen] } +// roundup rounds size up to the nearest multiple of blocksize. +func roundup(size, blocksize int) int { + return size + blocksize - (size % blocksize) +} + // messageTag computes the MAC of a message (called the tag) as per // SEC 1, 3.5. func messageTag(hash func() hash.Hash, km, msg, shared []byte) []byte { From 026172acb5af476a33b8f4d5dae3cfe61dc32327 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 1 Apr 2020 12:45:48 +0200 Subject: [PATCH 11/17] crypto/ecies: extract KDF call into shared function This fixes the hash.Reset issue in a different way and removes some duplication. --- crypto/ecies/ecies.go | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/crypto/ecies/ecies.go b/crypto/ecies/ecies.go index c7169a770e2a..9f94047864aa 100644 --- a/crypto/ecies/ecies.go +++ b/crypto/ecies/ecies.go @@ -149,11 +149,11 @@ func concatKDF(hash hash.Hash, z, s1 []byte, kdLen int) []byte { k := make([]byte, 0, roundup(kdLen, hash.Size())) for counter := uint32(1); len(k) < kdLen; counter++ { binary.BigEndian.PutUint32(counterBytes, counter) + hash.Reset() hash.Write(counterBytes) hash.Write(z) hash.Write(s1) k = hash.Sum(k) - hash.Reset() } return k[:kdLen] } @@ -163,6 +163,17 @@ func roundup(size, blocksize int) int { return size + blocksize - (size % blocksize) } +// deriveKeys creates the encryption and MAC keys using concatKDF. +func deriveKeys(hash hash.Hash, z, s1 []byte, keyLen int) (Ke, Km []byte) { + K := concatKDF(hash, z, s1, 2*keyLen) + Ke = K[:keyLen] + Km = K[keyLen:] + hash.Reset() + hash.Write(Km) + Km = hash.Sum(Km[:0]) + return Ke, Km +} + // messageTag computes the MAC of a message (called the tag) as per // SEC 1, 3.5. func messageTag(hash func() hash.Hash, km, msg, shared []byte) []byte { @@ -238,12 +249,7 @@ func Encrypt(rand io.Reader, pub *PublicKey, m, s1, s2 []byte) (ct []byte, err e if err != nil { return } - K := concatKDF(hash, z, s1, params.KeyLen+params.KeyLen) - Ke := K[:params.KeyLen] - Km := K[params.KeyLen:] - hash.Write(Km) - Km = hash.Sum(nil) - hash.Reset() + Ke, Km := deriveKeys(hash, z, s1, params.KeyLen) em, err := symEncrypt(rand, params, Ke, m) if err != nil || len(em) <= params.BlockSize { @@ -310,22 +316,15 @@ func (prv *PrivateKey) Decrypt(c, s1, s2 []byte) (m []byte, err error) { z, err := prv.GenerateShared(R, params.KeyLen, params.KeyLen) if err != nil { - return + return nil, err } - - K := concatKDF(hash, z, s1, params.KeyLen+params.KeyLen) - Ke := K[:params.KeyLen] - Km := K[params.KeyLen:] - hash.Write(Km) - Km = hash.Sum(nil) - hash.Reset() + Ke, Km := deriveKeys(hash, z, s1, params.KeyLen) d := messageTag(params.Hash, Km, c[mStart:mEnd], s2) if subtle.ConstantTimeCompare(c[mEnd:], d) != 1 { err = ErrInvalidMessage return } - m, err = symDecrypt(params, Ke, c[mStart:mEnd]) return } From 2215cdfef409c99ecb179e0733536c8470f1d7c0 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 2 Apr 2020 00:27:25 +0200 Subject: [PATCH 12/17] crypto/ecies: fix doc comment for roundup --- crypto/ecies/ecies.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/ecies/ecies.go b/crypto/ecies/ecies.go index 9f94047864aa..cee290b34450 100644 --- a/crypto/ecies/ecies.go +++ b/crypto/ecies/ecies.go @@ -158,7 +158,7 @@ func concatKDF(hash hash.Hash, z, s1 []byte, kdLen int) []byte { return k[:kdLen] } -// roundup rounds size up to the nearest multiple of blocksize. +// roundup rounds size up to the next multiple of blocksize. func roundup(size, blocksize int) int { return size + blocksize - (size % blocksize) } From c8d0688920d4ceacfe0d47bfe14dac92866d6970 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 2 Apr 2020 20:23:52 +0200 Subject: [PATCH 13/17] crypto/ecies: add check on keyLen in ECIESParams This avoids any possibility of counter overflow in concatKDF. Also removed all the naked returns in Encrypt and Decrypt. --- crypto/ecies/ecies.go | 245 ++++--------------------------------- crypto/ecies/ecies_test.go | 4 +- crypto/ecies/params.go | 19 +++ 3 files changed, 42 insertions(+), 226 deletions(-) diff --git a/crypto/ecies/ecies.go b/crypto/ecies/ecies.go index cee290b34450..dad2b12fb4b2 100644 --- a/crypto/ecies/ecies.go +++ b/crypto/ecies/ecies.go @@ -1,198 +1,3 @@ -// Copyright (c) 2013 Kyle Isom -// Copyright (c) 2012 The Go Authors. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package ecies - -import ( - "crypto/cipher" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/hmac" - "crypto/subtle" - "encoding/binary" - "fmt" - "hash" - "io" - "math/big" -) - -var ( - ErrImport = fmt.Errorf("ecies: failed to import key") - ErrInvalidCurve = fmt.Errorf("ecies: invalid elliptic curve") - ErrInvalidParams = fmt.Errorf("ecies: invalid ECIES parameters") - ErrInvalidPublicKey = fmt.Errorf("ecies: invalid public key") - ErrSharedKeyIsPointAtInfinity = fmt.Errorf("ecies: shared key is point at infinity") - ErrSharedKeyTooBig = fmt.Errorf("ecies: shared key params are too big") -) - -// PublicKey is a representation of an elliptic curve public key. -type PublicKey struct { - X *big.Int - Y *big.Int - elliptic.Curve - Params *ECIESParams -} - -// Export an ECIES public key as an ECDSA public key. -func (pub *PublicKey) ExportECDSA() *ecdsa.PublicKey { - return &ecdsa.PublicKey{Curve: pub.Curve, X: pub.X, Y: pub.Y} -} - -// Import an ECDSA public key as an ECIES public key. -func ImportECDSAPublic(pub *ecdsa.PublicKey) *PublicKey { - return &PublicKey{ - X: pub.X, - Y: pub.Y, - Curve: pub.Curve, - Params: ParamsFromCurve(pub.Curve), - } -} - -// PrivateKey is a representation of an elliptic curve private key. -type PrivateKey struct { - PublicKey - D *big.Int -} - -// Export an ECIES private key as an ECDSA private key. -func (prv *PrivateKey) ExportECDSA() *ecdsa.PrivateKey { - pub := &prv.PublicKey - pubECDSA := pub.ExportECDSA() - return &ecdsa.PrivateKey{PublicKey: *pubECDSA, D: prv.D} -} - -// Import an ECDSA private key as an ECIES private key. -func ImportECDSA(prv *ecdsa.PrivateKey) *PrivateKey { - pub := ImportECDSAPublic(&prv.PublicKey) - return &PrivateKey{*pub, prv.D} -} - -// Generate an elliptic curve public / private keypair. If params is nil, -// the recommended default parameters for the key will be chosen. -func GenerateKey(rand io.Reader, curve elliptic.Curve, params *ECIESParams) (prv *PrivateKey, err error) { - pb, x, y, err := elliptic.GenerateKey(curve, rand) - if err != nil { - return - } - prv = new(PrivateKey) - prv.PublicKey.X = x - prv.PublicKey.Y = y - prv.PublicKey.Curve = curve - prv.D = new(big.Int).SetBytes(pb) - if params == nil { - params = ParamsFromCurve(curve) - } - prv.PublicKey.Params = params - return -} - -// MaxSharedKeyLength returns the maximum length of the shared key the -// public key can produce. -func MaxSharedKeyLength(pub *PublicKey) int { - return (pub.Curve.Params().BitSize + 7) / 8 -} - -// ECDH key agreement method used to establish secret keys for encryption. -func (prv *PrivateKey) GenerateShared(pub *PublicKey, skLen, macLen int) (sk []byte, err error) { - if prv.PublicKey.Curve != pub.Curve { - return nil, ErrInvalidCurve - } - if skLen+macLen > MaxSharedKeyLength(pub) { - return nil, ErrSharedKeyTooBig - } - - x, _ := pub.Curve.ScalarMult(pub.X, pub.Y, prv.D.Bytes()) - if x == nil { - return nil, ErrSharedKeyIsPointAtInfinity - } - - sk = make([]byte, skLen+macLen) - skBytes := x.Bytes() - copy(sk[len(sk)-len(skBytes):], skBytes) - return sk, nil -} - -var ( - ErrSharedTooLong = fmt.Errorf("ecies: shared secret is too long") - ErrInvalidMessage = fmt.Errorf("ecies: invalid message") -) - -// NIST SP 800-56 Concatenation Key Derivation Function (see section 5.8.1). -func concatKDF(hash hash.Hash, z, s1 []byte, kdLen int) []byte { - counterBytes := make([]byte, 4) - k := make([]byte, 0, roundup(kdLen, hash.Size())) - for counter := uint32(1); len(k) < kdLen; counter++ { - binary.BigEndian.PutUint32(counterBytes, counter) - hash.Reset() - hash.Write(counterBytes) - hash.Write(z) - hash.Write(s1) - k = hash.Sum(k) - } - return k[:kdLen] -} - -// roundup rounds size up to the next multiple of blocksize. -func roundup(size, blocksize int) int { - return size + blocksize - (size % blocksize) -} - -// deriveKeys creates the encryption and MAC keys using concatKDF. -func deriveKeys(hash hash.Hash, z, s1 []byte, keyLen int) (Ke, Km []byte) { - K := concatKDF(hash, z, s1, 2*keyLen) - Ke = K[:keyLen] - Km = K[keyLen:] - hash.Reset() - hash.Write(Km) - Km = hash.Sum(Km[:0]) - return Ke, Km -} - -// messageTag computes the MAC of a message (called the tag) as per -// SEC 1, 3.5. -func messageTag(hash func() hash.Hash, km, msg, shared []byte) []byte { - mac := hmac.New(hash, km) - mac.Write(msg) - mac.Write(shared) - tag := mac.Sum(nil) - return tag -} - -// Generate an initialisation vector for CTR mode. -func generateIV(params *ECIESParams, rand io.Reader) (iv []byte, err error) { - iv = make([]byte, params.BlockSize) - _, err = io.ReadFull(rand, iv) - return -} - -// symEncrypt carries out CTR encryption using the block cipher specified in the -// parameters. func symEncrypt(rand io.Reader, params *ECIESParams, key, m []byte) (ct []byte, err error) { c, err := params.Cipher(key) if err != nil { @@ -232,28 +37,27 @@ func symDecrypt(params *ECIESParams, key, ct []byte) (m []byte, err error) { // ciphertext. s1 is fed into key derivation, s2 is fed into the MAC. If the // shared information parameters aren't being used, they should be nil. func Encrypt(rand io.Reader, pub *PublicKey, m, s1, s2 []byte) (ct []byte, err error) { - params := pub.Params - if params == nil { - if params = ParamsFromCurve(pub.Curve); params == nil { - err = ErrUnsupportedECIESParameters - return - } + params, err := pubkeyParams(pub) + if err != nil { + return nil, err } + R, err := GenerateKey(rand, pub.Curve, params) if err != nil { - return + return nil, err } - hash := params.Hash() z, err := R.GenerateShared(pub, params.KeyLen, params.KeyLen) if err != nil { - return + return nil, err } + + hash := params.Hash() Ke, Km := deriveKeys(hash, z, s1, params.KeyLen) em, err := symEncrypt(rand, params, Ke, m) if err != nil || len(em) <= params.BlockSize { - return + return nil, err } d := messageTag(params.Hash, Km, em, s2) @@ -263,7 +67,7 @@ func Encrypt(rand io.Reader, pub *PublicKey, m, s1, s2 []byte) (ct []byte, err e copy(ct, Rb) copy(ct[len(Rb):], em) copy(ct[len(Rb)+len(em):], d) - return + return ct, nil } // Decrypt decrypts an ECIES ciphertext. @@ -271,13 +75,11 @@ func (prv *PrivateKey) Decrypt(c, s1, s2 []byte) (m []byte, err error) { if len(c) == 0 { return nil, ErrInvalidMessage } - params := prv.PublicKey.Params - if params == nil { - if params = ParamsFromCurve(prv.PublicKey.Curve); params == nil { - err = ErrUnsupportedECIESParameters - return - } + params, err := pubkeyParams(&prv.PublicKey) + if err != nil { + return nil, err } + hash := params.Hash() var ( @@ -291,12 +93,10 @@ func (prv *PrivateKey) Decrypt(c, s1, s2 []byte) (m []byte, err error) { case 2, 3, 4: rLen = (prv.PublicKey.Curve.Params().BitSize + 7) / 4 if len(c) < (rLen + hLen + 1) { - err = ErrInvalidMessage - return + return nil, ErrInvalidMessage } default: - err = ErrInvalidPublicKey - return + return nil, ErrInvalidPublicKey } mStart = rLen @@ -306,12 +106,10 @@ func (prv *PrivateKey) Decrypt(c, s1, s2 []byte) (m []byte, err error) { R.Curve = prv.PublicKey.Curve R.X, R.Y = elliptic.Unmarshal(R.Curve, c[:rLen]) if R.X == nil { - err = ErrInvalidPublicKey - return + return nil, ErrInvalidPublicKey } if !R.Curve.IsOnCurve(R.X, R.Y) { - err = ErrInvalidCurve - return + return nil, ErrInvalidCurve } z, err := prv.GenerateShared(R, params.KeyLen, params.KeyLen) @@ -322,9 +120,8 @@ func (prv *PrivateKey) Decrypt(c, s1, s2 []byte) (m []byte, err error) { d := messageTag(params.Hash, Km, c[mStart:mEnd], s2) if subtle.ConstantTimeCompare(c[mEnd:], d) != 1 { - err = ErrInvalidMessage - return + return nil, ErrInvalidMessage } - m, err = symDecrypt(params, Ke, c[mStart:mEnd]) - return + + return symDecrypt(params, Ke, c[mStart:mEnd]) } diff --git a/crypto/ecies/ecies_test.go b/crypto/ecies/ecies_test.go index f0fd8734c870..0a6aeb2b5175 100644 --- a/crypto/ecies/ecies_test.go +++ b/crypto/ecies/ecies_test.go @@ -299,8 +299,8 @@ func TestParamSelection(t *testing.T) { func testParamSelection(t *testing.T, c testCase) { params := ParamsFromCurve(c.Curve) - if params == nil && c.Expected != nil { - t.Fatalf("%s (%s)\n", ErrInvalidParams.Error(), c.Name) + if params == nil { + t.Fatal("ParamsFromCurve returned nil") } else if params != nil && !cmpParams(params, c.Expected) { t.Fatalf("ecies: parameters should be invalid (%s)\n", c.Name) } diff --git a/crypto/ecies/params.go b/crypto/ecies/params.go index 6312daf5a1c1..af34639dfa77 100644 --- a/crypto/ecies/params.go +++ b/crypto/ecies/params.go @@ -49,8 +49,14 @@ var ( DefaultCurve = ethcrypto.S256() ErrUnsupportedECDHAlgorithm = fmt.Errorf("ecies: unsupported ECDH algorithm") ErrUnsupportedECIESParameters = fmt.Errorf("ecies: unsupported ECIES parameters") + ErrInvalidKeyLen = fmt.Errorf("ecies: invalid key size (> 256) in ECIESParams") ) +// Maximem key size is limited to prevent overflow of the counter +// in ConcatKDF. While the theoretical limit is much higher, no known +// cipher uses keys larger than 512 bytes. +const maxKeyLen = 512 + type ECIESParams struct { Hash func() hash.Hash // hash function hashAlgo crypto.Hash @@ -115,3 +121,16 @@ func AddParamsForCurve(curve elliptic.Curve, params *ECIESParams) { func ParamsFromCurve(curve elliptic.Curve) (params *ECIESParams) { return paramsFromCurve[curve] } + +func pubkeyParams(key *PublicKey) (*ECIESParams, error) { + params := key.Params + if params == nil { + if params = ParamsFromCurve(key.Curve); params == nil { + return nil, ErrUnsupportedECIESParameters + } + } + if params.KeyLen > maxKeyLen { + return nil, ErrInvalidKeyLen + } + return params, nil +} From 623f49af8a91c98532ab8baeda504f311367e1b9 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 2 Apr 2020 20:26:23 +0200 Subject: [PATCH 14/17] crypto/ecies: remove redundant on-curve check in Decrypt crypto/elliptic already checks whether the input is a valid curve point since Go 1.5. --- crypto/ecies/ecies.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/crypto/ecies/ecies.go b/crypto/ecies/ecies.go index dad2b12fb4b2..a1b5dbbfae57 100644 --- a/crypto/ecies/ecies.go +++ b/crypto/ecies/ecies.go @@ -108,9 +108,6 @@ func (prv *PrivateKey) Decrypt(c, s1, s2 []byte) (m []byte, err error) { if R.X == nil { return nil, ErrInvalidPublicKey } - if !R.Curve.IsOnCurve(R.X, R.Y) { - return nil, ErrInvalidCurve - } z, err := prv.GenerateShared(R, params.KeyLen, params.KeyLen) if err != nil { From 15c72be0e3a833a152b25d5ca33a3b3c7bc5e1cf Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 2 Apr 2020 20:41:18 +0200 Subject: [PATCH 15/17] crypto/ecies: oops --- crypto/ecies/ecies.go | 193 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) diff --git a/crypto/ecies/ecies.go b/crypto/ecies/ecies.go index a1b5dbbfae57..64b5a99d03a7 100644 --- a/crypto/ecies/ecies.go +++ b/crypto/ecies/ecies.go @@ -1,3 +1,196 @@ +// Copyright (c) 2013 Kyle Isom +// Copyright (c) 2012 The Go Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package ecies + +import ( + "crypto/cipher" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/hmac" + "crypto/subtle" + "encoding/binary" + "fmt" + "hash" + "io" + "math/big" +) + +var ( + ErrImport = fmt.Errorf("ecies: failed to import key") + ErrInvalidCurve = fmt.Errorf("ecies: invalid elliptic curve") + ErrInvalidPublicKey = fmt.Errorf("ecies: invalid public key") + ErrSharedKeyIsPointAtInfinity = fmt.Errorf("ecies: shared key is point at infinity") + ErrSharedKeyTooBig = fmt.Errorf("ecies: shared key params are too big") +) + +// PublicKey is a representation of an elliptic curve public key. +type PublicKey struct { + X *big.Int + Y *big.Int + elliptic.Curve + Params *ECIESParams +} + +// Export an ECIES public key as an ECDSA public key. +func (pub *PublicKey) ExportECDSA() *ecdsa.PublicKey { + return &ecdsa.PublicKey{Curve: pub.Curve, X: pub.X, Y: pub.Y} +} + +// Import an ECDSA public key as an ECIES public key. +func ImportECDSAPublic(pub *ecdsa.PublicKey) *PublicKey { + return &PublicKey{ + X: pub.X, + Y: pub.Y, + Curve: pub.Curve, + Params: ParamsFromCurve(pub.Curve), + } +} + +// PrivateKey is a representation of an elliptic curve private key. +type PrivateKey struct { + PublicKey + D *big.Int +} + +// Export an ECIES private key as an ECDSA private key. +func (prv *PrivateKey) ExportECDSA() *ecdsa.PrivateKey { + pub := &prv.PublicKey + pubECDSA := pub.ExportECDSA() + return &ecdsa.PrivateKey{PublicKey: *pubECDSA, D: prv.D} +} + +// Import an ECDSA private key as an ECIES private key. +func ImportECDSA(prv *ecdsa.PrivateKey) *PrivateKey { + pub := ImportECDSAPublic(&prv.PublicKey) + return &PrivateKey{*pub, prv.D} +} + +// Generate an elliptic curve public / private keypair. If params is nil, +// the recommended default parameters for the key will be chosen. +func GenerateKey(rand io.Reader, curve elliptic.Curve, params *ECIESParams) (prv *PrivateKey, err error) { + pb, x, y, err := elliptic.GenerateKey(curve, rand) + if err != nil { + return + } + prv = new(PrivateKey) + prv.PublicKey.X = x + prv.PublicKey.Y = y + prv.PublicKey.Curve = curve + prv.D = new(big.Int).SetBytes(pb) + if params == nil { + params = ParamsFromCurve(curve) + } + prv.PublicKey.Params = params + return +} + +// MaxSharedKeyLength returns the maximum length of the shared key the +// public key can produce. +func MaxSharedKeyLength(pub *PublicKey) int { + return (pub.Curve.Params().BitSize + 7) / 8 +} + +// ECDH key agreement method used to establish secret keys for encryption. +func (prv *PrivateKey) GenerateShared(pub *PublicKey, skLen, macLen int) (sk []byte, err error) { + if prv.PublicKey.Curve != pub.Curve { + return nil, ErrInvalidCurve + } + if skLen+macLen > MaxSharedKeyLength(pub) { + return nil, ErrSharedKeyTooBig + } + + x, _ := pub.Curve.ScalarMult(pub.X, pub.Y, prv.D.Bytes()) + if x == nil { + return nil, ErrSharedKeyIsPointAtInfinity + } + + sk = make([]byte, skLen+macLen) + skBytes := x.Bytes() + copy(sk[len(sk)-len(skBytes):], skBytes) + return sk, nil +} + +var ( + ErrSharedTooLong = fmt.Errorf("ecies: shared secret is too long") + ErrInvalidMessage = fmt.Errorf("ecies: invalid message") +) + +// NIST SP 800-56 Concatenation Key Derivation Function (see section 5.8.1). +func concatKDF(hash hash.Hash, z, s1 []byte, kdLen int) []byte { + counterBytes := make([]byte, 4) + k := make([]byte, 0, roundup(kdLen, hash.Size())) + for counter := uint32(1); len(k) < kdLen; counter++ { + binary.BigEndian.PutUint32(counterBytes, counter) + hash.Reset() + hash.Write(counterBytes) + hash.Write(z) + hash.Write(s1) + k = hash.Sum(k) + } + return k[:kdLen] +} + +// roundup rounds size up to the next multiple of blocksize. +func roundup(size, blocksize int) int { + return size + blocksize - (size % blocksize) +} + +// deriveKeys creates the encryption and MAC keys using concatKDF. +func deriveKeys(hash hash.Hash, z, s1 []byte, keyLen int) (Ke, Km []byte) { + K := concatKDF(hash, z, s1, 2*keyLen) + Ke = K[:keyLen] + Km = K[keyLen:] + hash.Reset() + hash.Write(Km) + Km = hash.Sum(Km[:0]) + return Ke, Km +} + +// messageTag computes the MAC of a message (called the tag) as per +// SEC 1, 3.5. +func messageTag(hash func() hash.Hash, km, msg, shared []byte) []byte { + mac := hmac.New(hash, km) + mac.Write(msg) + mac.Write(shared) + tag := mac.Sum(nil) + return tag +} + +// Generate an initialisation vector for CTR mode. +func generateIV(params *ECIESParams, rand io.Reader) (iv []byte, err error) { + iv = make([]byte, params.BlockSize) + _, err = io.ReadFull(rand, iv) + return +} + +// symEncrypt carries out CTR encryption using the block cipher specified in the func symEncrypt(rand io.Reader, params *ECIESParams, key, m []byte) (ct []byte, err error) { c, err := params.Cipher(key) if err != nil { From d23792f333178d8a40c9156b3023cc357ef6c6cb Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 2 Apr 2020 21:33:13 +0200 Subject: [PATCH 16/17] crypto/ecies: fix typo in comment --- crypto/ecies/params.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crypto/ecies/params.go b/crypto/ecies/params.go index af34639dfa77..d2db45c06713 100644 --- a/crypto/ecies/params.go +++ b/crypto/ecies/params.go @@ -52,9 +52,9 @@ var ( ErrInvalidKeyLen = fmt.Errorf("ecies: invalid key size (> 256) in ECIESParams") ) -// Maximem key size is limited to prevent overflow of the counter -// in ConcatKDF. While the theoretical limit is much higher, no known -// cipher uses keys larger than 512 bytes. +// KeyLen is limited to prevent overflow of the counter +// in concatKDF. While the theoretical limit is much higher, +// no known cipher uses keys larger than 512 bytes. const maxKeyLen = 512 type ECIESParams struct { From 2775eedf9f79e291f26c334a2463cbca7c33d2bb Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 2 Apr 2020 21:52:14 +0200 Subject: [PATCH 17/17] crypto/ecies: fix error message --- crypto/ecies/params.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/ecies/params.go b/crypto/ecies/params.go index d2db45c06713..0bd3877ddd6f 100644 --- a/crypto/ecies/params.go +++ b/crypto/ecies/params.go @@ -49,7 +49,7 @@ var ( DefaultCurve = ethcrypto.S256() ErrUnsupportedECDHAlgorithm = fmt.Errorf("ecies: unsupported ECDH algorithm") ErrUnsupportedECIESParameters = fmt.Errorf("ecies: unsupported ECIES parameters") - ErrInvalidKeyLen = fmt.Errorf("ecies: invalid key size (> 256) in ECIESParams") + ErrInvalidKeyLen = fmt.Errorf("ecies: invalid key size (> %d) in ECIESParams", maxKeyLen) ) // KeyLen is limited to prevent overflow of the counter