diff options
| author | Rose Hogenson <rosehogenson@posteo.net> | 2025-09-27 10:14:25 -0700 |
|---|---|---|
| committer | Rose Hogenson <rosehogenson@posteo.net> | 2025-09-27 10:14:25 -0700 |
| commit | c01a9c08ffecc89f3f318586a2e7214a2981a35e (patch) | |
| tree | d9fa95a3eb363c115ce908c48dfd5d5ab360a92b | |
| download | kmac-c01a9c08ffecc89f3f318586a2e7214a2981a35e.tar.zst | |
Initial commit
| -rw-r--r-- | go.mod | 3 | ||||
| -rw-r--r-- | kmac.go | 90 | ||||
| -rw-r--r-- | kmac_test.go | 9 |
3 files changed, 102 insertions, 0 deletions
@@ -0,0 +1,3 @@ +module github.com/rhogenson/kmac + +go 1.25.0 @@ -0,0 +1,90 @@ +// Package kmac implements the KMAC128 and KMAC256 keyed hash functions based +// on sha3. +// +// The KECCAK Message Authentication Code (KMAC) algorithm is a PRF and keyed +// hash function based on KECCAK. It provides variable-length output, and unlike +// SHAKE and cSHAKE, altering the requested output length generates a new, +// unrelated output. KMAC has two variants, KMAC128 and KMAC256, built from +// cSHAKE128 and cSHAKE256, respectively. The two variants differ somewhat in +// their technical security properties. Nonetheless, for most applications, both +// variants can support any security strength up to 256 bits of security, +// provided that the length of the input key is not less than the required +// security strength. +// +// It's possible to use KMAC as a key derivation function. For the input data, +// use a string containing information related to the derived keying material. +// It may include the identities of the parties who are deriving and/or using +// the derived keying material and, optionally, a nonce known by the parties who +// derive the keys. Use the string "KDF" for optionalCustomizationString. +package kmac + +import ( + "crypto/sha3" + "encoding/binary" + "math/bits" +) + +// from https://cs.opensource.google/go/go/+/master:src/crypto/internal/fips140/sha3/shake.go;l=36;drc=4a3cef2036097d323b6cc0bbe90fc4d8c7588660 +func leftEncode(x uint64) []byte { + // Let n be the smallest positive integer for which 2^(8n) > x. + n := (bits.Len64(x) + 7) / 8 + if n == 0 { + n = 1 + } + // Return n || x with n as a byte and x an n bytes in big-endian order. + b := make([]byte, 9) + binary.BigEndian.PutUint64(b[1:], x) + b = b[9-n-1:] + b[0] = byte(n) + return b +} + +func rightEncode(x uint64) []byte { + // Let n be the smallest positive integer for which 2^(8n) > x. + n := (bits.Len64(x) + 7) / 8 + if n == 0 { + n = 1 + } + // Return x || n with n as a byte and x an n bytes in big-endian order. + b := make([]byte, 9) + binary.BigEndian.PutUint64(b, x) + b = b[9-n-1:] + b[n] = byte(n) + return b +} + +func encodeString(s []byte) []byte { + return append(leftEncode(uint64(len(s))), s...) +} + +func bytepad(data []byte, rate int) []byte { + out := make([]byte, 0, 9+len(data)+rate-1) + out = append(out, leftEncode(uint64(rate))...) + out = append(out, data...) + if padlen := rate - len(out)%rate; padlen < rate { + out = append(out, make([]byte, padlen)...) + } + return out +} + +// KMAC128 computes the KMAC128 message authentication code for data using key +// as the secret key. KMAC provides variable-length output and fills out with +// the resulting MAC. It's recommended to use at least 8 bytes of output. +func KMAC128(out, key, data []byte, optionalCustomizationString string) { + shake := sha3.NewCSHAKE128([]byte("KMAC"), []byte(optionalCustomizationString)) + shake.Write(bytepad(encodeString(key), 168)) + shake.Write(data) + shake.Write(rightEncode(uint64(len(out)))) + shake.Read(out) +} + +// KMAC256 computes the KMAC256 message authentication code for data using key +// as the secret key. KMAC provides variable-length output and fills out with +// the resulting MAC. It's recommended to use at least 8 bytes of output. +func KMAC256(out, key, data []byte, optionalCustomizationString string) { + shake := sha3.NewCSHAKE256([]byte("KMAC"), []byte(optionalCustomizationString)) + shake.Write(bytepad(encodeString(key), 136)) + shake.Write(data) + shake.Write(rightEncode(uint64(len(out)))) + shake.Read(out) +} diff --git a/kmac_test.go b/kmac_test.go new file mode 100644 index 0000000..0bddfc3 --- /dev/null +++ b/kmac_test.go @@ -0,0 +1,9 @@ +package kmac + +func Example_kdf() { + inputKey := []byte(`A random or pseudorandom key, perhaps shared through ECDH or derived from a user password`) + context := []byte(`A non-secret value; consider using a random salt`) + const desiredKeyLen = 4096 + derivedKeyMaterial := make([]byte, desiredKeyLen) + KMAC256(derivedKeyMaterial, inputKey, context, "KDF") +} |
