-
Notifications
You must be signed in to change notification settings - Fork 101
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
278 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |