This repository has been archived by the owner on Nov 9, 2017. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9
/
chacha20.go
235 lines (201 loc) · 7.18 KB
/
chacha20.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
// Package chacha20 provides a pure Go implementation of ChaCha20, a fast,
// secure stream cipher.
//
// From Bernstein, Daniel J. "ChaCha, a variant of Salsa20." Workshop Record of
// SASC. 2008. (http://cr.yp.to/chacha/chacha-20080128.pdf):
//
// ChaCha8 is a 256-bit stream cipher based on the 8-round cipher Salsa20/8.
// The changes from Salsa20/8 to ChaCha8 are designed to improve diffusion per
// round, conjecturally increasing resistance to cryptanalysis, while
// preserving -- and often improving -- time per round. ChaCha12 and ChaCha20
// are analogous modifications of the 12-round and 20-round ciphers Salsa20/12
// and Salsa20/20. This paper presents the ChaCha family and explains the
// differences between Salsa20 and ChaCha.
//
// For more information, see http://cr.yp.to/chacha.html
package chacha20
import (
"crypto/cipher"
"encoding/binary"
"errors"
"unsafe"
)
const (
// KeySize is the length of ChaCha20 keys, in bytes.
KeySize = 32
// NonceSize is the length of ChaCha20 nonces, in bytes.
NonceSize = 8
// XNonceSize is the length of XChaCha20 nonces, in bytes.
XNonceSize = 24
)
var (
// ErrInvalidKey is returned when the provided key is not 256 bits long.
ErrInvalidKey = errors.New("invalid key length (must be 256 bits)")
// ErrInvalidNonce is returned when the provided nonce is not 64 bits long.
ErrInvalidNonce = errors.New("invalid nonce length (must be 64 bits)")
// ErrInvalidXNonce is returned when the provided nonce is not 192 bits
// long.
ErrInvalidXNonce = errors.New("invalid nonce length (must be 192 bits)")
// ErrInvalidRounds is returned when the provided rounds is not
// 8, 12, or 20.
ErrInvalidRounds = errors.New("invalid rounds number (must be 8, 12, or 20)")
)
// New creates and returns a new cipher.Stream. The key argument must be 256
// bits long, and the nonce argument must be 64 bits long. The nonce must be
// randomly generated or used only once. This Stream instance must not be used
// to encrypt more than 2^70 bytes (~1 zettabyte).
func New(key []byte, nonce []byte) (cipher.Stream, error) {
return NewWithRounds(key, nonce, 20)
}
// NewWithRounds creates and returns a new cipher.Stream just like New but
// the rounds number of 8, 12, or 20 can be specified.
func NewWithRounds(key []byte, nonce []byte, rounds uint8) (cipher.Stream, error) {
if len(key) != KeySize {
return nil, ErrInvalidKey
}
if len(nonce) != NonceSize {
return nil, ErrInvalidNonce
}
if (rounds != 8) && (rounds != 12) && (rounds != 20) {
return nil, ErrInvalidRounds
}
s := new(stream)
s.init(key, nonce, rounds)
s.advance()
return s, nil
}
// NewXChaCha creates and returns a new cipher.Stream. The key argument must be
// 256 bits long, and the nonce argument must be 192 bits long. The nonce must
// be randomly generated or only used once. This Stream instance must not be
// used to encrypt more than 2^70 bytes (~1 zetta byte).
func NewXChaCha(key []byte, nonce []byte) (cipher.Stream, error) {
return NewXChaChaWithRounds(key, nonce, 20)
}
// NewXChaChaWithRounds creates and returns a new cipher.Stream just like
// NewXChaCha but the rounds number of 8, 12, or 20 can be specified.
func NewXChaChaWithRounds(key []byte, nonce []byte, rounds uint8) (cipher.Stream, error) {
if len(key) != KeySize {
return nil, ErrInvalidKey
}
if len(nonce) != XNonceSize {
return nil, ErrInvalidXNonce
}
if (rounds != 8) && (rounds != 12) && (rounds != 20) {
return nil, ErrInvalidRounds
}
s := new(stream)
s.init(key, nonce, rounds)
// Call HChaCha to derive the subkey using the key and the first 16 bytes
// of the nonce, and re-initialize the state using the subkey and the
// remaining nonce.
blockArr := (*[stateSize]uint32)(unsafe.Pointer(&s.block))
core(&s.state, blockArr, s.rounds, true)
copy(s.state[4:8], blockArr[0:4])
copy(s.state[8:12], blockArr[12:16])
s.state[12] = 0
s.state[13] = 0
s.state[14] = binary.LittleEndian.Uint32(nonce[16:])
s.state[15] = binary.LittleEndian.Uint32(nonce[20:])
s.advance()
return s, nil
}
type stream struct {
state [stateSize]uint32 // the state as an array of 16 32-bit words
block [blockSize]byte // the keystream as an array of 64 bytes
offset int // the offset of used bytes in block
rounds uint8
}
func (s *stream) XORKeyStream(dst, src []byte) {
// Stride over the input in 64-byte blocks, minus the amount of keystream
// previously used. This will produce best results when processing blocks
// of a size evenly divisible by 64.
i := 0
max := len(src)
for i < max {
gap := blockSize - s.offset
limit := i + gap
if limit > max {
limit = max
}
o := s.offset
for j := i; j < limit; j++ {
dst[j] = src[j] ^ s.block[o]
o++
}
i += gap
s.offset = o
if o == blockSize {
s.advance()
}
}
}
func (s *stream) init(key []byte, nonce []byte, rounds uint8) {
// the magic constants for 256-bit keys
s.state[0] = 0x61707865
s.state[1] = 0x3320646e
s.state[2] = 0x79622d32
s.state[3] = 0x6b206574
s.state[4] = binary.LittleEndian.Uint32(key[0:])
s.state[5] = binary.LittleEndian.Uint32(key[4:])
s.state[6] = binary.LittleEndian.Uint32(key[8:])
s.state[7] = binary.LittleEndian.Uint32(key[12:])
s.state[8] = binary.LittleEndian.Uint32(key[16:])
s.state[9] = binary.LittleEndian.Uint32(key[20:])
s.state[10] = binary.LittleEndian.Uint32(key[24:])
s.state[11] = binary.LittleEndian.Uint32(key[28:])
switch len(nonce) {
case NonceSize:
// ChaCha20 uses 8 byte nonces.
s.state[12] = 0
s.state[13] = 0
s.state[14] = binary.LittleEndian.Uint32(nonce[0:])
s.state[15] = binary.LittleEndian.Uint32(nonce[4:])
case XNonceSize:
// XChaCha20 derives the subkey via HChaCha initialized
// with the first 16 bytes of the nonce.
s.state[12] = binary.LittleEndian.Uint32(nonce[0:])
s.state[13] = binary.LittleEndian.Uint32(nonce[4:])
s.state[14] = binary.LittleEndian.Uint32(nonce[8:])
s.state[15] = binary.LittleEndian.Uint32(nonce[12:])
default:
// Never happens, both ctors validate the nonce length.
panic("invalid nonce size")
}
s.rounds = rounds
}
// BUG(codahale): Totally untested on big-endian CPUs. Would very much
// appreciate someone with an ARM device giving this a swing.
// advances the keystream
func (s *stream) advance() {
core(&s.state, (*[stateSize]uint32)(unsafe.Pointer(&s.block)), s.rounds, false)
if bigEndian {
j := blockSize - 1
for i := 0; i < blockSize/2; i++ {
s.block[j], s.block[i] = s.block[i], s.block[j]
j--
}
}
s.offset = 0
i := s.state[12] + 1
s.state[12] = i
if i == 0 {
s.state[13]++
}
}
const (
wordSize = 4 // the size of ChaCha20's words
stateSize = 16 // the size of ChaCha20's state, in words
blockSize = stateSize * wordSize // the size of ChaCha20's block, in bytes
)
var (
bigEndian bool // whether or not we're running on a bigEndian CPU
)
// Do some up-front bookkeeping on what sort of CPU we're using. ChaCha20 treats
// its state as a little-endian byte array when it comes to generating the
// keystream, which allows for a zero-copy approach to the core transform. On
// big-endian architectures, we have to take a hit to reverse the bytes.
func init() {
x := uint32(0x04030201)
y := [4]byte{0x1, 0x2, 0x3, 0x4}
bigEndian = *(*[4]byte)(unsafe.Pointer(&x)) != y
}