Skip to content

Commit e84da03

Browse files
committed
hkdf: add Extract and Expand
RFC 5869, Section 3.3 suggests it might be sometimes appropriate to use Expand without Extract, and it is reasonable to reuse (secret, salt) with different info values, in which case the Extract can be performed once as an optimization. TLS 1.3 also needs direct access to both Extract and Expand. pseudorandomKey is ugly to look at, but that's intentional, as it signals that this should have non-obvious properties to the user. The docs will make it clear it's not the thing you should use in most cases. Fixes golang/go#28237 Change-Id: Ib43ae8cdde0663aa4752172c39aadfb0e1c35f10 Reviewed-on: https://go-review.googlesource.com/c/144398 Reviewed-by: Adam Langley <[email protected]>
1 parent d2c3f1d commit e84da03

File tree

2 files changed

+119
-22
lines changed

2 files changed

+119
-22
lines changed

hkdf/hkdf.go

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
// HKDF is a cryptographic key derivation function (KDF) with the goal of
99
// expanding limited input keying material into one or more cryptographically
1010
// strong secret keys.
11-
//
12-
// RFC 5869: https://tools.ietf.org/html/rfc5869
1311
package hkdf // import "golang.org/x/crypto/hkdf"
1412

1513
import (
@@ -19,29 +17,44 @@ import (
1917
"io"
2018
)
2119

20+
// Extract generates a pseudorandom key for use with Expand from an input secret
21+
// and an optional independent salt.
22+
//
23+
// Only use this function if you need to reuse the extracted key with multiple
24+
// Expand invocations and different context values. Most common scenarios,
25+
// including the generation of multiple keys, should use New instead.
26+
func Extract(hash func() hash.Hash, secret, salt []byte) []byte {
27+
if salt == nil {
28+
salt = make([]byte, hash().Size())
29+
}
30+
extractor := hmac.New(hash, salt)
31+
extractor.Write(secret)
32+
return extractor.Sum(nil)
33+
}
34+
2235
type hkdf struct {
2336
expander hash.Hash
2437
size int
2538

2639
info []byte
2740
counter byte
2841

29-
prev []byte
30-
cache []byte
42+
prev []byte
43+
buf []byte
3144
}
3245

3346
func (f *hkdf) Read(p []byte) (int, error) {
3447
// Check whether enough data can be generated
3548
need := len(p)
36-
remains := len(f.cache) + int(255-f.counter+1)*f.size
49+
remains := len(f.buf) + int(255-f.counter+1)*f.size
3750
if remains < need {
3851
return 0, errors.New("hkdf: entropy limit reached")
3952
}
40-
// Read from the cache, if enough data is present
41-
n := copy(p, f.cache)
53+
// Read any leftover from the buffer
54+
n := copy(p, f.buf)
4255
p = p[n:]
4356

44-
// Fill the buffer
57+
// Fill the rest of the buffer
4558
for len(p) > 0 {
4659
f.expander.Reset()
4760
f.expander.Write(f.prev)
@@ -51,25 +64,30 @@ func (f *hkdf) Read(p []byte) (int, error) {
5164
f.counter++
5265

5366
// Copy the new batch into p
54-
f.cache = f.prev
55-
n = copy(p, f.cache)
67+
f.buf = f.prev
68+
n = copy(p, f.buf)
5669
p = p[n:]
5770
}
5871
// Save leftovers for next run
59-
f.cache = f.cache[n:]
72+
f.buf = f.buf[n:]
6073

6174
return need, nil
6275
}
6376

64-
// New returns a new HKDF using the given hash, the secret keying material to expand
65-
// and optional salt and info fields.
66-
func New(hash func() hash.Hash, secret, salt, info []byte) io.Reader {
67-
if salt == nil {
68-
salt = make([]byte, hash().Size())
69-
}
70-
extractor := hmac.New(hash, salt)
71-
extractor.Write(secret)
72-
prk := extractor.Sum(nil)
77+
// Expand returns a Reader, from which keys can be read, using the given
78+
// pseudorandom key and optional context info, skipping the extraction step.
79+
//
80+
// The pseudorandomKey should have been generated by Extract, or be a uniformly
81+
// random or pseudorandom cryptographically strong key. See RFC 5869, Section
82+
// 3.3. Most common scenarios will want to use New instead.
83+
func Expand(hash func() hash.Hash, pseudorandomKey, info []byte) io.Reader {
84+
expander := hmac.New(hash, pseudorandomKey)
85+
return &hkdf{expander, expander.Size(), info, 1, nil, nil}
86+
}
7387

74-
return &hkdf{hmac.New(hash, prk), extractor.Size(), info, 1, nil, nil}
88+
// New returns a Reader, from which keys can be read, using the given hash,
89+
// secret, salt and context info. Salt and info can be nil.
90+
func New(hash func() hash.Hash, secret, salt, info []byte) io.Reader {
91+
prk := Extract(hash, secret, salt)
92+
return Expand(hash, prk, info)
7593
}

hkdf/hkdf_test.go

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ type hkdfTest struct {
1818
hash func() hash.Hash
1919
master []byte
2020
salt []byte
21+
prk []byte
2122
info []byte
2223
out []byte
2324
}
@@ -35,6 +36,12 @@ var hkdfTests = []hkdfTest{
3536
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
3637
0x08, 0x09, 0x0a, 0x0b, 0x0c,
3738
},
39+
[]byte{
40+
0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf,
41+
0x0d, 0xdc, 0x3f, 0x0d, 0xc4, 0x7b, 0xba, 0x63,
42+
0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f, 0x9c, 0x31,
43+
0x22, 0xec, 0x84, 0x4a, 0xd7, 0xc2, 0xb3, 0xe5,
44+
},
3845
[]byte{
3946
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
4047
0xf8, 0xf9,
@@ -74,6 +81,12 @@ var hkdfTests = []hkdfTest{
7481
0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
7582
0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
7683
},
84+
[]byte{
85+
0x06, 0xa6, 0xb8, 0x8c, 0x58, 0x53, 0x36, 0x1a,
86+
0x06, 0x10, 0x4c, 0x9c, 0xeb, 0x35, 0xb4, 0x5c,
87+
0xef, 0x76, 0x00, 0x14, 0x90, 0x46, 0x71, 0x01,
88+
0x4a, 0x19, 0x3f, 0x40, 0xc1, 0x5f, 0xc2, 0x44,
89+
},
7790
[]byte{
7891
0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
7992
0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
@@ -108,6 +121,12 @@ var hkdfTests = []hkdfTest{
108121
0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
109122
},
110123
[]byte{},
124+
[]byte{
125+
0x19, 0xef, 0x24, 0xa3, 0x2c, 0x71, 0x7b, 0x16,
126+
0x7f, 0x33, 0xa9, 0x1d, 0x6f, 0x64, 0x8b, 0xdf,
127+
0x96, 0x59, 0x67, 0x76, 0xaf, 0xdb, 0x63, 0x77,
128+
0xac, 0x43, 0x4c, 0x1c, 0x29, 0x3c, 0xcb, 0x04,
129+
},
111130
[]byte{},
112131
[]byte{
113132
0x8d, 0xa4, 0xe7, 0x75, 0xa5, 0x63, 0xc1, 0x8f,
@@ -118,6 +137,30 @@ var hkdfTests = []hkdfTest{
118137
0x96, 0xc8,
119138
},
120139
},
140+
{
141+
sha256.New,
142+
[]byte{
143+
0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
144+
0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
145+
0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
146+
},
147+
nil,
148+
[]byte{
149+
0x19, 0xef, 0x24, 0xa3, 0x2c, 0x71, 0x7b, 0x16,
150+
0x7f, 0x33, 0xa9, 0x1d, 0x6f, 0x64, 0x8b, 0xdf,
151+
0x96, 0x59, 0x67, 0x76, 0xaf, 0xdb, 0x63, 0x77,
152+
0xac, 0x43, 0x4c, 0x1c, 0x29, 0x3c, 0xcb, 0x04,
153+
},
154+
nil,
155+
[]byte{
156+
0x8d, 0xa4, 0xe7, 0x75, 0xa5, 0x63, 0xc1, 0x8f,
157+
0x71, 0x5f, 0x80, 0x2a, 0x06, 0x3c, 0x5a, 0x31,
158+
0xb8, 0xa1, 0x1f, 0x5c, 0x5e, 0xe1, 0x87, 0x9e,
159+
0xc3, 0x45, 0x4e, 0x5f, 0x3c, 0x73, 0x8d, 0x2d,
160+
0x9d, 0x20, 0x13, 0x95, 0xfa, 0xa4, 0xb6, 0x1a,
161+
0x96, 0xc8,
162+
},
163+
},
121164
{
122165
sha1.New,
123166
[]byte{
@@ -128,6 +171,11 @@ var hkdfTests = []hkdfTest{
128171
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
129172
0x08, 0x09, 0x0a, 0x0b, 0x0c,
130173
},
174+
[]byte{
175+
0x9b, 0x6c, 0x18, 0xc4, 0x32, 0xa7, 0xbf, 0x8f,
176+
0x0e, 0x71, 0xc8, 0xeb, 0x88, 0xf4, 0xb3, 0x0b,
177+
0xaa, 0x2b, 0xa2, 0x43,
178+
},
131179
[]byte{
132180
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
133181
0xf8, 0xf9,
@@ -167,6 +215,11 @@ var hkdfTests = []hkdfTest{
167215
0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
168216
0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
169217
},
218+
[]byte{
219+
0x8a, 0xda, 0xe0, 0x9a, 0x2a, 0x30, 0x70, 0x59,
220+
0x47, 0x8d, 0x30, 0x9b, 0x26, 0xc4, 0x11, 0x5a,
221+
0x22, 0x4c, 0xfa, 0xf6,
222+
},
170223
[]byte{
171224
0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
172225
0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
@@ -201,6 +254,11 @@ var hkdfTests = []hkdfTest{
201254
0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
202255
},
203256
[]byte{},
257+
[]byte{
258+
0xda, 0x8c, 0x8a, 0x73, 0xc7, 0xfa, 0x77, 0x28,
259+
0x8e, 0xc6, 0xf5, 0xe7, 0xc2, 0x97, 0x78, 0x6a,
260+
0xa0, 0xd3, 0x2d, 0x01,
261+
},
204262
[]byte{},
205263
[]byte{
206264
0x0a, 0xc1, 0xaf, 0x70, 0x02, 0xb3, 0xd7, 0x61,
@@ -219,7 +277,12 @@ var hkdfTests = []hkdfTest{
219277
0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
220278
},
221279
nil,
222-
[]byte{},
280+
[]byte{
281+
0x2a, 0xdc, 0xca, 0xda, 0x18, 0x77, 0x9e, 0x7c,
282+
0x20, 0x77, 0xad, 0x2e, 0xb1, 0x9d, 0x3f, 0x3e,
283+
0x73, 0x13, 0x85, 0xdd,
284+
},
285+
nil,
223286
[]byte{
224287
0x2c, 0x91, 0x11, 0x72, 0x04, 0xd7, 0x45, 0xf3,
225288
0x50, 0x0d, 0x63, 0x6a, 0x62, 0xf6, 0x4f, 0x0a,
@@ -233,6 +296,11 @@ var hkdfTests = []hkdfTest{
233296

234297
func TestHKDF(t *testing.T) {
235298
for i, tt := range hkdfTests {
299+
prk := Extract(tt.hash, tt.master, tt.salt)
300+
if !bytes.Equal(prk, tt.prk) {
301+
t.Errorf("test %d: incorrect PRK: have %v, need %v.", i, prk, tt.prk)
302+
}
303+
236304
hkdf := New(tt.hash, tt.master, tt.salt, tt.info)
237305
out := make([]byte, len(tt.out))
238306

@@ -244,6 +312,17 @@ func TestHKDF(t *testing.T) {
244312
if !bytes.Equal(out, tt.out) {
245313
t.Errorf("test %d: incorrect output: have %v, need %v.", i, out, tt.out)
246314
}
315+
316+
hkdf = Expand(tt.hash, prk, tt.info)
317+
318+
n, err = io.ReadFull(hkdf, out)
319+
if n != len(tt.out) || err != nil {
320+
t.Errorf("test %d: not enough output bytes from Expand: %d.", i, n)
321+
}
322+
323+
if !bytes.Equal(out, tt.out) {
324+
t.Errorf("test %d: incorrect output from Expand: have %v, need %v.", i, out, tt.out)
325+
}
247326
}
248327
}
249328

0 commit comments

Comments
 (0)