mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-08-22 12:38:06 +02:00
223 lines
6.2 KiB
Go
223 lines
6.2 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 core ChaCha20 function as specified
|
|
// in https://tools.ietf.org/html/rfc7539#section-2.3.
|
|
package chacha20
|
|
|
|
import (
|
|
"crypto/cipher"
|
|
"encoding/binary"
|
|
)
|
|
|
|
// assert that *Cipher implements cipher.Stream
|
|
var _ cipher.Stream = (*Cipher)(nil)
|
|
|
|
// Cipher is a stateful instance of ChaCha20 using a particular key
|
|
// and nonce. A *Cipher implements the cipher.Stream interface.
|
|
type Cipher struct {
|
|
key [8]uint32
|
|
nonce [3]uint32
|
|
counter uint32 // incremented after each block
|
|
buf [64]byte // buffer for unused keystream bytes
|
|
len int // number of unused keystream bytes at end of buf
|
|
}
|
|
|
|
// 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}
|
|
}
|
|
|
|
// 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) {
|
|
// xor src with buffered keystream first
|
|
if s.len != 0 {
|
|
buf := s.buf[len(s.buf)-s.len:]
|
|
if len(src) < len(buf) {
|
|
buf = buf[:len(src)]
|
|
}
|
|
td, ts := dst[:len(buf)], src[:len(buf)] // BCE hint
|
|
for i, b := range buf {
|
|
td[i] = ts[i] ^ b
|
|
}
|
|
s.len -= len(buf)
|
|
if s.len != 0 {
|
|
return
|
|
}
|
|
s.buf = [len(s.buf)]byte{} // zero the empty buffer
|
|
src = src[len(buf):]
|
|
dst = dst[len(buf):]
|
|
}
|
|
|
|
if len(src) == 0 {
|
|
return
|
|
}
|
|
|
|
// set up a 64-byte buffer to pad out the final block if needed
|
|
// (hoisted out of the main loop to avoid spills)
|
|
rem := len(src) % 64 // length of final block
|
|
fin := len(src) - rem // index of final block
|
|
if rem > 0 {
|
|
copy(s.buf[len(s.buf)-64:], src[fin:])
|
|
}
|
|
|
|
// qr calculates a quarter round
|
|
qr := func(a, b, c, d uint32) (uint32, uint32, uint32, uint32) {
|
|
a += b
|
|
d ^= a
|
|
d = (d << 16) | (d >> 16)
|
|
c += d
|
|
b ^= c
|
|
b = (b << 12) | (b >> 20)
|
|
a += b
|
|
d ^= a
|
|
d = (d << 8) | (d >> 24)
|
|
c += d
|
|
b ^= c
|
|
b = (b << 7) | (b >> 25)
|
|
return a, b, c, d
|
|
}
|
|
|
|
// ChaCha20 constants
|
|
const (
|
|
j0 = 0x61707865
|
|
j1 = 0x3320646e
|
|
j2 = 0x79622d32
|
|
j3 = 0x6b206574
|
|
)
|
|
|
|
// pre-calculate most of the first round
|
|
s1, s5, s9, s13 := qr(j1, s.key[1], s.key[5], s.nonce[0])
|
|
s2, s6, s10, s14 := qr(j2, s.key[2], s.key[6], s.nonce[1])
|
|
s3, s7, s11, s15 := qr(j3, s.key[3], s.key[7], s.nonce[2])
|
|
|
|
n := len(src)
|
|
src, dst = src[:n:n], dst[:n:n] // BCE hint
|
|
for i := 0; i < n; i += 64 {
|
|
// calculate the remainder of the first round
|
|
s0, s4, s8, s12 := qr(j0, s.key[0], s.key[4], s.counter)
|
|
|
|
// execute the second round
|
|
x0, x5, x10, x15 := qr(s0, s5, s10, s15)
|
|
x1, x6, x11, x12 := qr(s1, s6, s11, s12)
|
|
x2, x7, x8, x13 := qr(s2, s7, s8, s13)
|
|
x3, x4, x9, x14 := qr(s3, s4, s9, s14)
|
|
|
|
// execute the remaining 18 rounds
|
|
for i := 0; i < 9; i++ {
|
|
x0, x4, x8, x12 = qr(x0, x4, x8, x12)
|
|
x1, x5, x9, x13 = qr(x1, x5, x9, x13)
|
|
x2, x6, x10, x14 = qr(x2, x6, x10, x14)
|
|
x3, x7, x11, x15 = qr(x3, x7, x11, x15)
|
|
|
|
x0, x5, x10, x15 = qr(x0, x5, x10, x15)
|
|
x1, x6, x11, x12 = qr(x1, x6, x11, x12)
|
|
x2, x7, x8, x13 = qr(x2, x7, x8, x13)
|
|
x3, x4, x9, x14 = qr(x3, x4, x9, x14)
|
|
}
|
|
|
|
x0 += j0
|
|
x1 += j1
|
|
x2 += j2
|
|
x3 += j3
|
|
|
|
x4 += s.key[0]
|
|
x5 += s.key[1]
|
|
x6 += s.key[2]
|
|
x7 += s.key[3]
|
|
x8 += s.key[4]
|
|
x9 += s.key[5]
|
|
x10 += s.key[6]
|
|
x11 += s.key[7]
|
|
|
|
x12 += s.counter
|
|
x13 += s.nonce[0]
|
|
x14 += s.nonce[1]
|
|
x15 += s.nonce[2]
|
|
|
|
// increment the counter
|
|
s.counter += 1
|
|
if s.counter == 0 {
|
|
panic("chacha20: counter overflow")
|
|
}
|
|
|
|
// pad to 64 bytes if needed
|
|
in, out := src[i:], dst[i:]
|
|
if i == fin {
|
|
// src[fin:] has already been copied into s.buf before
|
|
// the main loop
|
|
in, out = s.buf[len(s.buf)-64:], s.buf[len(s.buf)-64:]
|
|
}
|
|
in, out = in[:64], out[:64] // BCE 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)
|
|
}
|
|
// copy any trailing bytes out of the buffer and into dst
|
|
if rem != 0 {
|
|
s.len = 64 - rem
|
|
copy(dst[fin:], s.buf[len(s.buf)-64:])
|
|
}
|
|
}
|
|
|
|
// Advance discards bytes in the key stream until the next 64 byte block
|
|
// boundary is reached and updates the counter accordingly. If the key
|
|
// stream is already at a block boundary no bytes will be discarded and
|
|
// the counter will be unchanged.
|
|
func (s *Cipher) Advance() {
|
|
s.len -= s.len % 64
|
|
if s.len == 0 {
|
|
s.buf = [len(s.buf)]byte{}
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
}
|