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

Experimental: Symmetric Keys and Forwarding #141

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
23 changes: 2 additions & 21 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Go

on:
push:
branches: [ main ]
branches: [ main, Proton ]
pull_request:
branches: [ main ]
branches: [ main, Proton ]

jobs:

Expand All @@ -26,22 +26,3 @@ jobs:

- name: Randomized test suite 2
run: go test -v ./... -run RandomizeSlow -count=32

test-old:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Set up Go 1.17
uses: actions/setup-go@v3
with:
go-version: 1.17

- name: Short test
run: go test -short -v ./...

- name: Randomized test suite 1
run: go test -v ./... -run RandomizeFast -count=512

- name: Randomized test suite 2
run: go test -v ./... -run RandomizeSlow -count=32
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
module github.com/ProtonMail/go-crypto

go 1.17
go 1.22.0

require (
github.com/cloudflare/circl v1.3.7
golang.org/x/crypto v0.17.0
github.com/cloudflare/circl v1.5.0
golang.org/x/crypto v0.25.0
)

require golang.org/x/sys v0.16.0 // indirect
require golang.org/x/sys v0.22.0 // indirect
50 changes: 6 additions & 44 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,44 +1,6 @@
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys=
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
147 changes: 147 additions & 0 deletions internal/kmac/kmac.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// 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"
"fmt"
"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) (h hash.Hash, err error) {
c := sha3.NewCShake128([]byte("KMAC"), customizationString)
h = newKMAC(key, tagSize, c, rate128)
if len(key) < 16 {
return h, fmt.Errorf("kmac: key is too short with %d bytes: should be at least %d", len(key), 16)
}
return h, nil
}

// 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) (h hash.Hash, err error) {
c := sha3.NewCShake256([]byte("KMAC"), customizationString)
h = newKMAC(key, tagSize, c, rate256)
if len(key) < 32 {
return h, fmt.Errorf("kmac: key is too short with %d bytes: should be at least %d", len(key), 32)
}
return h, nil
}

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:]
}
142 changes: 142 additions & 0 deletions internal/kmac/kmac_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/// 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, err = kmac.NewKMAC128(key, len(tag), []byte(test.customization))
} else {
mac, err = kmac.NewKMAC256(key, len(tag), []byte(test.customization))
}
if err != nil {
t.Fatal(err)
}
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, err := kmac.NewKMAC256(key, len(tag), []byte("Partition1"))
if err != nil {
panic(err)
}
k.Write(msg)
k.Sum(tag[:0])
fmt.Println(hex.EncodeToString(tag))
// Example 2: Different customization string produces different digest
k, err = kmac.NewKMAC256(key, 16, []byte("Partition2"))
if err != nil {
panic(err)
}
k.Write(msg)
k.Sum(tag[:0])
fmt.Println(hex.EncodeToString(tag))
// Output:
//3814d78758add078334b8ab9e5c4f942
//3762371e99e1e01ab17742b95c0360da
}
Loading
Loading