Skip to content

Commit

Permalink
chore: Add kmac back
Browse files Browse the repository at this point in the history
  • Loading branch information
lubux committed Oct 11, 2024
1 parent d8b79f7 commit 384a0e0
Show file tree
Hide file tree
Showing 2 changed files with 278 additions and 0 deletions.
145 changes: 145 additions & 0 deletions internal/kmac/kmac.go
Original file line number Diff line number Diff line change
@@ -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:]
}
133 changes: 133 additions & 0 deletions internal/kmac/kmac_test.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 384a0e0

Please sign in to comment.