@@ -9,12 +9,14 @@ import math.bits
9
9
import crypto.internal.subtle
10
10
import encoding.binary
11
11
12
- // size of ChaCha20 key, ie 256 bits size, in bytes
12
+ // The size of ChaCha20 key, ie 256 bits size, in bytes
13
13
pub const key_size = 32
14
- // size of ietf ChaCha20 nonce, ie 96 bits size, in bytes
14
+ // The size of standard IETF ChaCha20 nonce, ie 96 bits size, in bytes
15
15
pub const nonce_size = 12
16
- // size of extended ChaCha20 nonce, called XChaCha20, 192 bits
16
+ // The size of extended variant of standard ChaCha20 ( XChaCha20) nonce , 192 bits
17
17
pub const x_nonce_size = 24
18
+ // The size of original ChaCha20 nonce, 64 bits
19
+ pub const orig_nonce_size = 8
18
20
// internal block size ChaCha20 operates on, in bytes
19
21
const block_size = 64
20
22
@@ -24,19 +26,39 @@ const cc1 = u32(0x3320646e) // nd 3
24
26
const cc2 = u32 (0x79622d32 ) // 2-by
25
27
const cc3 = u32 (0x6b206574 ) // te k
26
28
29
+ // CipherMode was enumeration of ChaCha20 supported variant.
30
+ enum CipherMode {
31
+ // The standard IETF ChaCha20 (and XChaCha20), with 32-bit internal counter.
32
+ standard
33
+ // The original ChaCha20 with 64-bit internal counter.
34
+ original
35
+ }
36
+
27
37
// Cipher represents ChaCha20 stream cipher instances.
28
38
pub struct Cipher {
39
+ // The mode of ChaCha20 cipher, set on cipher's creation.
40
+ mode CipherMode = .standard
29
41
mut :
30
- // internal's of ChaCha20 states, ie, 16 of u32 words, 4 of ChaCha20 constants,
31
- // 8 word (32 bytes) of keys, 3 word (24 bytes) of nonces and 1 word of counter
32
- key [8 ]u32
33
- nonce [3 ]u32
34
- counter u32
42
+ // The internal's of ChaCha20 states contains 512 bits (64 bytes), contains of
43
+ // 4 words (16 bytes) of ChaCha20 constants,
44
+ // 8 words (32 bytes) of ChaCha20 keys,
45
+ // 4 words (16 bytes) of raw nonces, with internal counter, support for 32 and 64 bit counters.
46
+ key [8 ]u32
47
+ nonce [4 ]u32
48
+
49
+ // Flag indicates whether this cipher's counter has reached the limit
35
50
overflow bool
51
+ // Flag that tells whether this cipher was an extended XChaCha20 standard variant.
52
+ // only make sense when mode == .standard
53
+ extended bool
54
+
36
55
// internal buffer for storing key stream results
37
- block []u8 = []u8 {len: block_size}
56
+ block []u8 = []u8 {len: block_size}
57
+ // The last length of leftover unprocessed keystream from internal buffer
38
58
length int
39
- // additional fields, follow the go version
59
+
60
+ // Additional fields, follows the go version. Its mainly used to optimize
61
+ // standard IETF ciphers operations by pre-chache some quarter_round step.
40
62
// vfmt off
41
63
precomp bool
42
64
p1 u32 p5 u32 p9 u32 p13 u32
45
67
// vfmt on
46
68
}
47
69
48
- // new_cipher creates a new ChaCha20 stream cipher with the given 32 bytes key, a 12 or 24 bytes nonce.
70
+ // new_cipher creates a new ChaCha20 stream cipher with the given 32 bytes key
71
+ // and bytes of nonce with supported size, ie, 8, 12 or 24 bytes nonce.
72
+ // Standard IETF variant use 12 bytes nonce's, if you want create original ChaCha20 cipher
73
+ // with support for 64-bit counter, use 8 bytes length nonce's instead
49
74
// If 24 bytes of nonce was provided, the XChaCha20 construction will be used.
50
75
// It returns new ChaCha20 cipher instance or an error if key or nonce have any other length.
76
+ @[direct_array_access]
51
77
pub fn new_cipher (key []u8 , nonce []u8 ) ! & Cipher {
52
- mut c := & Cipher{}
78
+ if key.len != key_size {
79
+ return error ('Bad key size provided' )
80
+ }
81
+ mut mode := CipherMode.standard
82
+ mut extended := false
83
+ match nonce.len {
84
+ nonce_size {}
85
+ x_nonce_size {
86
+ extended = true
87
+ }
88
+ orig_nonce_size {
89
+ mode = .original
90
+ // TODO: removes this when its getting fully supported
91
+ return error ('Original mode currently was not supported' )
92
+ }
93
+ else {
94
+ return error ('Unsupported nonce size' )
95
+ }
96
+ }
97
+ mut c := & Cipher{
98
+ mode: mode
99
+ extended: extended
100
+ }
53
101
// we dont need reset on new cipher instance
54
102
c.do_rekey (key, nonce)!
55
103
@@ -123,9 +171,9 @@ pub fn (mut c Cipher) xor_key_stream(mut dst []u8, src []u8) {
123
171
124
172
// check for counter overflow
125
173
num_blocks := (u64 (src_len) + block_size - 1 ) / block_size
126
- if c.overflow || u64 (c.counter ) + num_blocks > max_u32 {
174
+ if c.overflow || u64 (c.nonce[ 0 ] ) + num_blocks > max_u32 {
127
175
panic ('chacha20: counter overflow' )
128
- } else if u64 (c.counter ) + num_blocks == max_u32 {
176
+ } else if u64 (c.nonce[ 0 ] ) + num_blocks == max_u32 {
129
177
c.overflow = true
130
178
}
131
179
@@ -140,7 +188,7 @@ pub fn (mut c Cipher) xor_key_stream(mut dst []u8, src []u8) {
140
188
src_len - = full
141
189
142
190
// we dont support bufsize
143
- if u64 (c.counter ) + 1 > max_u32 {
191
+ if u64 (c.nonce[ 0 ] ) + 1 > max_u32 {
144
192
numblocks := (src_len + block_size - 1 ) / block_size
145
193
mut buf := c.block[block_size - numblocks * block_size..]
146
194
_ := copy (mut buf, src[idx..])
@@ -219,6 +267,13 @@ fn (mut c Cipher) chacha20_block_generic(mut dst []u8, src []u8) {
219
267
if dst.len != src.len || dst.len % block_size != 0 {
220
268
panic ('chacha20: internal error: wrong dst and/or src length' )
221
269
}
270
+ // Safety checks to make sure increasing current cipher's counter
271
+ // by nr_block was not overflowing internal counter.
272
+ num_block := u64 ((src.len + block_size - 1 ) / block_size)
273
+ if u64 (c.nonce[0 ]) + num_block > max_u32 {
274
+ panic ('Adding num_block to the current counter lead to overflow' )
275
+ }
276
+
222
277
// initializes ChaCha20 state
223
278
// 0:cccccccc 1:cccccccc 2:cccccccc 3:cccccccc
224
279
// 4:kkkkkkkk 5:kkkkkkkk 6:kkkkkkkk 7:kkkkkkkk
@@ -230,8 +285,8 @@ fn (mut c Cipher) chacha20_block_generic(mut dst []u8, src []u8) {
230
285
c4 , c5 , c6 , c7 := c.key[0 ], c.key[1 ], c.key[2 ], c.key[3 ]
231
286
c8 , c9 , c10 , c11 := c.key[4 ], c.key[5 ], c.key[6 ], c.key[7 ]
232
287
233
- _ := c.counter
234
- c13 , c14 , c15 := c.nonce[0 ], c.nonce[1 ], c.nonce[2 ]
288
+ mut c12 := c.nonce[ 0 ]
289
+ c13 , c14 , c15 := c.nonce[1 ], c.nonce[2 ], c.nonce[3 ]
235
290
236
291
// precomputes three first column rounds that do not depend on counter
237
292
if ! c.precomp {
@@ -244,7 +299,7 @@ fn (mut c Cipher) chacha20_block_generic(mut dst []u8, src []u8) {
244
299
mut src_len := src.len
245
300
for src_len > = block_size {
246
301
// remaining first column round
247
- fcr0 , fcr4 , fcr8 , fcr12 := quarter_round (c0 , c4 , c8 , c.counter )
302
+ fcr0 , fcr4 , fcr8 , fcr12 := quarter_round (c0 , c4 , c8 , c 12 )
248
303
249
304
// The second diagonal round.
250
305
mut x0 , mut x5 , mut x10 , mut x15 := quarter_round (fcr0 , c.p5 , c.p10 , c.p15 )
@@ -293,15 +348,17 @@ fn (mut c Cipher) chacha20_block_generic(mut dst []u8, src []u8) {
293
348
binary.little_endian_put_u32 (mut dst[idx + 44 ..idx + 48 ], binary.little_endian_u32 (src[
294
349
idx + 44 ..idx + 48 ]) ^ (x11 + c11 ))
295
350
binary.little_endian_put_u32 (mut dst[idx + 48 ..idx + 52 ], binary.little_endian_u32 (src[
296
- idx + 48 ..idx + 52 ]) ^ (x12 + c.counter ))
351
+ idx + 48 ..idx + 52 ]) ^ (x12 + c 12 ))
297
352
binary.little_endian_put_u32 (mut dst[idx + 52 ..idx + 56 ], binary.little_endian_u32 (src[
298
353
idx + 52 ..idx + 56 ]) ^ (x13 + c13 ))
299
354
binary.little_endian_put_u32 (mut dst[idx + 56 ..idx + 60 ], binary.little_endian_u32 (src[
300
355
idx + 56 ..idx + 60 ]) ^ (x14 + c14 ))
301
356
binary.little_endian_put_u32 (mut dst[idx + 60 ..idx + 64 ], binary.little_endian_u32 (src[
302
357
idx + 60 ..idx + 64 ]) ^ (x15 + c15 ))
303
358
304
- c.counter + = 1
359
+ // Its safe to update internal counter, its already checked before.
360
+ c12 + = 1
361
+ c.nonce[0 ] = c12
305
362
306
363
idx + = block_size
307
364
src_len - = block_size
@@ -324,28 +381,16 @@ pub fn (mut c Cipher) free() {
324
381
pub fn (mut c Cipher) reset () {
325
382
unsafe {
326
383
_ := vmemset (& c.key, 0 , 32 )
327
- _ := vmemset (& c.nonce, 0 , 12 )
384
+ _ := vmemset (& c.nonce, 0 , 16 )
328
385
c.block.reset ()
329
386
}
330
387
c.length = 0
331
- c.counter = u32 (0 )
332
388
c.overflow = false
333
389
c.precomp = false
334
390
335
- c.p1 = u32 (0 )
336
- c.p5 = u32 (0 )
337
- c.p9 = u32 (0 )
338
- c.p13 = u32 (0 )
339
-
340
- c.p2 = u32 (0 )
341
- c.p6 = u32 (0 )
342
- c.p10 = u32 (0 )
343
- c.p14 = u32 (0 )
344
-
345
- c.p3 = u32 (0 )
346
- c.p7 = u32 (0 )
347
- c.p11 = u32 (0 )
348
- c.p15 = u32 (0 )
391
+ c.p1 , c.p5 , c.p9 , c.p13 = u32 (0 ), u32 (0 ), u32 (0 ), u32 (0 )
392
+ c.p2 , c.p6 , c.p10 , c.p14 = u32 (0 ), u32 (0 ), u32 (0 ), u32 (0 )
393
+ c.p3 , c.p7 , c.p11 , c.p15 = u32 (0 ), u32 (0 ), u32 (0 ), u32 (0 )
349
394
}
350
395
351
396
// set_counter sets Cipher's counter
@@ -356,26 +401,41 @@ pub fn (mut c Cipher) set_counter(ctr u32) {
356
401
if c.overflow {
357
402
panic ('counter would overflow' )
358
403
}
359
- c.counter = ctr
404
+ c.nonce[ 0 ] = ctr
360
405
}
361
406
362
407
// rekey resets internal Cipher's state and reinitializes state with the provided key and nonce
363
408
pub fn (mut c Cipher) rekey (key []u8 , nonce []u8 ) ! {
409
+ // Original mode was not supported
410
+ // TODO: removes this when its getting fully supported
411
+ if nonce.len == orig_nonce_size {
412
+ return error ('Original mode was not supported' )
413
+ }
364
414
unsafe { c.reset () }
415
+ // this routine was publicly accesible to user, so we add a check here
416
+ // to ensure the supplied key and nonce has the correct size.
417
+ if key.len != key_size {
418
+ return error ('Bad key size provided for rekey' )
419
+ }
420
+ // For the standard cipher, allowed nonce size was nonce_size or x_nonce_size
421
+ if c.mode == .standard {
422
+ if nonce.len != x_nonce_size && nonce.len != nonce_size {
423
+ return error ('Bad nonce size for standard cipher, use 12 or 24 bytes length nonce' )
424
+ }
425
+ if c.extended && nonce.len != x_nonce_size {
426
+ return error ('Bad nonce size provided for extended variant cipher' )
427
+ }
428
+ }
429
+ // in the original variant, nonce should be orig_nonce_size length (8 bytes)
430
+ if c.mode == .original && nonce.len != orig_nonce_size {
431
+ return error ('Bad nonce size provided for original mode' )
432
+ }
365
433
c.do_rekey (key, nonce)!
366
434
}
367
435
368
436
// do_rekey reinitializes ChaCha20 instance with the provided key and nonce.
369
437
@[direct_array_access]
370
438
fn (mut c Cipher) do_rekey (key []u8 , nonce []u8 ) ! {
371
- // check for correctness of key and nonce length
372
- if key.len != key_size {
373
- return error ('chacha20: bad key size provided ' )
374
- }
375
- // check for nonce's length is 12 or 24
376
- if nonce.len != nonce_size && nonce.len != x_nonce_size {
377
- return error ('chacha20: bad nonce size provided' )
378
- }
379
439
mut nonces := nonce.clone ()
380
440
mut keys := key.clone ()
381
441
@@ -400,10 +460,12 @@ fn (mut c Cipher) do_rekey(key []u8, nonce []u8) ! {
400
460
c.key[6 ] = binary.little_endian_u32 (keys[24 ..28 ])
401
461
c.key[7 ] = binary.little_endian_u32 (keys[28 ..32 ])
402
462
463
+ // internal counter
464
+ c.nonce[0 ] = 0
403
465
// setup ChaCha20 cipher nonce
404
- c.nonce[0 ] = binary.little_endian_u32 (nonces[0 ..4 ])
405
- c.nonce[1 ] = binary.little_endian_u32 (nonces[4 ..8 ])
406
- c.nonce[2 ] = binary.little_endian_u32 (nonces[8 ..12 ])
466
+ c.nonce[1 ] = binary.little_endian_u32 (nonces[0 ..4 ])
467
+ c.nonce[2 ] = binary.little_endian_u32 (nonces[4 ..8 ])
468
+ c.nonce[3 ] = binary.little_endian_u32 (nonces[8 ..12 ])
407
469
}
408
470
409
471
// Helper and core function for ChaCha20
0 commit comments