From 1c8dffaa282ba9ebfe597e38f9849bc70f40efe5 Mon Sep 17 00:00:00 2001 From: Zaki Manian Date: Sun, 13 May 2018 11:06:20 -0400 Subject: [PATCH 1/4] Initial implementation of xchacha20poly1035 aead --- xchacha20poly1035/xchachapoly.go | 237 +++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 xchacha20poly1035/xchachapoly.go diff --git a/xchacha20poly1035/xchachapoly.go b/xchacha20poly1035/xchachapoly.go new file mode 100644 index 000000000..b9d823414 --- /dev/null +++ b/xchacha20poly1035/xchachapoly.go @@ -0,0 +1,237 @@ +package xchacha20poly1305 + +import ( + "crypto/cipher" + "encoding/binary" + "errors" + + "golang.org/x/crypto/chacha20poly1305" +) + +var sigma = [4]uint32{0x61707865, 0x3320646e, 0x79622d32, 0x6b206574} + +type xchacha20poly1305 struct { + key [KeySize]byte +} + +const ( + // KeySize is the size of the key used by this AEAD, in bytes. + KeySize = 32 + // NonceSize is the size of the nonce used with this AEAD, in bytes. + NonceSize = 24 +) + +//New xChaChapoly1305 AEAD with 24 byte nonces +func New(key []byte) (cipher.AEAD, error) { + if len(key) != KeySize { + return nil, errors.New("chacha20poly1305: bad key length") + } + ret := new(xchacha20poly1305) + copy(ret.key[:], key) + return ret, nil + +} +func (c *xchacha20poly1305) NonceSize() int { + return NonceSize +} + +func (c *xchacha20poly1305) Overhead() int { + return 16 +} + +func (c *xchacha20poly1305) Seal(dst, nonce, plaintext, additionalData []byte) []byte { + if len(nonce) != NonceSize { + panic("chacha20poly1305: bad nonce length passed to Seal") + } + + if uint64(len(plaintext)) > (1<<38)-64 { + panic("chacha20poly1305: plaintext too large") + } + + var subKey [KeySize]byte + var hNonce [16]byte + var subNonce [chacha20poly1305.NonceSize]byte + copy(hNonce[:], nonce[:16]) + + HChaCha20(&subKey, &hNonce, &c.key) + + chacha20poly1305, _ := chacha20poly1305.New(subKey[:]) + + copy(subNonce[4:], nonce[16:]) + + return chacha20poly1305.Seal(dst, subNonce[:], plaintext, additionalData) +} + +func (c *xchacha20poly1305) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { + if len(nonce) != NonceSize { + panic("chacha20poly1305: bad nonce length passed to Open") + } + if uint64(len(ciphertext)) > (1<<38)-48 { + panic("chacha20poly1305: ciphertext too large") + } + var subKey [KeySize]byte + var hNonce [16]byte + var subNonce [chacha20poly1305.NonceSize]byte + copy(hNonce[:], nonce[:16]) + + HChaCha20(&subKey, &hNonce, &c.key) + + chacha20poly1305, _ := chacha20poly1305.New(subKey[:]) + + copy(subNonce[4:], nonce[16:]) + + return chacha20poly1305.Open(dst, subNonce[:], ciphertext, additionalData) +} + +// The MIT License (MIT) + +// Copyright (c) 2016 Andreas Auernhammer + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// HChaCha20 generates 32 pseudo-random bytes from a 128 bit nonce and a 256 bit secret key. +// It can be used as a key-derivation-function (KDF). +func HChaCha20(out *[32]byte, nonce *[16]byte, key *[32]byte) { hChaCha20Generic(out, nonce, key) } + +func hChaCha20Generic(out *[32]byte, nonce *[16]byte, key *[32]byte) { + v00 := sigma[0] + v01 := sigma[1] + v02 := sigma[2] + v03 := sigma[3] + v04 := binary.LittleEndian.Uint32(key[0:]) + v05 := binary.LittleEndian.Uint32(key[4:]) + v06 := binary.LittleEndian.Uint32(key[8:]) + v07 := binary.LittleEndian.Uint32(key[12:]) + v08 := binary.LittleEndian.Uint32(key[16:]) + v09 := binary.LittleEndian.Uint32(key[20:]) + v10 := binary.LittleEndian.Uint32(key[24:]) + v11 := binary.LittleEndian.Uint32(key[28:]) + v12 := binary.LittleEndian.Uint32(nonce[0:]) + v13 := binary.LittleEndian.Uint32(nonce[4:]) + v14 := binary.LittleEndian.Uint32(nonce[8:]) + v15 := binary.LittleEndian.Uint32(nonce[12:]) + + for i := 0; i < 20; i += 2 { + v00 += v04 + v12 ^= v00 + v12 = (v12 << 16) | (v12 >> 16) + v08 += v12 + v04 ^= v08 + v04 = (v04 << 12) | (v04 >> 20) + v00 += v04 + v12 ^= v00 + v12 = (v12 << 8) | (v12 >> 24) + v08 += v12 + v04 ^= v08 + v04 = (v04 << 7) | (v04 >> 25) + v01 += v05 + v13 ^= v01 + v13 = (v13 << 16) | (v13 >> 16) + v09 += v13 + v05 ^= v09 + v05 = (v05 << 12) | (v05 >> 20) + v01 += v05 + v13 ^= v01 + v13 = (v13 << 8) | (v13 >> 24) + v09 += v13 + v05 ^= v09 + v05 = (v05 << 7) | (v05 >> 25) + v02 += v06 + v14 ^= v02 + v14 = (v14 << 16) | (v14 >> 16) + v10 += v14 + v06 ^= v10 + v06 = (v06 << 12) | (v06 >> 20) + v02 += v06 + v14 ^= v02 + v14 = (v14 << 8) | (v14 >> 24) + v10 += v14 + v06 ^= v10 + v06 = (v06 << 7) | (v06 >> 25) + v03 += v07 + v15 ^= v03 + v15 = (v15 << 16) | (v15 >> 16) + v11 += v15 + v07 ^= v11 + v07 = (v07 << 12) | (v07 >> 20) + v03 += v07 + v15 ^= v03 + v15 = (v15 << 8) | (v15 >> 24) + v11 += v15 + v07 ^= v11 + v07 = (v07 << 7) | (v07 >> 25) + v00 += v05 + v15 ^= v00 + v15 = (v15 << 16) | (v15 >> 16) + v10 += v15 + v05 ^= v10 + v05 = (v05 << 12) | (v05 >> 20) + v00 += v05 + v15 ^= v00 + v15 = (v15 << 8) | (v15 >> 24) + v10 += v15 + v05 ^= v10 + v05 = (v05 << 7) | (v05 >> 25) + v01 += v06 + v12 ^= v01 + v12 = (v12 << 16) | (v12 >> 16) + v11 += v12 + v06 ^= v11 + v06 = (v06 << 12) | (v06 >> 20) + v01 += v06 + v12 ^= v01 + v12 = (v12 << 8) | (v12 >> 24) + v11 += v12 + v06 ^= v11 + v06 = (v06 << 7) | (v06 >> 25) + v02 += v07 + v13 ^= v02 + v13 = (v13 << 16) | (v13 >> 16) + v08 += v13 + v07 ^= v08 + v07 = (v07 << 12) | (v07 >> 20) + v02 += v07 + v13 ^= v02 + v13 = (v13 << 8) | (v13 >> 24) + v08 += v13 + v07 ^= v08 + v07 = (v07 << 7) | (v07 >> 25) + v03 += v04 + v14 ^= v03 + v14 = (v14 << 16) | (v14 >> 16) + v09 += v14 + v04 ^= v09 + v04 = (v04 << 12) | (v04 >> 20) + v03 += v04 + v14 ^= v03 + v14 = (v14 << 8) | (v14 >> 24) + v09 += v14 + v04 ^= v09 + v04 = (v04 << 7) | (v04 >> 25) + } + + binary.LittleEndian.PutUint32(out[0:], v00) + binary.LittleEndian.PutUint32(out[4:], v01) + binary.LittleEndian.PutUint32(out[8:], v02) + binary.LittleEndian.PutUint32(out[12:], v03) + binary.LittleEndian.PutUint32(out[16:], v12) + binary.LittleEndian.PutUint32(out[20:], v13) + binary.LittleEndian.PutUint32(out[24:], v14) + binary.LittleEndian.PutUint32(out[28:], v15) +} From 8423f6ef5a7fd29bd76636cc16702911d246b4b1 Mon Sep 17 00:00:00 2001 From: Zaki Manian Date: Mon, 21 May 2018 20:45:36 -0700 Subject: [PATCH 2/4] Add the libsodium test vector --- xchacha20poly1035/xchachapoly.go | 8 +-- xchacha20poly1035/xchachapoly_test.go | 91 +++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 xchacha20poly1035/xchachapoly_test.go diff --git a/xchacha20poly1035/xchachapoly.go b/xchacha20poly1035/xchachapoly.go index b9d823414..96413b8c1 100644 --- a/xchacha20poly1035/xchachapoly.go +++ b/xchacha20poly1035/xchachapoly.go @@ -41,11 +41,11 @@ func (c *xchacha20poly1305) Overhead() int { func (c *xchacha20poly1305) Seal(dst, nonce, plaintext, additionalData []byte) []byte { if len(nonce) != NonceSize { - panic("chacha20poly1305: bad nonce length passed to Seal") + panic("xchacha20poly1305: bad nonce length passed to Seal") } if uint64(len(plaintext)) > (1<<38)-64 { - panic("chacha20poly1305: plaintext too large") + panic("xchacha20poly1305: plaintext too large") } var subKey [KeySize]byte @@ -64,10 +64,10 @@ func (c *xchacha20poly1305) Seal(dst, nonce, plaintext, additionalData []byte) [ func (c *xchacha20poly1305) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { if len(nonce) != NonceSize { - panic("chacha20poly1305: bad nonce length passed to Open") + panic("xchacha20poly1305: bad nonce length passed to Open") } if uint64(len(ciphertext)) > (1<<38)-48 { - panic("chacha20poly1305: ciphertext too large") + panic("xchacha20poly1305: ciphertext too large") } var subKey [KeySize]byte var hNonce [16]byte diff --git a/xchacha20poly1035/xchachapoly_test.go b/xchacha20poly1035/xchachapoly_test.go new file mode 100644 index 000000000..5e522e0b1 --- /dev/null +++ b/xchacha20poly1035/xchachapoly_test.go @@ -0,0 +1,91 @@ +package xchacha20poly1305 + +import ( + "bytes" + "encoding/hex" + "testing" +) + +func toHex(bits []byte) string { + return hex.EncodeToString(bits) +} + +func fromHex(bits string) []byte { + b, err := hex.DecodeString(bits) + if err != nil { + panic(err) + } + return b +} + +func TestHChaCha20(t *testing.T) { + for i, v := range hChaCha20Vectors { + var key [32]byte + var nonce [16]byte + copy(key[:], v.key) + copy(nonce[:], v.nonce) + + HChaCha20(&key, &nonce, &key) + if !bytes.Equal(key[:], v.keystream) { + t.Errorf("Test %d: keystream mismatch:\n \t got: %s\n \t want: %s", i, toHex(key[:]), toHex(v.keystream)) + } + } +} + +var hChaCha20Vectors = []struct { + key, nonce, keystream []byte +}{ + { + fromHex("0000000000000000000000000000000000000000000000000000000000000000"), + fromHex("000000000000000000000000000000000000000000000000"), + fromHex("1140704c328d1d5d0e30086cdf209dbd6a43b8f41518a11cc387b669b2ee6586"), + }, + { + fromHex("8000000000000000000000000000000000000000000000000000000000000000"), + fromHex("000000000000000000000000000000000000000000000000"), + fromHex("7d266a7fd808cae4c02a0a70dcbfbcc250dae65ce3eae7fc210f54cc8f77df86"), + }, + { + fromHex("0000000000000000000000000000000000000000000000000000000000000001"), + fromHex("000000000000000000000000000000000000000000000002"), + fromHex("e0c77ff931bb9163a5460c02ac281c2b53d792b1c43fea817e9ad275ae546963"), + }, + { + fromHex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"), + fromHex("000102030405060708090a0b0c0d0e0f1011121314151617"), + fromHex("51e3ff45a895675c4b33b46c64f4a9ace110d34df6a2ceab486372bacbd3eff6"), + }, +} + +func TestVectors(t *testing.T) { + for i, v := range vectors { + if len(v.plaintext) == 0 { + v.plaintext = make([]byte, len(v.ciphertext)) + } + + var nonce [24]byte + copy(nonce[:], v.nonce) + + aead, err := New(v.key) + if err != nil { + t.Error(err) + } + + dst := aead.Seal(nil, nonce[:], v.plaintext, v.ad) + if !bytes.Equal(dst, v.ciphertext) { + t.Errorf("Test %d: ciphertext mismatch:\n \t got: %s\n \t want: %s", i, toHex(dst), toHex(v.ciphertext)) + } + } +} + +var vectors = []struct { + key, nonce, ad, plaintext, ciphertext []byte +}{ + { + []byte{0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f}, + []byte{0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b}, + []byte{0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7}, + []byte("Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."), + []byte{0x45, 0x3c, 0x06, 0x93, 0xa7, 0x40, 0x7f, 0x04, 0xff, 0x4c, 0x56, 0xae, 0xdb, 0x17, 0xa3, 0xc0, 0xa1, 0xaf, 0xff, 0x01, 0x17, 0x49, 0x30, 0xfc, 0x22, 0x28, 0x7c, 0x33, 0xdb, 0xcf, 0x0a, 0xc8, 0xb8, 0x9a, 0xd9, 0x29, 0x53, 0x0a, 0x1b, 0xb3, 0xab, 0x5e, 0x69, 0xf2, 0x4c, 0x7f, 0x60, 0x70, 0xc8, 0xf8, 0x40, 0xc9, 0xab, 0xb4, 0xf6, 0x9f, 0xbf, 0xc8, 0xa7, 0xff, 0x51, 0x26, 0xfa, 0xee, 0xbb, 0xb5, 0x58, 0x05, 0xee, 0x9c, 0x1c, 0xf2, 0xce, 0x5a, 0x57, 0x26, 0x32, 0x87, 0xae, 0xc5, 0x78, 0x0f, 0x04, 0xec, 0x32, 0x4c, 0x35, 0x14, 0x12, 0x2c, 0xfc, 0x32, 0x31, 0xfc, 0x1a, 0x8b, 0x71, 0x8a, 0x62, 0x86, 0x37, 0x30, 0xa2, 0x70, 0x2b, 0xb7, 0x63, 0x66, 0x11, 0x6b, 0xed, 0x09, 0xe0, 0xfd, 0x5c, 0x6d, 0x84, 0xb6, 0xb0, 0xc1, 0xab, 0xaf, 0x24, 0x9d, 0x5d, 0xd0, 0xf7, 0xf5, 0xa7, 0xea}, + }, +} From 384f1e399afb23a30f01e629145c82afb3498664 Mon Sep 17 00:00:00 2001 From: Zaki Manian Date: Wed, 23 May 2018 23:54:26 +0200 Subject: [PATCH 3/4] Fixed the directory name and added a test vector --- {xchacha20poly1035 => xchacha20poly1305}/xchachapoly.go | 0 {xchacha20poly1035 => xchacha20poly1305}/xchachapoly_test.go | 5 +++++ 2 files changed, 5 insertions(+) rename {xchacha20poly1035 => xchacha20poly1305}/xchachapoly.go (100%) rename {xchacha20poly1035 => xchacha20poly1305}/xchachapoly_test.go (94%) diff --git a/xchacha20poly1035/xchachapoly.go b/xchacha20poly1305/xchachapoly.go similarity index 100% rename from xchacha20poly1035/xchachapoly.go rename to xchacha20poly1305/xchachapoly.go diff --git a/xchacha20poly1035/xchachapoly_test.go b/xchacha20poly1305/xchachapoly_test.go similarity index 94% rename from xchacha20poly1035/xchachapoly_test.go rename to xchacha20poly1305/xchachapoly_test.go index 5e522e0b1..460b90ab4 100644 --- a/xchacha20poly1035/xchachapoly_test.go +++ b/xchacha20poly1305/xchachapoly_test.go @@ -55,6 +55,11 @@ var hChaCha20Vectors = []struct { fromHex("000102030405060708090a0b0c0d0e0f1011121314151617"), fromHex("51e3ff45a895675c4b33b46c64f4a9ace110d34df6a2ceab486372bacbd3eff6"), }, + { + fromHex("24f11cce8a1b3d61e441561a696c1c1b7e173d084fd4812425435a8896a013dc"), + fromHex("d9660c5900ae19ddad28d6e06e45fe5e"), + fromHex("5966b3eec3bff1189f831f06afe4d4e3be97fa9235ec8c20d08acfbbb4e851e3"), + }, } func TestVectors(t *testing.T) { From 05a5294e528161ca7192295a9397d0d8e8adea63 Mon Sep 17 00:00:00 2001 From: Zaki Manian Date: Thu, 24 May 2018 00:12:54 +0200 Subject: [PATCH 4/4] Remove panic and check the round trip --- xchacha20poly1305/xchachapoly.go | 5 +++-- xchacha20poly1305/xchachapoly_test.go | 7 +++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/xchacha20poly1305/xchachapoly.go b/xchacha20poly1305/xchachapoly.go index 96413b8c1..9e0778579 100644 --- a/xchacha20poly1305/xchachapoly.go +++ b/xchacha20poly1305/xchachapoly.go @@ -4,6 +4,7 @@ import ( "crypto/cipher" "encoding/binary" "errors" + "fmt" "golang.org/x/crypto/chacha20poly1305" ) @@ -64,10 +65,10 @@ func (c *xchacha20poly1305) Seal(dst, nonce, plaintext, additionalData []byte) [ func (c *xchacha20poly1305) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { if len(nonce) != NonceSize { - panic("xchacha20poly1305: bad nonce length passed to Open") + return nil, fmt.Errorf("xchacha20poly1305: bad nonce length passed to Open") } if uint64(len(ciphertext)) > (1<<38)-48 { - panic("xchacha20poly1305: ciphertext too large") + return nil, fmt.Errorf("xchacha20poly1305: ciphertext too large") } var subKey [KeySize]byte var hNonce [16]byte diff --git a/xchacha20poly1305/xchachapoly_test.go b/xchacha20poly1305/xchachapoly_test.go index 460b90ab4..3001217f4 100644 --- a/xchacha20poly1305/xchachapoly_test.go +++ b/xchacha20poly1305/xchachapoly_test.go @@ -80,6 +80,13 @@ func TestVectors(t *testing.T) { if !bytes.Equal(dst, v.ciphertext) { t.Errorf("Test %d: ciphertext mismatch:\n \t got: %s\n \t want: %s", i, toHex(dst), toHex(v.ciphertext)) } + open, err := aead.Open(nil, nonce[:], dst, v.ad) + if err != nil { + t.Error(err) + } + if !bytes.Equal(open, v.plaintext) { + t.Errorf("Test %d: plaintext mismatch:\n \t got: %s\n \t want: %s", i, string(open), string(v.plaintext)) + } } }