// Package xchacha20poly1305 creates an AEAD using hchacha, chacha, and poly1305 // This allows for randomized nonces to be used in conjunction with chacha. package xchacha20poly1305 import ( "crypto/cipher" "encoding/binary" "errors" "fmt" "golang.org/x/crypto/chacha20poly1305" ) // Implements crypto.AEAD 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 // TagSize is the size added from poly1305 TagSize = 16 // MaxPlaintextSize is the max size that can be passed into a single call of Seal MaxPlaintextSize = (1 << 38) - 64 // MaxCiphertextSize is the max size that can be passed into a single call of Open, // this differs from plaintext size due to the tag MaxCiphertextSize = (1 << 38) - 48 // sigma are constants used in xchacha. // Unrolled from a slice so that they can be inlined, as slices can't be constants. sigma0 = uint32(0x61707865) sigma1 = uint32(0x3320646e) sigma2 = uint32(0x79622d32) sigma3 = uint32(0x6b206574) ) // New returns a new xchachapoly1305 AEAD func New(key []byte) (cipher.AEAD, error) { if len(key) != KeySize { return nil, errors.New("xchacha20poly1305: 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 TagSize } func (c *xchacha20poly1305) Seal(dst, nonce, plaintext, additionalData []byte) []byte { if len(nonce) != NonceSize { panic("xchacha20poly1305: bad nonce length passed to Seal") } if uint64(len(plaintext)) > MaxPlaintextSize { panic("xchacha20poly1305: 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) // This can't error because we always provide a correctly sized 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 { return nil, fmt.Errorf("xchacha20poly1305: bad nonce length passed to Open") } if uint64(len(ciphertext)) > MaxCiphertextSize { return nil, fmt.Errorf("xchacha20poly1305: 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) // This can't error because we always provide a correctly sized key chacha20poly1305, _ := chacha20poly1305.New(subKey[:]) copy(subNonce[4:], nonce[16:]) return chacha20poly1305.Open(dst, subNonce[:], ciphertext, additionalData) } // HChaCha exported from // https://github.com/aead/chacha20/blob/8b13a72661dae6e9e5dea04f344f0dc95ea29547/chacha/chacha_generic.go#L194 // TODO: Add support for the different assembly instructions used there. // 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 := sigma0 v01 := sigma1 v02 := sigma2 v03 := sigma3 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) }