Separated the complex buffering logic from key stream generation more clearly, added plenty of comments and generally refactored the Go implementation for readability. Made the interface with the generic/assembly cores smaller and more consistent, according to golang.org/wiki/TargetSpecific. We will recover the lost performance on unaligned calls by caching 3/4 of the first round across XORKeyStream invocations, which we now have complexity budget for. name old speed new speed delta ChaCha20/64-4 435MB/s ± 2% 429MB/s ± 2% -1.47% (p=0.013 n=10+9) ChaCha20/256-4 496MB/s ± 1% 493MB/s ± 2% ~ (p=0.280 n=10+10) ChaCha20/10x25-4 283MB/s ± 1% 274MB/s ± 2% -3.13% (p=0.000 n=10+10) ChaCha20/4096-4 494MB/s ± 1% 493MB/s ± 5% ~ (p=0.631 n=10+10) ChaCha20/100x40-4 421MB/s ± 3% 408MB/s ± 1% -3.14% (p=0.003 n=9+9) ChaCha20/65536-4 515MB/s ± 1% 519MB/s ± 3% ~ (p=0.161 n=7+10) ChaCha20/1000x65-4 501MB/s ± 2% 501MB/s ± 3% ~ (p=0.497 n=9+10) Also applied a fix for a lingering bug in the ppc64le assembly written by Lynn Boger <laboger@linux.vnet.ibm.com>. Updates golang/go#24485 Change-Id: I10cf24a7f10359b1b4ae63c9bb1946735b98ac9b Reviewed-on: https://go-review.googlesource.com/c/crypto/+/185439 Reviewed-by: Michael Munday <mike.munday@ibm.com>
294 lines
9.1 KiB
Go
294 lines
9.1 KiB
Go
// Copyright 2016 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 chacha20 implements the ChaCha20 encryption algorithm
|
|
// as specified in RFC 8439.
|
|
package chacha20
|
|
|
|
import (
|
|
"crypto/cipher"
|
|
"encoding/binary"
|
|
"math/bits"
|
|
|
|
"golang.org/x/crypto/internal/subtle"
|
|
)
|
|
|
|
// Cipher is a stateful instance of ChaCha20 using a particular key
|
|
// and nonce. A *Cipher implements the cipher.Stream interface.
|
|
type Cipher struct {
|
|
// The ChaCha20 state is 16 words: 4 constant, 8 of key, 1 of counter
|
|
// (incremented after each block), and 3 of nonce.
|
|
key [8]uint32
|
|
counter uint32
|
|
nonce [3]uint32
|
|
|
|
// The last len bytes of buf are leftover key stream bytes from the previous
|
|
// XORKeyStream invocation. The size of buf depends on how many blocks are
|
|
// computed at a time.
|
|
buf [bufSize]byte
|
|
len int
|
|
}
|
|
|
|
var _ cipher.Stream = (*Cipher)(nil)
|
|
|
|
// New creates a new ChaCha20 stream cipher with the given key and nonce.
|
|
// The initial counter value is set to 0.
|
|
func New(key [8]uint32, nonce [3]uint32) *Cipher {
|
|
return &Cipher{key: key, nonce: nonce}
|
|
}
|
|
|
|
// The constant first 4 words of the ChaCha20 state.
|
|
const (
|
|
j0 uint32 = 0x61707865 // expa
|
|
j1 uint32 = 0x3320646e // nd 3
|
|
j2 uint32 = 0x79622d32 // 2-by
|
|
j3 uint32 = 0x6b206574 // te k
|
|
)
|
|
|
|
const blockSize = 64
|
|
|
|
// quarterRound is the core of ChaCha20. It shuffles the bits of 4 state words.
|
|
// It's executed 4 times for each of the 20 ChaCha20 rounds, operating on all 16
|
|
// words each round, in columnar or diagonal groups of 4 at a time.
|
|
func quarterRound(a, b, c, d uint32) (uint32, uint32, uint32, uint32) {
|
|
a += b
|
|
d ^= a
|
|
d = bits.RotateLeft32(d, 16)
|
|
c += d
|
|
b ^= c
|
|
b = bits.RotateLeft32(b, 12)
|
|
a += b
|
|
d ^= a
|
|
d = bits.RotateLeft32(d, 8)
|
|
c += d
|
|
b ^= c
|
|
b = bits.RotateLeft32(b, 7)
|
|
return a, b, c, d
|
|
}
|
|
|
|
// XORKeyStream XORs each byte in the given slice with a byte from the
|
|
// cipher's key stream. Dst and src must overlap entirely or not at all.
|
|
//
|
|
// If len(dst) < len(src), XORKeyStream will panic. It is acceptable
|
|
// to pass a dst bigger than src, and in that case, XORKeyStream will
|
|
// only update dst[:len(src)] and will not touch the rest of dst.
|
|
//
|
|
// Multiple calls to XORKeyStream behave as if the concatenation of
|
|
// the src buffers was passed in a single run. That is, Cipher
|
|
// maintains state and does not reset at each XORKeyStream call.
|
|
func (s *Cipher) XORKeyStream(dst, src []byte) {
|
|
if len(src) == 0 {
|
|
return
|
|
}
|
|
if len(dst) < len(src) {
|
|
panic("chacha20: output smaller than input")
|
|
}
|
|
dst = dst[:len(src)]
|
|
if subtle.InexactOverlap(dst, src) {
|
|
panic("chacha20: invalid buffer overlap")
|
|
}
|
|
|
|
// First, drain any remaining key stream from a previous XORKeyStream.
|
|
if s.len != 0 {
|
|
keyStream := s.buf[bufSize-s.len:]
|
|
if len(src) < len(keyStream) {
|
|
keyStream = keyStream[:len(src)]
|
|
}
|
|
_ = src[len(keyStream)-1] // bounds check elimination hint
|
|
for i, b := range keyStream {
|
|
dst[i] = src[i] ^ b
|
|
}
|
|
s.len -= len(keyStream)
|
|
src = src[len(keyStream):]
|
|
dst = dst[len(keyStream):]
|
|
}
|
|
|
|
const blocksPerBuf = bufSize / blockSize
|
|
numBufs := (uint64(len(src)) + bufSize - 1) / bufSize
|
|
if uint64(s.counter)+numBufs*blocksPerBuf >= 1<<32 {
|
|
panic("chacha20: counter overflow")
|
|
}
|
|
|
|
// xorKeyStreamBlocks implementations expect input lengths that are a
|
|
// multiple of bufSize. Platform-specific ones process multiple blocks at a
|
|
// time, so have bufSizes that are a multiple of blockSize.
|
|
|
|
rem := len(src) % bufSize
|
|
full := len(src) - rem
|
|
|
|
if full > 0 {
|
|
s.xorKeyStreamBlocks(dst[:full], src[:full])
|
|
}
|
|
|
|
// If we have a partial (multi-)block, pad it for xorKeyStreamBlocks, and
|
|
// keep the leftover keystream for the next XORKeyStream invocation.
|
|
if rem > 0 {
|
|
s.buf = [bufSize]byte{}
|
|
copy(s.buf[:], src[full:])
|
|
s.xorKeyStreamBlocks(s.buf[:], s.buf[:])
|
|
s.len = bufSize - copy(dst[full:], s.buf[:])
|
|
}
|
|
}
|
|
|
|
func (s *Cipher) xorKeyStreamBlocksGeneric(dst, src []byte) {
|
|
if len(dst) != len(src) || len(dst)%blockSize != 0 {
|
|
panic("chacha20: internal error: wrong dst and/or src length")
|
|
}
|
|
|
|
// To generate each block of key stream, the initial cipher state
|
|
// (represented below) is passed through 20 rounds of shuffling,
|
|
// alternatively applying quarterRounds by columns (like 1, 5, 9, 13)
|
|
// or by diagonals (like 1, 6, 11, 12).
|
|
//
|
|
// 0:cccccccc 1:cccccccc 2:cccccccc 3:cccccccc
|
|
// 4:kkkkkkkk 5:kkkkkkkk 6:kkkkkkkk 7:kkkkkkkk
|
|
// 8:kkkkkkkk 9:kkkkkkkk 10:kkkkkkkk 11:kkkkkkkk
|
|
// 12:bbbbbbbb 13:nnnnnnnn 14:nnnnnnnn 15:nnnnnnnn
|
|
//
|
|
// c=constant k=key b=blockcount n=nonce
|
|
var (
|
|
c0, c1, c2, c3 = j0, j1, j2, j3
|
|
c4, c5, c6, c7 = s.key[0], s.key[1], s.key[2], s.key[3]
|
|
c8, c9, c10, c11 = s.key[4], s.key[5], s.key[6], s.key[7]
|
|
_, c13, c14, c15 = s.counter, s.nonce[0], s.nonce[1], s.nonce[2]
|
|
)
|
|
|
|
// Three quarters of the first round don't depend on the counter, so we can
|
|
// calculate them here, and reuse them for multiple blocks in the loop.
|
|
// TODO(filippo): experiment with reusing across XORKeyStream calls.
|
|
s1, s5, s9, s13 := quarterRound(c1, c5, c9, c13)
|
|
s2, s6, s10, s14 := quarterRound(c2, c6, c10, c14)
|
|
s3, s7, s11, s15 := quarterRound(c3, c7, c11, c15)
|
|
|
|
for i := 0; i < len(src); i += blockSize {
|
|
// The remainder of the first column round.
|
|
s0, s4, s8, s12 := quarterRound(c0, c4, c8, s.counter)
|
|
|
|
// The second diagonal round.
|
|
x0, x5, x10, x15 := quarterRound(s0, s5, s10, s15)
|
|
x1, x6, x11, x12 := quarterRound(s1, s6, s11, s12)
|
|
x2, x7, x8, x13 := quarterRound(s2, s7, s8, s13)
|
|
x3, x4, x9, x14 := quarterRound(s3, s4, s9, s14)
|
|
|
|
// The remaining 18 rounds.
|
|
for i := 0; i < 9; i++ {
|
|
// Column round.
|
|
x0, x4, x8, x12 = quarterRound(x0, x4, x8, x12)
|
|
x1, x5, x9, x13 = quarterRound(x1, x5, x9, x13)
|
|
x2, x6, x10, x14 = quarterRound(x2, x6, x10, x14)
|
|
x3, x7, x11, x15 = quarterRound(x3, x7, x11, x15)
|
|
|
|
// Diagonal round.
|
|
x0, x5, x10, x15 = quarterRound(x0, x5, x10, x15)
|
|
x1, x6, x11, x12 = quarterRound(x1, x6, x11, x12)
|
|
x2, x7, x8, x13 = quarterRound(x2, x7, x8, x13)
|
|
x3, x4, x9, x14 = quarterRound(x3, x4, x9, x14)
|
|
}
|
|
|
|
// Finally, add back the initial state to generate the key stream.
|
|
x0 += c0
|
|
x1 += c1
|
|
x2 += c2
|
|
x3 += c3
|
|
x4 += c4
|
|
x5 += c5
|
|
x6 += c6
|
|
x7 += c7
|
|
x8 += c8
|
|
x9 += c9
|
|
x10 += c10
|
|
x11 += c11
|
|
x12 += s.counter
|
|
x13 += c13
|
|
x14 += c14
|
|
x15 += c15
|
|
|
|
s.counter += 1
|
|
if s.counter == 0 {
|
|
panic("chacha20: internal error: counter overflow")
|
|
}
|
|
|
|
in, out := src[i:], dst[i:]
|
|
in, out = in[:blockSize], out[:blockSize] // bounds check elimination hint
|
|
|
|
// XOR the key stream with the source and write out the result.
|
|
xor(out[0:], in[0:], x0)
|
|
xor(out[4:], in[4:], x1)
|
|
xor(out[8:], in[8:], x2)
|
|
xor(out[12:], in[12:], x3)
|
|
xor(out[16:], in[16:], x4)
|
|
xor(out[20:], in[20:], x5)
|
|
xor(out[24:], in[24:], x6)
|
|
xor(out[28:], in[28:], x7)
|
|
xor(out[32:], in[32:], x8)
|
|
xor(out[36:], in[36:], x9)
|
|
xor(out[40:], in[40:], x10)
|
|
xor(out[44:], in[44:], x11)
|
|
xor(out[48:], in[48:], x12)
|
|
xor(out[52:], in[52:], x13)
|
|
xor(out[56:], in[56:], x14)
|
|
xor(out[60:], in[60:], x15)
|
|
}
|
|
}
|
|
|
|
// Advance discards bytes in the key stream until the next 64 byte block
|
|
// boundary is reached. If the key stream is already at a block boundary no
|
|
// bytes will be discarded.
|
|
func (s *Cipher) Advance() {
|
|
s.len -= s.len % blockSize
|
|
}
|
|
|
|
// XORKeyStream crypts bytes from in to out using the given key and counters.
|
|
// In and out must overlap entirely or not at all. Counter contains the raw
|
|
// ChaCha20 counter bytes (i.e. block counter followed by nonce).
|
|
func XORKeyStream(out, in []byte, counter *[16]byte, key *[32]byte) {
|
|
s := Cipher{
|
|
key: [8]uint32{
|
|
binary.LittleEndian.Uint32(key[0:4]),
|
|
binary.LittleEndian.Uint32(key[4:8]),
|
|
binary.LittleEndian.Uint32(key[8:12]),
|
|
binary.LittleEndian.Uint32(key[12:16]),
|
|
binary.LittleEndian.Uint32(key[16:20]),
|
|
binary.LittleEndian.Uint32(key[20:24]),
|
|
binary.LittleEndian.Uint32(key[24:28]),
|
|
binary.LittleEndian.Uint32(key[28:32]),
|
|
},
|
|
nonce: [3]uint32{
|
|
binary.LittleEndian.Uint32(counter[4:8]),
|
|
binary.LittleEndian.Uint32(counter[8:12]),
|
|
binary.LittleEndian.Uint32(counter[12:16]),
|
|
},
|
|
counter: binary.LittleEndian.Uint32(counter[0:4]),
|
|
}
|
|
s.XORKeyStream(out, in)
|
|
}
|
|
|
|
// HChaCha20 uses the ChaCha20 core to generate a derived key from a key and a
|
|
// nonce. It should only be used as part of the XChaCha20 construction.
|
|
func HChaCha20(key *[8]uint32, nonce *[4]uint32) [8]uint32 {
|
|
x0, x1, x2, x3 := j0, j1, j2, j3
|
|
x4, x5, x6, x7 := key[0], key[1], key[2], key[3]
|
|
x8, x9, x10, x11 := key[4], key[5], key[6], key[7]
|
|
x12, x13, x14, x15 := nonce[0], nonce[1], nonce[2], nonce[3]
|
|
|
|
for i := 0; i < 10; i++ {
|
|
// Diagonal round.
|
|
x0, x4, x8, x12 = quarterRound(x0, x4, x8, x12)
|
|
x1, x5, x9, x13 = quarterRound(x1, x5, x9, x13)
|
|
x2, x6, x10, x14 = quarterRound(x2, x6, x10, x14)
|
|
x3, x7, x11, x15 = quarterRound(x3, x7, x11, x15)
|
|
|
|
// Column round.
|
|
x0, x5, x10, x15 = quarterRound(x0, x5, x10, x15)
|
|
x1, x6, x11, x12 = quarterRound(x1, x6, x11, x12)
|
|
x2, x7, x8, x13 = quarterRound(x2, x7, x8, x13)
|
|
x3, x4, x9, x14 = quarterRound(x3, x4, x9, x14)
|
|
}
|
|
|
|
var out [8]uint32
|
|
out[0], out[1], out[2], out[3] = x0, x1, x2, x3
|
|
out[4], out[5], out[6], out[7] = x12, x13, x14, x15
|
|
return out
|
|
}
|