github.com/emmansun/gmsm@v0.29.1/internal/cryptotest/hash.go (about) 1 // Copyright 2024 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package cryptotest 6 7 import ( 8 "bytes" 9 "hash" 10 "io" 11 "math/rand" 12 "testing" 13 "time" 14 ) 15 16 type MakeHash func() hash.Hash 17 18 // TestHash performs a set of tests on hash.Hash implementations, checking the 19 // documented requirements of Write, Sum, Reset, Size, and BlockSize. 20 func TestHash(t *testing.T, mh MakeHash) { 21 22 // Test that Sum returns an appended digest matching output of Size 23 t.Run("SumAppend", func(t *testing.T) { 24 h := mh() 25 rng := newRandReader(t) 26 27 emptyBuff := []byte("") 28 shortBuff := []byte("a") 29 longBuff := make([]byte, h.BlockSize()+1) 30 rng.Read(longBuff) 31 32 // Set of example strings to append digest to 33 prefixes := [][]byte{nil, emptyBuff, shortBuff, longBuff} 34 35 // Go to each string and check digest gets appended to and is correct size. 36 for _, prefix := range prefixes { 37 h.Reset() 38 39 sum := getSum(t, h, prefix) // Append new digest to prefix 40 41 // Check that Sum didn't alter the prefix 42 if !bytes.Equal(sum[0:len(prefix)], prefix) { 43 t.Errorf("Sum alters passed buffer instead of appending; got %x, want %x", sum[0:len(prefix)], prefix) 44 } 45 46 // Check that the appended sum wasn't affected by the prefix 47 if expectedSum := getSum(t, h, nil); !bytes.Equal(sum[len(prefix):], expectedSum) { 48 t.Errorf("Sum behavior affected by data in the input buffer; got %x, want %x", sum[len(prefix):], expectedSum) 49 } 50 51 // Check size of append 52 if got, want := len(sum)-len(prefix), h.Size(); got != want { 53 t.Errorf("Sum appends number of bytes != Size; got %v , want %v", got, want) 54 } 55 } 56 }) 57 58 // Test that Hash.Write never returns error. 59 t.Run("WriteWithoutError", func(t *testing.T) { 60 h := mh() 61 rng := newRandReader(t) 62 63 emptySlice := []byte("") 64 shortSlice := []byte("a") 65 longSlice := make([]byte, h.BlockSize()+1) 66 rng.Read(longSlice) 67 68 // Set of example strings to append digest to 69 slices := [][]byte{emptySlice, shortSlice, longSlice} 70 71 for _, slice := range slices { 72 writeToHash(t, h, slice) // Writes and checks Write doesn't error 73 } 74 }) 75 76 t.Run("ResetState", func(t *testing.T) { 77 h := mh() 78 rng := newRandReader(t) 79 80 emptySum := getSum(t, h, nil) 81 82 // Write to hash and then Reset it and see if Sum is same as emptySum 83 writeEx := make([]byte, h.BlockSize()) 84 rng.Read(writeEx) 85 writeToHash(t, h, writeEx) 86 h.Reset() 87 resetSum := getSum(t, h, nil) 88 89 if !bytes.Equal(emptySum, resetSum) { 90 t.Errorf("Reset hash yields different Sum than new hash; got %x, want %x", emptySum, resetSum) 91 } 92 }) 93 94 // Check that Write isn't reading from beyond input slice's bounds 95 t.Run("OutOfBoundsRead", func(t *testing.T) { 96 h := mh() 97 blockSize := h.BlockSize() 98 rng := newRandReader(t) 99 100 msg := make([]byte, blockSize) 101 rng.Read(msg) 102 writeToHash(t, h, msg) 103 expectedDigest := getSum(t, h, nil) // Record control digest 104 105 h.Reset() 106 107 // Make a buffer with msg in the middle and data on either end 108 buff := make([]byte, blockSize*3) 109 endOfPrefix, startOfSuffix := blockSize, blockSize*2 110 111 copy(buff[endOfPrefix:startOfSuffix], msg) 112 rng.Read(buff[:endOfPrefix]) 113 rng.Read(buff[startOfSuffix:]) 114 115 writeToHash(t, h, buff[endOfPrefix:startOfSuffix]) 116 testDigest := getSum(t, h, nil) 117 118 if !bytes.Equal(testDigest, expectedDigest) { 119 t.Errorf("Write affected by data outside of input slice bounds; got %x, want %x", testDigest, expectedDigest) 120 } 121 }) 122 123 // Test that multiple calls to Write is stateful 124 t.Run("StatefulWrite", func(t *testing.T) { 125 h := mh() 126 rng := newRandReader(t) 127 128 prefix, suffix := make([]byte, h.BlockSize()), make([]byte, h.BlockSize()) 129 rng.Read(prefix) 130 rng.Read(suffix) 131 132 // Write prefix then suffix sequentially and record resulting hash 133 writeToHash(t, h, prefix) 134 writeToHash(t, h, suffix) 135 serialSum := getSum(t, h, nil) 136 137 h.Reset() 138 139 // Write prefix and suffix at the same time and record resulting hash 140 writeToHash(t, h, append(prefix, suffix...)) 141 compositeSum := getSum(t, h, nil) 142 143 // Check that sequential writing results in the same as writing all at once 144 if !bytes.Equal(compositeSum, serialSum) { 145 t.Errorf("two successive Write calls resulted in a different Sum than a single one; got %x, want %x", compositeSum, serialSum) 146 } 147 }) 148 } 149 150 // Helper function for writing. Verifies that Write does not error. 151 func writeToHash(t *testing.T, h hash.Hash, p []byte) { 152 t.Helper() 153 154 before := make([]byte, len(p)) 155 copy(before, p) 156 157 n, err := h.Write(p) 158 if err != nil || n != len(p) { 159 t.Errorf("Write returned error; got (%v, %v), want (nil, %v)", err, n, len(p)) 160 } 161 162 if !bytes.Equal(p, before) { 163 t.Errorf("Write modified input slice; got %x, want %x", p, before) 164 } 165 } 166 167 // Helper function for getting Sum. Checks that Sum doesn't change hash state. 168 func getSum(t *testing.T, h hash.Hash, buff []byte) []byte { 169 t.Helper() 170 171 testBuff := make([]byte, len(buff)) 172 copy(testBuff, buff) 173 174 sum := h.Sum(buff) 175 testSum := h.Sum(testBuff) 176 177 // Check that Sum doesn't change underlying hash state 178 if !bytes.Equal(sum, testSum) { 179 t.Errorf("successive calls to Sum yield different results; got %x, want %x", sum, testSum) 180 } 181 182 return sum 183 } 184 185 func newRandReader(t *testing.T) io.Reader { 186 seed := time.Now().UnixNano() 187 t.Logf("Deterministic RNG seed: 0x%x", seed) 188 return rand.New(rand.NewSource(seed)) 189 }