| // Copyright 2014 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 sha3_test |
| |
| import ( |
| "bytes" |
| "crypto/internal/cryptotest" |
| . "crypto/sha3" |
| "encoding/hex" |
| "hash" |
| "io" |
| "math/rand" |
| "strings" |
| "testing" |
| ) |
| |
| const testString = "brekeccakkeccak koax koax" |
| |
| // testDigests contains functions returning hash.Hash instances |
| // with output-length equal to the KAT length for SHA-3, Keccak |
| // and SHAKE instances. |
| var testDigests = map[string]func() *SHA3{ |
| "SHA3-224": New224, |
| "SHA3-256": New256, |
| "SHA3-384": New384, |
| "SHA3-512": New512, |
| } |
| |
| // testShakes contains functions that return *sha3.SHAKE instances for |
| // with output-length equal to the KAT length. |
| var testShakes = map[string]struct { |
| constructor func(N []byte, S []byte) *SHAKE |
| defAlgoName string |
| defCustomStr string |
| }{ |
| // NewCSHAKE without customization produces same result as SHAKE |
| "SHAKE128": {NewCSHAKE128, "", ""}, |
| "SHAKE256": {NewCSHAKE256, "", ""}, |
| "cSHAKE128": {NewCSHAKE128, "CSHAKE128", "CustomString"}, |
| "cSHAKE256": {NewCSHAKE256, "CSHAKE256", "CustomString"}, |
| } |
| |
| func TestSHA3Hash(t *testing.T) { |
| cryptotest.TestAllImplementations(t, "sha3", func(t *testing.T) { |
| for name, f := range testDigests { |
| t.Run(name, func(t *testing.T) { |
| cryptotest.TestHash(t, func() hash.Hash { return f() }) |
| }) |
| } |
| }) |
| } |
| |
| // TestUnalignedWrite tests that writing data in an arbitrary pattern with |
| // small input buffers. |
| func TestUnalignedWrite(t *testing.T) { |
| cryptotest.TestAllImplementations(t, "sha3", testUnalignedWrite) |
| } |
| |
| func testUnalignedWrite(t *testing.T) { |
| buf := sequentialBytes(0x10000) |
| for alg, df := range testDigests { |
| d := df() |
| d.Reset() |
| d.Write(buf) |
| want := d.Sum(nil) |
| d.Reset() |
| for i := 0; i < len(buf); { |
| // Cycle through offsets which make a 137 byte sequence. |
| // Because 137 is prime this sequence should exercise all corner cases. |
| offsets := [17]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1} |
| for _, j := range offsets { |
| if v := len(buf) - i; v < j { |
| j = v |
| } |
| d.Write(buf[i : i+j]) |
| i += j |
| } |
| } |
| got := d.Sum(nil) |
| if !bytes.Equal(got, want) { |
| t.Errorf("Unaligned writes, alg=%s\ngot %q, want %q", alg, got, want) |
| } |
| } |
| |
| // Same for SHAKE |
| for alg, df := range testShakes { |
| want := make([]byte, 16) |
| got := make([]byte, 16) |
| d := df.constructor([]byte(df.defAlgoName), []byte(df.defCustomStr)) |
| |
| d.Reset() |
| d.Write(buf) |
| d.Read(want) |
| d.Reset() |
| for i := 0; i < len(buf); { |
| // Cycle through offsets which make a 137 byte sequence. |
| // Because 137 is prime this sequence should exercise all corner cases. |
| offsets := [17]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1} |
| for _, j := range offsets { |
| if v := len(buf) - i; v < j { |
| j = v |
| } |
| d.Write(buf[i : i+j]) |
| i += j |
| } |
| } |
| d.Read(got) |
| if !bytes.Equal(got, want) { |
| t.Errorf("Unaligned writes, alg=%s\ngot %q, want %q", alg, got, want) |
| } |
| } |
| } |
| |
| // TestAppend checks that appending works when reallocation is necessary. |
| func TestAppend(t *testing.T) { |
| cryptotest.TestAllImplementations(t, "sha3", testAppend) |
| } |
| |
| func testAppend(t *testing.T) { |
| d := New224() |
| |
| for capacity := 2; capacity <= 66; capacity += 64 { |
| // The first time around the loop, Sum will have to reallocate. |
| // The second time, it will not. |
| buf := make([]byte, 2, capacity) |
| d.Reset() |
| d.Write([]byte{0xcc}) |
| buf = d.Sum(buf) |
| expected := "0000DF70ADC49B2E76EEE3A6931B93FA41841C3AF2CDF5B32A18B5478C39" |
| if got := strings.ToUpper(hex.EncodeToString(buf)); got != expected { |
| t.Errorf("got %s, want %s", got, expected) |
| } |
| } |
| } |
| |
| // TestAppendNoRealloc tests that appending works when no reallocation is necessary. |
| func TestAppendNoRealloc(t *testing.T) { |
| cryptotest.TestAllImplementations(t, "sha3", testAppendNoRealloc) |
| } |
| |
| func testAppendNoRealloc(t *testing.T) { |
| buf := make([]byte, 1, 200) |
| d := New224() |
| d.Write([]byte{0xcc}) |
| buf = d.Sum(buf) |
| expected := "00DF70ADC49B2E76EEE3A6931B93FA41841C3AF2CDF5B32A18B5478C39" |
| if got := strings.ToUpper(hex.EncodeToString(buf)); got != expected { |
| t.Errorf("got %s, want %s", got, expected) |
| } |
| } |
| |
| // TestSqueezing checks that squeezing the full output a single time produces |
| // the same output as repeatedly squeezing the instance. |
| func TestSqueezing(t *testing.T) { |
| cryptotest.TestAllImplementations(t, "sha3", testSqueezing) |
| } |
| |
| func testSqueezing(t *testing.T) { |
| for algo, v := range testShakes { |
| d0 := v.constructor([]byte(v.defAlgoName), []byte(v.defCustomStr)) |
| d0.Write([]byte(testString)) |
| ref := make([]byte, 32) |
| d0.Read(ref) |
| |
| d1 := v.constructor([]byte(v.defAlgoName), []byte(v.defCustomStr)) |
| d1.Write([]byte(testString)) |
| var multiple []byte |
| for range ref { |
| d1.Read(make([]byte, 0)) |
| one := make([]byte, 1) |
| d1.Read(one) |
| multiple = append(multiple, one...) |
| } |
| if !bytes.Equal(ref, multiple) { |
| t.Errorf("%s: squeezing %d bytes one at a time failed", algo, len(ref)) |
| } |
| } |
| } |
| |
| // sequentialBytes produces a buffer of size consecutive bytes 0x00, 0x01, ..., used for testing. |
| // |
| // The alignment of each slice is intentionally randomized to detect alignment |
| // issues in the implementation. See https://golang.org/issue/37644. |
| // Ideally, the compiler should fuzz the alignment itself. |
| // (See https://golang.org/issue/35128.) |
| func sequentialBytes(size int) []byte { |
| alignmentOffset := rand.Intn(8) |
| result := make([]byte, size+alignmentOffset)[alignmentOffset:] |
| for i := range result { |
| result[i] = byte(i) |
| } |
| return result |
| } |
| |
| func TestReset(t *testing.T) { |
| cryptotest.TestAllImplementations(t, "sha3", testReset) |
| } |
| |
| func testReset(t *testing.T) { |
| out1 := make([]byte, 32) |
| out2 := make([]byte, 32) |
| |
| for _, v := range testShakes { |
| // Calculate hash for the first time |
| c := v.constructor(nil, []byte{0x99, 0x98}) |
| c.Write(sequentialBytes(0x100)) |
| c.Read(out1) |
| |
| // Calculate hash again |
| c.Reset() |
| c.Write(sequentialBytes(0x100)) |
| c.Read(out2) |
| |
| if !bytes.Equal(out1, out2) { |
| t.Error("\nExpected:\n", out1, "\ngot:\n", out2) |
| } |
| } |
| } |
| |
| var sinkSHA3 byte |
| |
| func TestAllocations(t *testing.T) { |
| cryptotest.SkipTestAllocations(t) |
| t.Run("New", func(t *testing.T) { |
| if allocs := testing.AllocsPerRun(10, func() { |
| h := New256() |
| b := []byte("ABC") |
| h.Write(b) |
| out := make([]byte, 0, 32) |
| out = h.Sum(out) |
| sinkSHA3 ^= out[0] |
| }); allocs > 0 { |
| t.Errorf("expected zero allocations, got %0.1f", allocs) |
| } |
| }) |
| t.Run("NewSHAKE", func(t *testing.T) { |
| if allocs := testing.AllocsPerRun(10, func() { |
| h := NewSHAKE128() |
| b := []byte("ABC") |
| h.Write(b) |
| out := make([]byte, 32) |
| h.Read(out) |
| sinkSHA3 ^= out[0] |
| }); allocs > 0 { |
| t.Errorf("expected zero allocations, got %0.1f", allocs) |
| } |
| }) |
| t.Run("Sum", func(t *testing.T) { |
| if allocs := testing.AllocsPerRun(10, func() { |
| b := []byte("ABC") |
| out := Sum256(b) |
| sinkSHA3 ^= out[0] |
| }); allocs > 0 { |
| t.Errorf("expected zero allocations, got %0.1f", allocs) |
| } |
| }) |
| t.Run("SumSHAKE", func(t *testing.T) { |
| if allocs := testing.AllocsPerRun(10, func() { |
| b := []byte("ABC") |
| out := SumSHAKE128(b, 10) |
| sinkSHA3 ^= out[0] |
| }); allocs > 0 { |
| t.Errorf("expected zero allocations, got %0.1f", allocs) |
| } |
| }) |
| } |
| |
| func TestCSHAKEAccumulated(t *testing.T) { |
| // Generated with pycryptodome@3.20.0 |
| // |
| // from Crypto.Hash import cSHAKE128 |
| // rng = cSHAKE128.new() |
| // acc = cSHAKE128.new() |
| // for n in range(200): |
| // N = rng.read(n) |
| // for s in range(200): |
| // S = rng.read(s) |
| // c = cSHAKE128.cSHAKE_XOF(data=None, custom=S, capacity=256, function=N) |
| // c.update(rng.read(100)) |
| // acc.update(c.read(200)) |
| // c = cSHAKE128.cSHAKE_XOF(data=None, custom=S, capacity=256, function=N) |
| // c.update(rng.read(168)) |
| // acc.update(c.read(200)) |
| // c = cSHAKE128.cSHAKE_XOF(data=None, custom=S, capacity=256, function=N) |
| // c.update(rng.read(200)) |
| // acc.update(c.read(200)) |
| // print(acc.read(32).hex()) |
| // |
| // and with @noble/hashes@v1.5.0 |
| // |
| // import { bytesToHex } from "@noble/hashes/utils"; |
| // import { cshake128 } from "@noble/hashes/sha3-addons"; |
| // const rng = cshake128.create(); |
| // const acc = cshake128.create(); |
| // for (let n = 0; n < 200; n++) { |
| // const N = rng.xof(n); |
| // for (let s = 0; s < 200; s++) { |
| // const S = rng.xof(s); |
| // let c = cshake128.create({ NISTfn: N, personalization: S }); |
| // c.update(rng.xof(100)); |
| // acc.update(c.xof(200)); |
| // c = cshake128.create({ NISTfn: N, personalization: S }); |
| // c.update(rng.xof(168)); |
| // acc.update(c.xof(200)); |
| // c = cshake128.create({ NISTfn: N, personalization: S }); |
| // c.update(rng.xof(200)); |
| // acc.update(c.xof(200)); |
| // } |
| // } |
| // console.log(bytesToHex(acc.xof(32))); |
| // |
| cryptotest.TestAllImplementations(t, "sha3", func(t *testing.T) { |
| t.Run("cSHAKE128", func(t *testing.T) { |
| testCSHAKEAccumulated(t, NewCSHAKE128, (1600-256)/8, |
| "bb14f8657c6ec5403d0b0e2ef3d3393497e9d3b1a9a9e8e6c81dbaa5fd809252") |
| }) |
| t.Run("cSHAKE256", func(t *testing.T) { |
| testCSHAKEAccumulated(t, NewCSHAKE256, (1600-512)/8, |
| "0baaf9250c6e25f0c14ea5c7f9bfde54c8a922c8276437db28f3895bdf6eeeef") |
| }) |
| }) |
| } |
| |
| func testCSHAKEAccumulated(t *testing.T, newCSHAKE func(N, S []byte) *SHAKE, rate int64, exp string) { |
| rnd := newCSHAKE(nil, nil) |
| acc := newCSHAKE(nil, nil) |
| for n := 0; n < 200; n++ { |
| N := make([]byte, n) |
| rnd.Read(N) |
| for s := 0; s < 200; s++ { |
| S := make([]byte, s) |
| rnd.Read(S) |
| |
| c := newCSHAKE(N, S) |
| io.CopyN(c, rnd, 100 /* < rate */) |
| io.CopyN(acc, c, 200) |
| |
| c.Reset() |
| io.CopyN(c, rnd, rate) |
| io.CopyN(acc, c, 200) |
| |
| c.Reset() |
| io.CopyN(c, rnd, 200 /* > rate */) |
| io.CopyN(acc, c, 200) |
| } |
| } |
| out := make([]byte, 32) |
| acc.Read(out) |
| if got := hex.EncodeToString(out); got != exp { |
| t.Errorf("got %s, want %s", got, exp) |
| } |
| } |
| |
| func TestCSHAKELargeS(t *testing.T) { |
| cryptotest.TestAllImplementations(t, "sha3", testCSHAKELargeS) |
| } |
| |
| func testCSHAKELargeS(t *testing.T) { |
| if testing.Short() { |
| t.Skip("skipping test in short mode.") |
| } |
| |
| // See https://go.dev/issue/66232. |
| const s = (1<<32)/8 + 1000 // s * 8 > 2^32 |
| S := make([]byte, s) |
| rnd := NewSHAKE128() |
| rnd.Read(S) |
| c := NewCSHAKE128(nil, S) |
| io.CopyN(c, rnd, 1000) |
| out := make([]byte, 32) |
| c.Read(out) |
| |
| // Generated with pycryptodome@3.20.0 |
| // |
| // from Crypto.Hash import cSHAKE128 |
| // rng = cSHAKE128.new() |
| // S = rng.read(536871912) |
| // c = cSHAKE128.new(custom=S) |
| // c.update(rng.read(1000)) |
| // print(c.read(32).hex()) |
| // |
| exp := "2cb9f237767e98f2614b8779cf096a52da9b3a849280bbddec820771ae529cf0" |
| if got := hex.EncodeToString(out); got != exp { |
| t.Errorf("got %s, want %s", got, exp) |
| } |
| } |
| |
| func TestMarshalUnmarshal(t *testing.T) { |
| cryptotest.TestAllImplementations(t, "sha3", func(t *testing.T) { |
| t.Run("SHA3-224", func(t *testing.T) { testMarshalUnmarshal(t, New224()) }) |
| t.Run("SHA3-256", func(t *testing.T) { testMarshalUnmarshal(t, New256()) }) |
| t.Run("SHA3-384", func(t *testing.T) { testMarshalUnmarshal(t, New384()) }) |
| t.Run("SHA3-512", func(t *testing.T) { testMarshalUnmarshal(t, New512()) }) |
| t.Run("SHAKE128", func(t *testing.T) { testMarshalUnmarshalSHAKE(t, NewSHAKE128()) }) |
| t.Run("SHAKE256", func(t *testing.T) { testMarshalUnmarshalSHAKE(t, NewSHAKE256()) }) |
| t.Run("cSHAKE128", func(t *testing.T) { testMarshalUnmarshalSHAKE(t, NewCSHAKE128([]byte("N"), []byte("S"))) }) |
| t.Run("cSHAKE256", func(t *testing.T) { testMarshalUnmarshalSHAKE(t, NewCSHAKE256([]byte("N"), []byte("S"))) }) |
| }) |
| } |
| |
| // TODO(filippo): move this to crypto/internal/cryptotest. |
| func testMarshalUnmarshal(t *testing.T, h *SHA3) { |
| buf := make([]byte, 200) |
| rand.Read(buf) |
| n := rand.Intn(200) |
| h.Write(buf) |
| want := h.Sum(nil) |
| h.Reset() |
| h.Write(buf[:n]) |
| b, err := h.MarshalBinary() |
| if err != nil { |
| t.Errorf("MarshalBinary: %v", err) |
| } |
| h.Write(bytes.Repeat([]byte{0}, 200)) |
| if err := h.UnmarshalBinary(b); err != nil { |
| t.Errorf("UnmarshalBinary: %v", err) |
| } |
| h.Write(buf[n:]) |
| got := h.Sum(nil) |
| if !bytes.Equal(got, want) { |
| t.Errorf("got %x, want %x", got, want) |
| } |
| } |
| |
| // TODO(filippo): move this to crypto/internal/cryptotest. |
| func testMarshalUnmarshalSHAKE(t *testing.T, h *SHAKE) { |
| buf := make([]byte, 200) |
| rand.Read(buf) |
| n := rand.Intn(200) |
| h.Write(buf) |
| want := make([]byte, 32) |
| h.Read(want) |
| h.Reset() |
| h.Write(buf[:n]) |
| b, err := h.MarshalBinary() |
| if err != nil { |
| t.Errorf("MarshalBinary: %v", err) |
| } |
| h.Write(bytes.Repeat([]byte{0}, 200)) |
| if err := h.UnmarshalBinary(b); err != nil { |
| t.Errorf("UnmarshalBinary: %v", err) |
| } |
| h.Write(buf[n:]) |
| got := make([]byte, 32) |
| h.Read(got) |
| if !bytes.Equal(got, want) { |
| t.Errorf("got %x, want %x", got, want) |
| } |
| } |
| |
| // benchmarkHash tests the speed to hash num buffers of buflen each. |
| func benchmarkHash(b *testing.B, h hash.Hash, size, num int) { |
| b.StopTimer() |
| h.Reset() |
| data := sequentialBytes(size) |
| b.SetBytes(int64(size * num)) |
| b.StartTimer() |
| |
| var state []byte |
| for i := 0; i < b.N; i++ { |
| for j := 0; j < num; j++ { |
| h.Write(data) |
| } |
| state = h.Sum(state[:0]) |
| } |
| b.StopTimer() |
| h.Reset() |
| } |
| |
| // benchmarkShake is specialized to the Shake instances, which don't |
| // require a copy on reading output. |
| func benchmarkShake(b *testing.B, h *SHAKE, size, num int) { |
| b.StopTimer() |
| h.Reset() |
| data := sequentialBytes(size) |
| d := make([]byte, 32) |
| |
| b.SetBytes(int64(size * num)) |
| b.StartTimer() |
| |
| for i := 0; i < b.N; i++ { |
| h.Reset() |
| for j := 0; j < num; j++ { |
| h.Write(data) |
| } |
| h.Read(d) |
| } |
| } |
| |
| func BenchmarkSha3_512_MTU(b *testing.B) { benchmarkHash(b, New512(), 1350, 1) } |
| func BenchmarkSha3_384_MTU(b *testing.B) { benchmarkHash(b, New384(), 1350, 1) } |
| func BenchmarkSha3_256_MTU(b *testing.B) { benchmarkHash(b, New256(), 1350, 1) } |
| func BenchmarkSha3_224_MTU(b *testing.B) { benchmarkHash(b, New224(), 1350, 1) } |
| |
| func BenchmarkShake128_MTU(b *testing.B) { benchmarkShake(b, NewSHAKE128(), 1350, 1) } |
| func BenchmarkShake256_MTU(b *testing.B) { benchmarkShake(b, NewSHAKE256(), 1350, 1) } |
| func BenchmarkShake256_16x(b *testing.B) { benchmarkShake(b, NewSHAKE256(), 16, 1024) } |
| func BenchmarkShake256_1MiB(b *testing.B) { benchmarkShake(b, NewSHAKE256(), 1024, 1024) } |
| |
| func BenchmarkSha3_512_1MiB(b *testing.B) { benchmarkHash(b, New512(), 1024, 1024) } |