-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathkdf_utils.go
148 lines (132 loc) · 4.01 KB
/
kdf_utils.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
package ecies
import (
"crypto"
"encoding/binary"
"errors"
"hash"
"io"
// Force registration of SHA1 and SHA2 families of cryptographic primitives to
// reduce the burden on kdf consuming packages.
_ "crypto/sha1"
_ "crypto/sha256"
_ "crypto/sha512"
)
// We implement the key derivation function following the section 12.2 of these docs.
// https://www.shoup.net/papers/iso-2_1.pdf
// https://perso.telecom-paristech.fr/guilley/recherche/cryptoprocesseurs/ieee/00891000.pdf
// This code is modified based on the code from https://github.com/thales-e-security/xcrypto
var (
// errInvalidLengthParameter the kdf length parameter is invalid
errInvalidLengthParameter = errors.New("invalid length parameter")
// errInvalidSeedParameter a parameter is invalid.
errInvalidSeedParameter = errors.New("invalid input parameter")
)
// kdf key derivation context struct
type kdf struct {
seed []byte
other []byte
length int
iterations uint32
position int
buffer []byte
digester hash.Hash
}
// i2osp 4-byte integer marshalling.
func i2osp(i uint32) []byte {
osp := make([]byte, 4)
binary.BigEndian.PutUint32(osp, i)
return osp
}
// min select the minimum value of a and b.
func min(a, b int) int {
if a < b {
return a
}
return b
}
// Divide x by divisor rounding up. This is equivalent to the math.Ceil(float64(x)/float64(divisor)) using fixed point
// integer arithmetic.
func ceil(x, divisor int64) int64 {
return (x + (divisor - 1)) / divisor
}
// Read the next len(p) bytes from the kdf context.
func (kdf *kdf) read(p []byte) (int, error) {
var n int
// When there's no data left return EOF.
if kdf.length-kdf.position == 0 {
return 0, io.EOF
}
// Read the minimum of everything requested or whatever's left.
toRead := min(len(p), kdf.length-kdf.position)
// Use buffered data first to attempt to satisfy request.
if len(kdf.buffer) > 0 {
fromBuffer := min(len(kdf.buffer), toRead)
copy(p, kdf.buffer[:fromBuffer])
kdf.buffer = kdf.buffer[fromBuffer:]
n = fromBuffer
toRead -= fromBuffer
}
// If we completely satisfied the read from cache or there's no more data return.
if toRead == 0 {
kdf.position += n
return n, nil
}
// Calculate the number of full hash outputs required to satisfy request.
iterations := ceil(int64(toRead), int64(kdf.digester.Size()))
for i := int64(0); i < iterations; i++ {
osp := i2osp(kdf.iterations)
kdf.iterations++
if _, err := kdf.digester.Write(kdf.seed); err != nil {
return 0, err
}
if _, err := kdf.digester.Write(osp); err != nil {
return 0, err
}
if _, err := kdf.digester.Write(kdf.other); err != nil {
return 0, err
}
t := kdf.digester.Sum(nil)
tLen := len(t)
// The last iteration may have some leftover data which we buffer for the next invocation of read.
if tLen > toRead {
kdf.buffer = t[toRead:]
tLen = toRead
}
copy(p[n:], t[:tLen])
n += tLen
toRead -= tLen
kdf.digester.Reset()
}
kdf.position += n
return n, nil
}
func newKDF(seed, other []byte, hash crypto.Hash, offset uint32, length int) (*kdf, error) {
if len(seed) == 0 {
return nil, errInvalidSeedParameter
}
// See sections 6.2.2.2 and 6.2.3.2 of https://www.shoup.net/iso/std6.pdf.
// The maximum length is bounded by a 32-bit unsigned counter used in each iteration of the the kdf's inner digest
// loop. In this loop the counter is incremented by 1 and hash.Size() bytes are returned to the caller.
k := ceil(int64(length), int64(hash.Size()))
if k <= 0 || k > ((1<<32)-int64(offset)) {
return nil, errInvalidLengthParameter
}
kdf := &kdf{
seed: seed,
other: other,
length: length,
iterations: offset,
position: 0,
buffer: nil,
digester: hash.New(),
}
return kdf, nil
}
// newKDF1 create a new KDF1 context.
func newKDF1(seed, other []byte, hash crypto.Hash, length int) (*kdf, error) {
return newKDF(seed, other, hash, 0, length)
}
// newKDF2 create a new KDF2 context.
func newKDF2(seed, other []byte, hash crypto.Hash, length int) (*kdf, error) {
return newKDF(seed, other, hash, 1, length)
}