Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement PQC schemes #135

Closed
wants to merge 12 commits into from
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/ProtonMail/go-crypto
go 1.13

require (
github.com/cloudflare/circl v1.1.0 // indirect
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
github.com/cloudflare/circl v1.2.0
github.com/kasperdi/SPHINCSPLUS-golang v0.0.0-20221227220735-de985e5a663c
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898
)
25 changes: 14 additions & 11 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY=
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
github.com/bwesterb/go-ristretto v1.2.1/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cloudflare/circl v1.2.0 h1:NheeISPSUcYftKlfrLuOo4T62FkmD4t4jviLfFFYaec=
github.com/cloudflare/circl v1.2.0/go.mod h1:Ch2UgYr6ti2KTtlejELlROl0YIYj7SLjAC8M+INXlMk=
github.com/kasperdi/SPHINCSPLUS-golang v0.0.0-20221227220735-de985e5a663c h1:JCxCKz59IXghzSSUstoaWa7h7lZdmd0LFMiMfF56ECk=
github.com/kasperdi/SPHINCSPLUS-golang v0.0.0-20221227220735-de985e5a663c/go.mod h1:+SeUKO8dPlXRdYr4SK+UIs8SLz0Dl3ZceKdXGaSFsFY=
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 h1:SLP7Q4Di66FONjDJbCYrCRrh97focO6sLogHO7/g8F0=
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac h1:oN6lz7iLW/YC7un8pq+9bOLyXrprv2+DKfkJY+2LJJw=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
144 changes: 144 additions & 0 deletions internal/kmac/kmac.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// 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"
"golang.org/x/crypto/sha3"
"hash"
)

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:]
}
130 changes: 130 additions & 0 deletions internal/kmac/kmac_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/// 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"
"github.com/ProtonMail/go-crypto/internal/kmac"
"hash"
"testing"
)
// 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
}
Loading