--- /dev/null
+pkg crypto/sha3, method (*SHA3) Clone() (hash.Cloner, error) #69521
+pkg hash, type Cloner interface { BlockSize, Clone, Reset, Size, Sum, Write } #69521
+pkg hash, type Cloner interface, BlockSize() int #69521
+pkg hash, type Cloner interface, Clone() (Cloner, error) #69521
+pkg hash, type Cloner interface, Reset() #69521
+pkg hash, type Cloner interface, Size() int #69521
+pkg hash, type Cloner interface, Sum([]uint8) []uint8 #69521
+pkg hash, type Cloner interface, Write([]uint8) (int, error) #69521
+pkg hash/maphash, method (*Hash) Clone() (hash.Cloner, error) #69521
--- /dev/null
+The new [SHA3.Clone] method implements [hash.Cloner](/pkg/hash#Cloner).
--- /dev/null
+Hashes implementing the new [Cloner] interface can return a copy of their state.
+All standard library [Hash] implementations now implement [Cloner].
--- /dev/null
+The new [Hash.Clone] method implements [hash.Cloner](/pkg/hash#Cloner).
}
}
+func TestExtraMethods(t *testing.T) {
+ h := New(sha256.New, []byte("key"))
+ cryptotest.NoExtraMethods(t, maybeCloner(h))
+}
+
+func maybeCloner(h hash.Hash) any {
+ if c, ok := h.(hash.Cloner); ok {
+ return &c
+ }
+ return &h
+}
+
func BenchmarkHMACSHA256_1K(b *testing.B) {
key := make([]byte, 32)
buf := make([]byte, 1024)
package cryptotest
import (
+ "crypto/internal/boring"
+ "crypto/internal/fips140"
"hash"
"internal/testhash"
"io"
// 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) {
+ if boring.Enabled || fips140.Version() == "v1.0" {
+ testhash.TestHashWithoutClone(t, testhash.MakeHash(mh))
+ return
+ }
testhash.TestHash(t, testhash.MakeHash(mh))
}
"crypto/internal/fips140/sha256"
"crypto/internal/fips140/sha3"
"crypto/internal/fips140/sha512"
+ "errors"
"hash"
)
}
type HMAC struct {
+ // opad and ipad may share underlying storage with HMAC clones.
opad, ipad []byte
outer, inner hash.Hash
h.marshaled = true
}
+// Clone implements [hash.Cloner] if the underlying hash does.
+// Otherwise, it returns [errors.ErrUnsupported].
+func (h *HMAC) Clone() (hash.Cloner, error) {
+ r := *h
+ ic, ok := h.inner.(hash.Cloner)
+ if !ok {
+ return nil, errors.ErrUnsupported
+ }
+ oc, ok := h.outer.(hash.Cloner)
+ if !ok {
+ return nil, errors.ErrUnsupported
+ }
+ var err error
+ r.inner, err = ic.Clone()
+ if err != nil {
+ return nil, errors.ErrUnsupported
+ }
+ r.outer, err = oc.Clone()
+ if err != nil {
+ return nil, errors.ErrUnsupported
+ }
+ return &r, nil
+}
+
// New returns a new HMAC hash using the given [hash.Hash] type and key.
func New[H hash.Hash](h func() H, key []byte) *HMAC {
hm := &HMAC{keyLen: len(key)}
"crypto/internal/fips140"
"crypto/internal/fips140deps/byteorder"
"errors"
+ "hash"
)
// The size of a SHA-256 checksum in bytes.
return b[4:], byteorder.BEUint32(b)
}
+func (d *Digest) Clone() (hash.Cloner, error) {
+ r := *d
+ return &r, nil
+}
+
func (d *Digest) Reset() {
if !d.is224 {
d.h[0] = init0
"crypto/internal/fips140"
"crypto/internal/fips140deps/byteorder"
"errors"
+ "hash"
)
const (
return b[8:], byteorder.BEUint64(b)
}
+func (d *Digest) Clone() (hash.Cloner, error) {
+ r := *d
+ return &r, nil
+}
+
// New returns a new Digest computing the SHA-512 hash.
func New() *Digest {
d := &Digest{size: size512}
return b[4:], byteorder.BEUint32(b[0:4])
}
+func (d *digest) Clone() (hash.Cloner, error) {
+ r := *d
+ return &r, nil
+}
+
// New returns a new [hash.Hash] computing the MD5 checksum. The Hash
// also implements [encoding.BinaryMarshaler], [encoding.BinaryAppender] and
// [encoding.BinaryUnmarshaler] to marshal and unmarshal the internal
}
func TestExtraMethods(t *testing.T) {
- h := New()
+ h := maybeCloner(New())
cryptotest.NoExtraMethods(t, &h, "MarshalBinary", "UnmarshalBinary", "AppendBinary")
}
+func maybeCloner(h hash.Hash) any {
+ if c, ok := h.(hash.Cloner); ok {
+ return &c
+ }
+ return &h
+}
+
var bench = New()
var buf = make([]byte, 1024*1024*8+1)
var sum = make([]byte, bench.Size())
return b[4:], byteorder.BEUint32(b)
}
+func (d *digest) Clone() (hash.Cloner, error) {
+ r := *d
+ return &r, nil
+}
+
func (d *digest) Reset() {
d.h[0] = init0
d.h[1] = init1
}
func TestExtraMethods(t *testing.T) {
- h := New()
+ h := maybeCloner(New())
cryptotest.NoExtraMethods(t, &h, "ConstantTimeSum",
"MarshalBinary", "UnmarshalBinary", "AppendBinary")
}
+func maybeCloner(h hash.Hash) any {
+ if c, ok := h.(hash.Cloner); ok {
+ return &c
+ }
+ return &h
+}
+
var bench = New()
var buf = make([]byte, 8192)
func TestExtraMethods(t *testing.T) {
t.Run("SHA-224", func(t *testing.T) {
cryptotest.TestAllImplementations(t, "sha256", func(t *testing.T) {
- h := New224()
- cryptotest.NoExtraMethods(t, &h, "MarshalBinary", "UnmarshalBinary", "AppendBinary")
+ h := maybeCloner(New224())
+ cryptotest.NoExtraMethods(t, h, "MarshalBinary", "UnmarshalBinary", "AppendBinary")
})
})
t.Run("SHA-256", func(t *testing.T) {
cryptotest.TestAllImplementations(t, "sha256", func(t *testing.T) {
- h := New()
- cryptotest.NoExtraMethods(t, &h, "MarshalBinary", "UnmarshalBinary", "AppendBinary")
+ h := maybeCloner(New())
+ cryptotest.NoExtraMethods(t, h, "MarshalBinary", "UnmarshalBinary", "AppendBinary")
})
})
}
+func maybeCloner(h hash.Hash) any {
+ if c, ok := h.(hash.Cloner); ok {
+ return &c
+ }
+ return &h
+}
+
var bench = New()
func benchmarkSize(b *testing.B, size int) {
return s.s.UnmarshalBinary(data)
}
+// Clone implements [hash.Cloner].
+func (d *SHA3) Clone() (hash.Cloner, error) {
+ r := *d
+ return &r, nil
+}
+
// SHAKE is an instance of a SHAKE extendable output function.
type SHAKE struct {
s sha3.SHAKE
"cSHAKE256": {NewCSHAKE256, "CSHAKE256", "CustomString"},
}
-// decodeHex converts a hex-encoded string into a raw byte string.
-func decodeHex(s string) []byte {
- b, err := hex.DecodeString(s)
- if err != nil {
- panic(err)
- }
- return b
+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
func TestExtraMethods(t *testing.T) {
t.Run("SHA-384", func(t *testing.T) {
cryptotest.TestAllImplementations(t, "sha512", func(t *testing.T) {
- h := New384()
- cryptotest.NoExtraMethods(t, &h, "MarshalBinary", "UnmarshalBinary", "AppendBinary")
+ h := maybeCloner(New384())
+ cryptotest.NoExtraMethods(t, h, "MarshalBinary", "UnmarshalBinary", "AppendBinary")
})
})
t.Run("SHA-512/224", func(t *testing.T) {
cryptotest.TestAllImplementations(t, "sha512", func(t *testing.T) {
- h := New512_224()
- cryptotest.NoExtraMethods(t, &h, "MarshalBinary", "UnmarshalBinary", "AppendBinary")
+ h := maybeCloner(New512_224())
+ cryptotest.NoExtraMethods(t, h, "MarshalBinary", "UnmarshalBinary", "AppendBinary")
})
})
t.Run("SHA-512/256", func(t *testing.T) {
cryptotest.TestAllImplementations(t, "sha512", func(t *testing.T) {
- h := New512_256()
- cryptotest.NoExtraMethods(t, &h, "MarshalBinary", "UnmarshalBinary", "AppendBinary")
+ h := maybeCloner(New512_256())
+ cryptotest.NoExtraMethods(t, h, "MarshalBinary", "UnmarshalBinary", "AppendBinary")
})
})
t.Run("SHA-512", func(t *testing.T) {
cryptotest.TestAllImplementations(t, "sha512", func(t *testing.T) {
- h := New()
- cryptotest.NoExtraMethods(t, &h, "MarshalBinary", "UnmarshalBinary", "AppendBinary")
+ h := maybeCloner(New())
+ cryptotest.NoExtraMethods(t, h, "MarshalBinary", "UnmarshalBinary", "AppendBinary")
})
})
}
+func maybeCloner(h hash.Hash) any {
+ if c, ok := h.(hash.Cloner); ok {
+ return &c
+ }
+ return &h
+}
+
var bench = New()
var buf = make([]byte, 8192)
return nil
}
+func (d *digest) Clone() (hash.Cloner, error) {
+ r := *d
+ return &r, nil
+}
+
// Add p to the running checksum d.
func update(d digest, p []byte) digest {
s1, s2 := uint32(d&0xffff), uint32(d>>16)
return nil
}
+func (d *digest) Clone() (hash.Cloner, error) {
+ r := *d
+ return &r, nil
+}
+
func update(crc uint32, tab *Table, p []byte, checkInitIEEE bool) uint32 {
switch {
case haveCastagnoli.Load() && tab == castagnoliTable:
return nil
}
+func (d *digest) Clone() (hash.Cloner, error) {
+ r := *d
+ return &r, nil
+}
+
func update(crc uint64, tab *Table, p []byte) uint64 {
buildSlicing8TablesOnce()
crc = ^crc
s[1] = byteorder.BEUint64(b[12:])
return nil
}
+
+func (d *sum32) Clone() (hash.Cloner, error) {
+ r := *d
+ return &r, nil
+}
+
+func (d *sum32a) Clone() (hash.Cloner, error) {
+ r := *d
+ return &r, nil
+}
+
+func (d *sum64) Clone() (hash.Cloner, error) {
+ r := *d
+ return &r, nil
+}
+
+func (d *sum64a) Clone() (hash.Cloner, error) {
+ r := *d
+ return &r, nil
+}
+
+func (d *sum128) Clone() (hash.Cloner, error) {
+ r := *d
+ return &r, nil
+}
+
+func (d *sum128a) Clone() (hash.Cloner, error) {
+ r := *d
+ return &r, nil
+}
Sum64() uint64
}
+// A Cloner is a hash function whose state can be cloned.
+//
+// All [Hash] implementations in the standard library implement this interface,
+// unless GOFIPS140=v1.0.0 is set.
+//
+// If a hash can only determine at runtime if it can be cloned,
+// (e.g., if it wraps another hash), it may return [errors.ErrUnsupported].
+type Cloner interface {
+ Hash
+ Clone() (Cloner, error)
+}
+
// XOF (extendable output function) is a hash function with arbitrary or unlimited output length.
type XOF interface {
// Write absorbs more data into the XOF's state. It panics if called
package maphash
import (
+ "hash"
"internal/byteorder"
"math"
)
//
// The zero Hash is a valid Hash ready to use.
// A zero Hash chooses a random seed for itself during
-// the first call to a Reset, Write, Seed, or Sum64 method.
+// the first call to a Reset, Write, Seed, Clone, or Sum64 method.
// For control over the seed, use SetSeed.
//
// The computed hash values depend only on the initial seed and
// BlockSize returns h's block size.
func (h *Hash) BlockSize() int { return len(h.buf) }
+// Clone implements [hash.Cloner].
+func (h *Hash) Clone() (hash.Cloner, error) {
+ h.initSeed()
+ r := *h
+ return &r, nil
+}
+
// Comparable returns the hash of comparable value v with the given seed
// such that Comparable(s, v1) == Comparable(s, v2) if v1 == v2.
// If v != v, then the resulting hash is randomly distributed.
// 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) {
+ TestHashWithoutClone(t, mh)
+ // Test whether the results after cloning are consistent.
+ t.Run("Clone", func(t *testing.T) {
+ h, ok := mh().(hash.Cloner)
+ if !ok {
+ t.Fatalf("%T does not implement hash.Cloner", mh)
+ }
+ h3, err := h.Clone()
+ if err != nil {
+ t.Fatalf("Clone failed: %v", err)
+ }
+ prefix := []byte("tmp")
+ writeToHash(t, h, prefix)
+ h2, err := h.Clone()
+ if err != nil {
+ t.Fatalf("Clone failed: %v", err)
+ }
+ prefixSum := h.Sum(nil)
+ if !bytes.Equal(prefixSum, h2.Sum(nil)) {
+ t.Fatalf("%T Clone results are inconsistent", h)
+ }
+ suffix := []byte("tmp2")
+ writeToHash(t, h, suffix)
+ writeToHash(t, h3, append(prefix, suffix...))
+ compositeSum := h3.Sum(nil)
+ if !bytes.Equal(h.Sum(nil), compositeSum) {
+ t.Fatalf("%T Clone results are inconsistent", h)
+ }
+ if !bytes.Equal(h2.Sum(nil), prefixSum) {
+ t.Fatalf("%T Clone results are inconsistent", h)
+ }
+ writeToHash(t, h2, suffix)
+ if !bytes.Equal(h.Sum(nil), compositeSum) {
+ t.Fatalf("%T Clone results are inconsistent", h)
+ }
+ if !bytes.Equal(h2.Sum(nil), compositeSum) {
+ t.Fatalf("%T Clone results are inconsistent", h)
+ }
+ })
+}
+
+func TestHashWithoutClone(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()