Skip to content

Commit 462ddce

Browse files
lukechampinefjl
andauthored
crypto/ecies: improve concatKDF (ethereum#20836)
This removes a bunch of weird code around the counter overflow check in concatKDF and makes it actually work for different hash output sizes. The overflow check worked as follows: concatKDF applies the hash function N times, where N is roundup(kdLen, hashsize) / hashsize. N should not overflow 32 bits because that would lead to a repetition in the KDF output. A couple issues with the overflow check: - It used the hash.BlockSize, which is wrong because the block size is about the input of the hash function. Luckily, all standard hash functions have a block size that's greater than the output size, so concatKDF didn't crash, it just generated too much key material. - The check used big.Int to compare against 2^32-1. - The calculation could still overflow before reaching the check. The new code in concatKDF doesn't check for overflow. Instead, there is a new check on ECIESParams which ensures that params.KeyLen is < 512. This removes any possibility of overflow. There are a couple of miscellaneous improvements bundled in with this change: - The key buffer is pre-allocated instead of appending the hash output to an initially empty slice. - The code that uses concatKDF to derive keys is now shared between Encrypt and Decrypt. - There was a redundant invocation of IsOnCurve in Decrypt. This is now removed because elliptic.Unmarshal already checks whether the input is a valid curve point since Go 1.5. Co-authored-by: Felix Lange <fjl@twurst.com>
1 parent f7b29ec commit 462ddce

File tree

3 files changed

+93
-109
lines changed

3 files changed

+93
-109
lines changed

crypto/ecies/ecies.go

Lines changed: 47 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import (
3535
"crypto/elliptic"
3636
"crypto/hmac"
3737
"crypto/subtle"
38+
"encoding/binary"
3839
"fmt"
3940
"hash"
4041
"io"
@@ -44,7 +45,6 @@ import (
4445
var (
4546
ErrImport = fmt.Errorf("ecies: failed to import key")
4647
ErrInvalidCurve = fmt.Errorf("ecies: invalid elliptic curve")
47-
ErrInvalidParams = fmt.Errorf("ecies: invalid ECIES parameters")
4848
ErrInvalidPublicKey = fmt.Errorf("ecies: invalid public key")
4949
ErrSharedKeyIsPointAtInfinity = fmt.Errorf("ecies: shared key is point at infinity")
5050
ErrSharedKeyTooBig = fmt.Errorf("ecies: shared key params are too big")
@@ -138,57 +138,39 @@ func (prv *PrivateKey) GenerateShared(pub *PublicKey, skLen, macLen int) (sk []b
138138
}
139139

140140
var (
141-
ErrKeyDataTooLong = fmt.Errorf("ecies: can't supply requested key data")
142141
ErrSharedTooLong = fmt.Errorf("ecies: shared secret is too long")
143142
ErrInvalidMessage = fmt.Errorf("ecies: invalid message")
144143
)
145144

146-
var (
147-
big2To32 = new(big.Int).Exp(big.NewInt(2), big.NewInt(32), nil)
148-
big2To32M1 = new(big.Int).Sub(big2To32, big.NewInt(1))
149-
)
150-
151-
func incCounter(ctr []byte) {
152-
if ctr[3]++; ctr[3] != 0 {
153-
return
154-
}
155-
if ctr[2]++; ctr[2] != 0 {
156-
return
157-
}
158-
if ctr[1]++; ctr[1] != 0 {
159-
return
160-
}
161-
if ctr[0]++; ctr[0] != 0 {
162-
return
163-
}
164-
}
165-
166145
// NIST SP 800-56 Concatenation Key Derivation Function (see section 5.8.1).
167-
func concatKDF(hash hash.Hash, z, s1 []byte, kdLen int) (k []byte, err error) {
168-
if s1 == nil {
169-
s1 = make([]byte, 0)
170-
}
171-
172-
reps := ((kdLen + 7) * 8) / (hash.BlockSize() * 8)
173-
if big.NewInt(int64(reps)).Cmp(big2To32M1) > 0 {
174-
fmt.Println(big2To32M1)
175-
return nil, ErrKeyDataTooLong
176-
}
177-
178-
counter := []byte{0, 0, 0, 1}
179-
k = make([]byte, 0)
180-
181-
for i := 0; i <= reps; i++ {
182-
hash.Write(counter)
146+
func concatKDF(hash hash.Hash, z, s1 []byte, kdLen int) []byte {
147+
counterBytes := make([]byte, 4)
148+
k := make([]byte, 0, roundup(kdLen, hash.Size()))
149+
for counter := uint32(1); len(k) < kdLen; counter++ {
150+
binary.BigEndian.PutUint32(counterBytes, counter)
151+
hash.Reset()
152+
hash.Write(counterBytes)
183153
hash.Write(z)
184154
hash.Write(s1)
185-
k = append(k, hash.Sum(nil)...)
186-
hash.Reset()
187-
incCounter(counter)
155+
k = hash.Sum(k)
188156
}
157+
return k[:kdLen]
158+
}
189159

190-
k = k[:kdLen]
191-
return
160+
// roundup rounds size up to the next multiple of blocksize.
161+
func roundup(size, blocksize int) int {
162+
return size + blocksize - (size % blocksize)
163+
}
164+
165+
// deriveKeys creates the encryption and MAC keys using concatKDF.
166+
func deriveKeys(hash hash.Hash, z, s1 []byte, keyLen int) (Ke, Km []byte) {
167+
K := concatKDF(hash, z, s1, 2*keyLen)
168+
Ke = K[:keyLen]
169+
Km = K[keyLen:]
170+
hash.Reset()
171+
hash.Write(Km)
172+
Km = hash.Sum(Km[:0])
173+
return Ke, Km
192174
}
193175

194176
// messageTag computes the MAC of a message (called the tag) as per
@@ -209,7 +191,6 @@ func generateIV(params *ECIESParams, rand io.Reader) (iv []byte, err error) {
209191
}
210192

211193
// symEncrypt carries out CTR encryption using the block cipher specified in the
212-
// parameters.
213194
func symEncrypt(rand io.Reader, params *ECIESParams, key, m []byte) (ct []byte, err error) {
214195
c, err := params.Cipher(key)
215196
if err != nil {
@@ -249,36 +230,27 @@ func symDecrypt(params *ECIESParams, key, ct []byte) (m []byte, err error) {
249230
// ciphertext. s1 is fed into key derivation, s2 is fed into the MAC. If the
250231
// shared information parameters aren't being used, they should be nil.
251232
func Encrypt(rand io.Reader, pub *PublicKey, m, s1, s2 []byte) (ct []byte, err error) {
252-
params := pub.Params
253-
if params == nil {
254-
if params = ParamsFromCurve(pub.Curve); params == nil {
255-
err = ErrUnsupportedECIESParameters
256-
return
257-
}
233+
params, err := pubkeyParams(pub)
234+
if err != nil {
235+
return nil, err
258236
}
237+
259238
R, err := GenerateKey(rand, pub.Curve, params)
260239
if err != nil {
261-
return
240+
return nil, err
262241
}
263242

264-
hash := params.Hash()
265243
z, err := R.GenerateShared(pub, params.KeyLen, params.KeyLen)
266244
if err != nil {
267-
return
268-
}
269-
K, err := concatKDF(hash, z, s1, params.KeyLen+params.KeyLen)
270-
if err != nil {
271-
return
245+
return nil, err
272246
}
273-
Ke := K[:params.KeyLen]
274-
Km := K[params.KeyLen:]
275-
hash.Write(Km)
276-
Km = hash.Sum(nil)
277-
hash.Reset()
247+
248+
hash := params.Hash()
249+
Ke, Km := deriveKeys(hash, z, s1, params.KeyLen)
278250

279251
em, err := symEncrypt(rand, params, Ke, m)
280252
if err != nil || len(em) <= params.BlockSize {
281-
return
253+
return nil, err
282254
}
283255

284256
d := messageTag(params.Hash, Km, em, s2)
@@ -288,21 +260,19 @@ func Encrypt(rand io.Reader, pub *PublicKey, m, s1, s2 []byte) (ct []byte, err e
288260
copy(ct, Rb)
289261
copy(ct[len(Rb):], em)
290262
copy(ct[len(Rb)+len(em):], d)
291-
return
263+
return ct, nil
292264
}
293265

294266
// Decrypt decrypts an ECIES ciphertext.
295267
func (prv *PrivateKey) Decrypt(c, s1, s2 []byte) (m []byte, err error) {
296268
if len(c) == 0 {
297269
return nil, ErrInvalidMessage
298270
}
299-
params := prv.PublicKey.Params
300-
if params == nil {
301-
if params = ParamsFromCurve(prv.PublicKey.Curve); params == nil {
302-
err = ErrUnsupportedECIESParameters
303-
return
304-
}
271+
params, err := pubkeyParams(&prv.PublicKey)
272+
if err != nil {
273+
return nil, err
305274
}
275+
306276
hash := params.Hash()
307277

308278
var (
@@ -316,12 +286,10 @@ func (prv *PrivateKey) Decrypt(c, s1, s2 []byte) (m []byte, err error) {
316286
case 2, 3, 4:
317287
rLen = (prv.PublicKey.Curve.Params().BitSize + 7) / 4
318288
if len(c) < (rLen + hLen + 1) {
319-
err = ErrInvalidMessage
320-
return
289+
return nil, ErrInvalidMessage
321290
}
322291
default:
323-
err = ErrInvalidPublicKey
324-
return
292+
return nil, ErrInvalidPublicKey
325293
}
326294

327295
mStart = rLen
@@ -331,36 +299,19 @@ func (prv *PrivateKey) Decrypt(c, s1, s2 []byte) (m []byte, err error) {
331299
R.Curve = prv.PublicKey.Curve
332300
R.X, R.Y = elliptic.Unmarshal(R.Curve, c[:rLen])
333301
if R.X == nil {
334-
err = ErrInvalidPublicKey
335-
return
336-
}
337-
if !R.Curve.IsOnCurve(R.X, R.Y) {
338-
err = ErrInvalidCurve
339-
return
302+
return nil, ErrInvalidPublicKey
340303
}
341304

342305
z, err := prv.GenerateShared(R, params.KeyLen, params.KeyLen)
343306
if err != nil {
344-
return
307+
return nil, err
345308
}
346-
347-
K, err := concatKDF(hash, z, s1, params.KeyLen+params.KeyLen)
348-
if err != nil {
349-
return
350-
}
351-
352-
Ke := K[:params.KeyLen]
353-
Km := K[params.KeyLen:]
354-
hash.Write(Km)
355-
Km = hash.Sum(nil)
356-
hash.Reset()
309+
Ke, Km := deriveKeys(hash, z, s1, params.KeyLen)
357310

358311
d := messageTag(params.Hash, Km, c[mStart:mEnd], s2)
359312
if subtle.ConstantTimeCompare(c[mEnd:], d) != 1 {
360-
err = ErrInvalidMessage
361-
return
313+
return nil, ErrInvalidMessage
362314
}
363315

364-
m, err = symDecrypt(params, Ke, c[mStart:mEnd])
365-
return
316+
return symDecrypt(params, Ke, c[mStart:mEnd])
366317
}

crypto/ecies/ecies_test.go

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,23 @@ import (
4242
"github.com/ethereum/go-ethereum/crypto"
4343
)
4444

45-
// Ensure the KDF generates appropriately sized keys.
4645
func TestKDF(t *testing.T) {
47-
msg := []byte("Hello, world")
48-
h := sha256.New()
49-
50-
k, err := concatKDF(h, msg, nil, 64)
51-
if err != nil {
52-
t.Fatal(err)
53-
}
54-
if len(k) != 64 {
55-
t.Fatalf("KDF: generated key is the wrong size (%d instead of 64\n", len(k))
46+
tests := []struct {
47+
length int
48+
output []byte
49+
}{
50+
{6, decode("858b192fa2ed")},
51+
{32, decode("858b192fa2ed4395e2bf88dd8d5770d67dc284ee539f12da8bceaa45d06ebae0")},
52+
{48, decode("858b192fa2ed4395e2bf88dd8d5770d67dc284ee539f12da8bceaa45d06ebae0700f1ab918a5f0413b8140f9940d6955")},
53+
{64, decode("858b192fa2ed4395e2bf88dd8d5770d67dc284ee539f12da8bceaa45d06ebae0700f1ab918a5f0413b8140f9940d6955f3467fd6672cce1024c5b1effccc0f61")},
54+
}
55+
56+
for _, test := range tests {
57+
h := sha256.New()
58+
k := concatKDF(h, []byte("input"), nil, test.length)
59+
if !bytes.Equal(k, test.output) {
60+
t.Fatalf("KDF: generated key %x does not match expected output %x", k, test.output)
61+
}
5662
}
5763
}
5864

@@ -293,8 +299,8 @@ func TestParamSelection(t *testing.T) {
293299

294300
func testParamSelection(t *testing.T, c testCase) {
295301
params := ParamsFromCurve(c.Curve)
296-
if params == nil && c.Expected != nil {
297-
t.Fatalf("%s (%s)\n", ErrInvalidParams.Error(), c.Name)
302+
if params == nil {
303+
t.Fatal("ParamsFromCurve returned nil")
298304
} else if params != nil && !cmpParams(params, c.Expected) {
299305
t.Fatalf("ecies: parameters should be invalid (%s)\n", c.Name)
300306
}
@@ -401,7 +407,7 @@ func TestSharedKeyStatic(t *testing.T) {
401407
t.Fatal(ErrBadSharedKeys)
402408
}
403409

404-
sk, _ := hex.DecodeString("167ccc13ac5e8a26b131c3446030c60fbfac6aa8e31149d0869f93626a4cdf62")
410+
sk := decode("167ccc13ac5e8a26b131c3446030c60fbfac6aa8e31149d0869f93626a4cdf62")
405411
if !bytes.Equal(sk1, sk) {
406412
t.Fatalf("shared secret mismatch: want: %x have: %x", sk, sk1)
407413
}
@@ -414,3 +420,11 @@ func hexKey(prv string) *PrivateKey {
414420
}
415421
return ImportECDSA(key)
416422
}
423+
424+
func decode(s string) []byte {
425+
bytes, err := hex.DecodeString(s)
426+
if err != nil {
427+
panic(err)
428+
}
429+
return bytes
430+
}

crypto/ecies/params.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,14 @@ var (
4949
DefaultCurve = ethcrypto.S256()
5050
ErrUnsupportedECDHAlgorithm = fmt.Errorf("ecies: unsupported ECDH algorithm")
5151
ErrUnsupportedECIESParameters = fmt.Errorf("ecies: unsupported ECIES parameters")
52+
ErrInvalidKeyLen = fmt.Errorf("ecies: invalid key size (> %d) in ECIESParams", maxKeyLen)
5253
)
5354

55+
// KeyLen is limited to prevent overflow of the counter
56+
// in concatKDF. While the theoretical limit is much higher,
57+
// no known cipher uses keys larger than 512 bytes.
58+
const maxKeyLen = 512
59+
5460
type ECIESParams struct {
5561
Hash func() hash.Hash // hash function
5662
hashAlgo crypto.Hash
@@ -115,3 +121,16 @@ func AddParamsForCurve(curve elliptic.Curve, params *ECIESParams) {
115121
func ParamsFromCurve(curve elliptic.Curve) (params *ECIESParams) {
116122
return paramsFromCurve[curve]
117123
}
124+
125+
func pubkeyParams(key *PublicKey) (*ECIESParams, error) {
126+
params := key.Params
127+
if params == nil {
128+
if params = ParamsFromCurve(key.Curve); params == nil {
129+
return nil, ErrUnsupportedECIESParameters
130+
}
131+
}
132+
if params.KeyLen > maxKeyLen {
133+
return nil, ErrInvalidKeyLen
134+
}
135+
return params, nil
136+
}

0 commit comments

Comments
 (0)