From 384a0e0ac488f46c0a293ebf9d17952f16dd8747 Mon Sep 17 00:00:00 2001 From: Lukas Burkhalter Date: Fri, 11 Oct 2024 11:48:17 +0200 Subject: [PATCH] chore: Add kmac back --- internal/kmac/kmac.go | 145 +++++++++++++++++++++++++++++++++++++ internal/kmac/kmac_test.go | 133 ++++++++++++++++++++++++++++++++++ 2 files changed, 278 insertions(+) create mode 100644 internal/kmac/kmac.go create mode 100644 internal/kmac/kmac_test.go diff --git a/internal/kmac/kmac.go b/internal/kmac/kmac.go new file mode 100644 index 00000000..982ca409 --- /dev/null +++ b/internal/kmac/kmac.go @@ -0,0 +1,145 @@ +// Copyright 2019 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 kmac provides function for creating KMAC instances. +// KMAC is a Message Authentication Code that based on SHA-3 and +// specified in NIST Special Publication 800-185, "SHA-3 Derived Functions: +// cSHAKE, KMAC, TupleHash and ParallelHash" [1] +// +// [1] https://doi.org/10.6028/NIST.SP.800-185 +package kmac + +import ( + "encoding/binary" + "hash" + + "golang.org/x/crypto/sha3" +) + +const ( + // According to [1]: + // "When used as a MAC, applications of this Recommendation shall + // not select an output length L that is less than 32 bits, and + // shall only select an output length less than 64 bits after a + // careful risk analysis is performed." + // 64 bits was selected for safety. + kmacMinimumTagSize = 8 + rate128 = 168 + rate256 = 136 +) + +// KMAC specific context +type kmac struct { + sha3.ShakeHash // cSHAKE context and Read/Write operations + tagSize int // tag size + // initBlock is the KMAC specific initialization set of bytes. It is initialized + // by newKMAC function and stores the key, encoded by the method specified in 3.3 of [1]. + // It is stored here in order for Reset() to be able to put context into + // initial state. + initBlock []byte + rate int +} + +// NewKMAC128 returns a new KMAC hash providing 128 bits of security using +// the given key, which must have 16 bytes or more, generating the given tagSize +// bytes output and using the given customizationString. +// Note that unlike other hash implementations in the standard library, +// the returned Hash does not implement encoding.BinaryMarshaler +// or encoding.BinaryUnmarshaler. +func NewKMAC128(key []byte, tagSize int, customizationString []byte) hash.Hash { + if len(key) < 16 { + panic("Key must not be smaller than security strength") + } + c := sha3.NewCShake128([]byte("KMAC"), customizationString) + return newKMAC(key, tagSize, c, rate128) +} + +// NewKMAC256 returns a new KMAC hash providing 256 bits of security using +// the given key, which must have 32 bytes or more, generating the given tagSize +// bytes output and using the given customizationString. +// Note that unlike other hash implementations in the standard library, +// the returned Hash does not implement encoding.BinaryMarshaler +// or encoding.BinaryUnmarshaler. + +func NewKMAC256(key []byte, tagSize int, customizationString []byte) hash.Hash { + if len(key) < 32 { + panic("Key must not be smaller than security strength") + } + c := sha3.NewCShake256([]byte("KMAC"), customizationString) + return newKMAC(key, tagSize, c, rate256) +} + +func newKMAC(key []byte, tagSize int, c sha3.ShakeHash, rate int) hash.Hash { + if tagSize < kmacMinimumTagSize { + panic("tagSize is too small") + } + k := &kmac{ShakeHash: c, tagSize: tagSize, rate: rate} + // leftEncode returns max 9 bytes + k.initBlock = make([]byte, 0, 9+len(key)) + k.initBlock = append(k.initBlock, leftEncode(uint64(len(key)*8))...) + k.initBlock = append(k.initBlock, key...) + k.Write(bytepad(k.initBlock, k.BlockSize())) + return k +} + +// Reset resets the hash to initial state. +func (k *kmac) Reset() { + k.ShakeHash.Reset() + k.Write(bytepad(k.initBlock, k.BlockSize())) +} + +// BlockSize returns the hash block size. +func (k *kmac) BlockSize() int { + return k.rate +} + +// Size returns the tag size. +func (k *kmac) Size() int { + return k.tagSize +} + +// Sum appends the current KMAC to b and returns the resulting slice. +// It does not change the underlying hash state. +func (k *kmac) Sum(b []byte) []byte { + dup := k.ShakeHash.Clone() + dup.Write(rightEncode(uint64(k.tagSize * 8))) + hash := make([]byte, k.tagSize) + dup.Read(hash) + return append(b, hash...) +} + +func bytepad(input []byte, w int) []byte { + // leftEncode always returns max 9 bytes + buf := make([]byte, 0, 9+len(input)+w) + buf = append(buf, leftEncode(uint64(w))...) + buf = append(buf, input...) + padlen := w - (len(buf) % w) + return append(buf, make([]byte, padlen)...) +} + +func leftEncode(value uint64) []byte { + var b [9]byte + binary.BigEndian.PutUint64(b[1:], value) + // Trim all but last leading zero bytes + i := byte(1) + for i < 8 && b[i] == 0 { + i++ + } + // Prepend number of encoded bytes + b[i-1] = 9 - i + return b[i-1:] +} + +func rightEncode(value uint64) []byte { + var b [9]byte + binary.BigEndian.PutUint64(b[:8], value) + // Trim all but last leading zero bytes + i := byte(0) + for i < 7 && b[i] == 0 { + i++ + } + // Append number of encoded bytes + b[8] = 8 - i + return b[i:] +} diff --git a/internal/kmac/kmac_test.go b/internal/kmac/kmac_test.go new file mode 100644 index 00000000..0b484425 --- /dev/null +++ b/internal/kmac/kmac_test.go @@ -0,0 +1,133 @@ +/// Copyright 2019 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 kmac_test implements a vector-based test suite for the cSHAKE KMAC implementation +package kmac_test + +import ( + "bytes" + "encoding/hex" + "fmt" + "hash" + "testing" + + "github.com/ProtonMail/go-crypto/internal/kmac" +) + +// Test vectors from +// https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/cSHAKE_samples.pdf +var kmacTests = []struct { + security int + key, data, customization, tag string +}{ + { + 128, + "404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F", + "00010203", + "", + "E5780B0D3EA6F7D3A429C5706AA43A00FADBD7D49628839E3187243F456EE14E", + }, + { + 128, + "404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F", + "00010203", + "My Tagged Application", + "3B1FBA963CD8B0B59E8C1A6D71888B7143651AF8BA0A7070C0979E2811324AA5", + }, + { + 128, + "404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F", + "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7", + "My Tagged Application", + "1F5B4E6CCA02209E0DCB5CA635B89A15E271ECC760071DFD805FAA38F9729230", + }, + { + 256, + "404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F", + "00010203", + "My Tagged Application", + "20C570C31346F703C9AC36C61C03CB64C3970D0CFC787E9B79599D273A68D2F7F69D4CC3DE9D104A351689F27CF6F5951F0103F33F4F24871024D9C27773A8DD", + }, + { + 256, + "404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F", + "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7", + "", + "75358CF39E41494E949707927CEE0AF20A3FF553904C86B08F21CC414BCFD691589D27CF5E15369CBBFF8B9A4C2EB17800855D0235FF635DA82533EC6B759B69", + }, + { + 256, + "404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F", + "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7", + "My Tagged Application", + "B58618F71F92E1D56C1B8C55DDD7CD188B97B4CA4D99831EB2699A837DA2E4D970FBACFDE50033AEA585F1A2708510C32D07880801BD182898FE476876FC8965", + }, +} + +func TestKMAC(t *testing.T) { + for i, test := range kmacTests { + key, err := hex.DecodeString(test.key) + if err != nil { + t.Errorf("error decoding KAT: %s", err) + } + tag, err := hex.DecodeString(test.tag) + if err != nil { + t.Errorf("error decoding KAT: %s", err) + } + var mac hash.Hash + if test.security == 128 { + mac = kmac.NewKMAC128(key, len(tag), []byte(test.customization)) + } else { + mac = kmac.NewKMAC256(key, len(tag), []byte(test.customization)) + } + data, err := hex.DecodeString(test.data) + if err != nil { + t.Errorf("error decoding KAT: %s", err) + } + mac.Write(data) + computedTag := mac.Sum(nil) + if !bytes.Equal(tag, computedTag) { + t.Errorf("#%d: got %x, want %x", i, tag, computedTag) + } + if mac.Size() != len(tag) { + t.Errorf("#%d: Size() = %x, want %x", i, mac.Size(), len(tag)) + } + // Test if it works after Reset. + mac.Reset() + mac.Write(data) + computedTag = mac.Sum(nil) + if !bytes.Equal(tag, computedTag) { + t.Errorf("#%d: got %x, want %x", i, tag, computedTag) + } + // Test if Sum does not change state. + if len(data) > 1 { + mac.Reset() + mac.Write(data[0:1]) + mac.Sum(nil) + mac.Write(data[1:]) + computedTag = mac.Sum(nil) + if !bytes.Equal(tag, computedTag) { + t.Errorf("#%d: got %x, want %x", i, tag, computedTag) + } + } + } +} +func ExampleNewKMAC256() { + key := []byte("this is a secret key; you should generate a strong random key that's at least 32 bytes long") + tag := make([]byte, 16) + msg := []byte("The quick brown fox jumps over the lazy dog") + // Example 1: Simple KMAC + k := kmac.NewKMAC256(key, len(tag), []byte("Partition1")) + k.Write(msg) + k.Sum(tag[:0]) + fmt.Println(hex.EncodeToString(tag)) + // Example 2: Different customization string produces different digest + k = kmac.NewKMAC256(key, 16, []byte("Partition2")) + k.Write(msg) + k.Sum(tag[:0]) + fmt.Println(hex.EncodeToString(tag)) + // Output: + //3814d78758add078334b8ab9e5c4f942 + //3762371e99e1e01ab17742b95c0360da +}