-
Notifications
You must be signed in to change notification settings - Fork 107
/
hmac_drbg.go
216 lines (176 loc) · 5.65 KB
/
hmac_drbg.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
// Package drbg implements the HMAC_DRBG construct as per NIST Special
// Publication 800-90A Revision 1.
package drbg
import (
"crypto"
"crypto/hmac"
"errors"
"math"
)
const (
// MaxLength is the maximum length of the input entropy, personalization
// nonce, and additional input bit strings in bytes.
//
// Note: SP 800-90A R1 allows 8 bits more than the value used.
MaxLength = math.MaxUint32 // 2^35 - 8 bits.
// ReseedInterval is the maximum number of requests that can be made
// before a reseed operation is required.
ReseedInterval = 1 << 48
maxBytesPerRequest = 1 << 16 // 2^19 bits.
)
// Drbg is a keyed and initialized HMAC_DRBG instance.
//
// Note: This implementation does not support reseeding, and if the internal
// counter is exceeded, the instance will be rendered unusable. The limit is
// sufficiently large that it will not be hit under realistic usage.
type Drbg struct {
v []byte
k []byte
reseedCounter uint64
hash crypto.Hash
}
// Read reads len(p) bytes from HMAC_DRBG. It will always succeed completely
// (n = len(p)) or not at all. On failures, any partial reads already copied
// into p will be overwritten by NUL bytes.
//
// Note: 0 length reads are a no-op and do not advance the HMAC_DRBG state.
func (r *Drbg) Read(p []byte) (n int, err error) {
toRead, off := len(p), 0
for toRead > 0 {
readSz := toRead
if readSz > maxBytesPerRequest {
readSz = maxBytesPerRequest
}
b, err := r.generate(readSz, nil)
if err != nil {
for i := 0; i < off; i++ {
p[i] = 0
}
return 0, err
}
copy(p[off:], b)
off += readSz
toRead -= readSz
}
return off, nil
}
func (r *Drbg) generate(requestedNumberOfBytes int, additionalInput []byte) ([]byte, error) {
if requestedNumberOfBytes < 0 {
return nil, errors.New("drbg: invalid output size requested")
}
if requestedNumberOfBytes > maxBytesPerRequest {
return nil, errors.New("drbg: excessive output size requested")
}
if len(additionalInput) > MaxLength {
return nil, errors.New("drbg: excessive additionalInput size")
}
// 10.1.2.5 Generating Pseudorandom Bits Using HMAC_DRBG
// HMAC_DRBG Generate Process:
// 1. If reseed_counter > reseed_interval, then return an indication
// that a reseed is required.
if r.reseedCounter > ReseedInterval {
return nil, errors.New("drbg: reseed required")
}
// 2. If additional_input != Null, then (Key, V) = HMAC_DRBG_Update
// (additional_input, Key, V).
if len(additionalInput) != 0 {
r.update(additionalInput)
}
// 3. temp = Null.
outLen := r.hash.Size()
tempSize := ((requestedNumberOfBytes + outLen - 1) / outLen) * outLen
temp := make([]byte, 0, tempSize)
// 4. While (len (temp) < requested_number_of_bits) do:
for len(temp) < requestedNumberOfBytes {
// 4.1 V = HMAC (Key, V).
r.v = updateV(r.hash, r.k, r.v)
// 4.2 temp = temp || V.
temp = append(temp, r.v...)
}
// 5. returned_bits = leftmost (temp, requested_number_of_bits).
temp = temp[:requestedNumberOfBytes]
// 6. (Key, V) = HMAC_DRBG_Update (additional_input, Key, V).
r.update(additionalInput)
// 7. reseed_counter = reseed_counter + 1.
r.reseedCounter++
// 8. Return (SUCCESS, returned_bits, Key, V, reseed_counter).
return temp, nil
}
func (r *Drbg) update(providedData []byte) {
// 10.1.2.2 The HMAC_DRBG Update Function (Update)
// HMAC_DRBG Update Process:
// 1. K = HMAC (K, V || 0x00 || provided_data).
k := updateK(r.hash, r.k, r.v, providedData, 0x00)
// 2. V = HMAC (K, V).
v := updateV(r.hash, k, r.v)
// 3. If (provided_data = Null), then return K and V.
if len(providedData) == 0 {
r.v, r.k = v, k
return
}
// 4. K = HMAC (K, V || 0x01 || provided_data).
k = updateK(r.hash, k, v, providedData, 0x01)
// 5. V = HMAC (K, V).
v = updateV(r.hash, k, v)
// 6. Return (K, V).
r.v, r.k = v, k
}
// nolint: gas
func updateK(hash crypto.Hash, k, v, providedData []byte, b byte) []byte {
mac := hmac.New(hash.New, k)
_, _ = mac.Write(v)
_, _ = mac.Write([]byte{b})
if len(providedData) > 0 {
_, _ = mac.Write(providedData)
}
return mac.Sum(nil)
}
// nolint: gas
func updateV(hash crypto.Hash, k, v []byte) []byte {
mac := hmac.New(hash.New, k)
_, _ = mac.Write(v)
return mac.Sum(nil)
}
// New creates a new HMAC_DRBG instance with the specified configuration.
func New(hash crypto.Hash, entropyInput, nonce, personalizationString []byte) (*Drbg, error) {
outLen := hash.Size()
minLen := outLen / 2
eiLen, nonceLen, psLen := len(entropyInput), len(nonce), len(personalizationString)
if eiLen < minLen {
return nil, errors.New("drbg: insufficient entropyInput")
}
if eiLen > MaxLength {
return nil, errors.New("drbg: excessive entropyInput size")
}
if nonceLen > MaxLength {
return nil, errors.New("drbg: excessive nonce size")
}
if psLen > MaxLength {
return nil, errors.New("drbg: excessive personalizationString size")
}
// 10.1.2.3 Instantiation of HMAC_DRBG
// HMAC_DRBG Instantiate Process:
// 1. seed_material = entropy_input || nonce || personalization_string.
seedMaterial := make([]byte, 0, eiLen+nonceLen+psLen)
seedMaterial = append(seedMaterial, entropyInput...)
seedMaterial = append(seedMaterial, nonce...)
seedMaterial = append(seedMaterial, personalizationString...)
// 2. Key = 0x00 00...00. Comment: outlen bits.
key := make([]byte, outLen)
// 3. V = 0x01 01...01. Comment: outlen bits.
v := make([]byte, outLen)
for i := range v {
v[i] = 0x01
}
// Comment: Update Key and V.
rng := &Drbg{
v: v,
k: key,
reseedCounter: 1, // 5. reseed_counter = 1.
hash: hash,
}
// 4. (Key, V) = HMAC_DRBG_Update (seed_material, Key, V).
rng.update(seedMaterial)
// 6. Return (V, Key, reseed_counter).
return rng, nil
}