-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhash.go
189 lines (146 loc) · 5.28 KB
/
hash.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
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cryptotest
import (
"bytes"
"hash"
"io"
"math/rand"
"testing"
"time"
)
type MakeHash func() hash.Hash
// TestHash performs a set of tests on hash.Hash implementations, checking the
// documented requirements of Write, Sum, Reset, Size, and BlockSize.
func TestHash(t *testing.T, mh MakeHash) {
// Test that Sum returns an appended digest matching output of Size
t.Run("SumAppend", func(t *testing.T) {
h := mh()
rng := newRandReader(t)
emptyBuff := []byte("")
shortBuff := []byte("a")
longBuff := make([]byte, h.BlockSize()+1)
rng.Read(longBuff)
// Set of example strings to append digest to
prefixes := [][]byte{nil, emptyBuff, shortBuff, longBuff}
// Go to each string and check digest gets appended to and is correct size.
for _, prefix := range prefixes {
h.Reset()
sum := getSum(t, h, prefix) // Append new digest to prefix
// Check that Sum didn't alter the prefix
if !bytes.Equal(sum[:len(prefix)], prefix) {
t.Errorf("Sum alters passed buffer instead of appending; got %x, want %x", sum[:len(prefix)], prefix)
}
// Check that the appended sum wasn't affected by the prefix
if expectedSum := getSum(t, h, nil); !bytes.Equal(sum[len(prefix):], expectedSum) {
t.Errorf("Sum behavior affected by data in the input buffer; got %x, want %x", sum[len(prefix):], expectedSum)
}
// Check size of append
if got, want := len(sum)-len(prefix), h.Size(); got != want {
t.Errorf("Sum appends number of bytes != Size; got %v , want %v", got, want)
}
}
})
// Test that Hash.Write never returns error.
t.Run("WriteWithoutError", func(t *testing.T) {
h := mh()
rng := newRandReader(t)
emptySlice := []byte("")
shortSlice := []byte("a")
longSlice := make([]byte, h.BlockSize()+1)
rng.Read(longSlice)
// Set of example strings to append digest to
slices := [][]byte{emptySlice, shortSlice, longSlice}
for _, slice := range slices {
writeToHash(t, h, slice) // Writes and checks Write doesn't error
}
})
t.Run("ResetState", func(t *testing.T) {
h := mh()
rng := newRandReader(t)
emptySum := getSum(t, h, nil)
// Write to hash and then Reset it and see if Sum is same as emptySum
writeEx := make([]byte, h.BlockSize())
rng.Read(writeEx)
writeToHash(t, h, writeEx)
h.Reset()
resetSum := getSum(t, h, nil)
if !bytes.Equal(emptySum, resetSum) {
t.Errorf("Reset hash yields different Sum than new hash; got %x, want %x", emptySum, resetSum)
}
})
// Check that Write isn't reading from beyond input slice's bounds
t.Run("OutOfBoundsRead", func(t *testing.T) {
h := mh()
blockSize := h.BlockSize()
rng := newRandReader(t)
msg := make([]byte, blockSize)
rng.Read(msg)
writeToHash(t, h, msg)
expectedDigest := getSum(t, h, nil) // Record control digest
h.Reset()
// Make a buffer with msg in the middle and data on either end
buff := make([]byte, blockSize*3)
endOfPrefix, startOfSuffix := blockSize, blockSize*2
copy(buff[endOfPrefix:startOfSuffix], msg)
rng.Read(buff[:endOfPrefix])
rng.Read(buff[startOfSuffix:])
writeToHash(t, h, buff[endOfPrefix:startOfSuffix])
testDigest := getSum(t, h, nil)
if !bytes.Equal(testDigest, expectedDigest) {
t.Errorf("Write affected by data outside of input slice bounds; got %x, want %x", testDigest, expectedDigest)
}
})
// Test that multiple calls to Write is stateful
t.Run("StatefulWrite", func(t *testing.T) {
h := mh()
rng := newRandReader(t)
prefix, suffix := make([]byte, h.BlockSize()), make([]byte, h.BlockSize())
rng.Read(prefix)
rng.Read(suffix)
// Write prefix then suffix sequentially and record resulting hash
writeToHash(t, h, prefix)
writeToHash(t, h, suffix)
serialSum := getSum(t, h, nil)
h.Reset()
// Write prefix and suffix at the same time and record resulting hash
writeToHash(t, h, append(prefix, suffix...))
compositeSum := getSum(t, h, nil)
// Check that sequential writing results in the same as writing all at once
if !bytes.Equal(compositeSum, serialSum) {
t.Errorf("two successive Write calls resulted in a different Sum than a single one; got %x, want %x", compositeSum, serialSum)
}
})
}
// Helper function for writing. Verifies that Write does not error.
func writeToHash(t *testing.T, h hash.Hash, p []byte) {
t.Helper()
before := make([]byte, len(p))
copy(before, p)
n, err := h.Write(p)
if err != nil || n != len(p) {
t.Errorf("Write returned error; got (%v, %v), want (nil, %v)", err, n, len(p))
}
if !bytes.Equal(p, before) {
t.Errorf("Write modified input slice; got %x, want %x", p, before)
}
}
// Helper function for getting Sum. Checks that Sum doesn't change hash state.
func getSum(t *testing.T, h hash.Hash, buff []byte) []byte {
t.Helper()
testBuff := make([]byte, len(buff))
copy(testBuff, buff)
sum := h.Sum(buff)
testSum := h.Sum(testBuff)
// Check that Sum doesn't change underlying hash state
if !bytes.Equal(sum, testSum) {
t.Errorf("successive calls to Sum yield different results; got %x, want %x", sum, testSum)
}
return sum
}
func newRandReader(t *testing.T) io.Reader {
seed := time.Now().UnixNano()
t.Logf("Deterministic RNG seed: 0x%x", seed)
return rand.New(rand.NewSource(seed))
}