mirror of
https://github.com/vlang/v.git
synced 2025-09-24 04:48:28 -04:00
x.crypto.chacha20: fix internal counter handling (#25334)
This commit is contained in:
parent
0e68a92838
commit
5fd2278df4
@ -39,7 +39,7 @@ enum CipherMode {
|
|||||||
pub fn encrypt(key []u8, nonce []u8, plaintext []u8) ![]u8 {
|
pub fn encrypt(key []u8, nonce []u8, plaintext []u8) ![]u8 {
|
||||||
mut stream := new_stream(key, nonce)!
|
mut stream := new_stream(key, nonce)!
|
||||||
mut dst := []u8{len: plaintext.len}
|
mut dst := []u8{len: plaintext.len}
|
||||||
stream.keystream_full(mut dst, plaintext)
|
stream.keystream_full(mut dst, plaintext)!
|
||||||
unsafe { stream.reset() }
|
unsafe { stream.reset() }
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
@ -49,7 +49,7 @@ pub fn encrypt(key []u8, nonce []u8, plaintext []u8) ![]u8 {
|
|||||||
pub fn decrypt(key []u8, nonce []u8, ciphertext []u8) ![]u8 {
|
pub fn decrypt(key []u8, nonce []u8, ciphertext []u8) ![]u8 {
|
||||||
mut stream := new_stream(key, nonce)!
|
mut stream := new_stream(key, nonce)!
|
||||||
mut dst := []u8{len: ciphertext.len}
|
mut dst := []u8{len: ciphertext.len}
|
||||||
stream.keystream_full(mut dst, ciphertext)
|
stream.keystream_full(mut dst, ciphertext)!
|
||||||
unsafe { stream.reset() }
|
unsafe { stream.reset() }
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
@ -94,14 +94,8 @@ pub fn (mut c Cipher) xor_key_stream(mut dst []u8, src []u8) {
|
|||||||
|
|
||||||
mut idx := 0
|
mut idx := 0
|
||||||
mut src_len := src.len
|
mut src_len := src.len
|
||||||
// check for counter overflow
|
|
||||||
num_blocks := (u64(src_len) + block_size - 1) / block_size
|
|
||||||
if c.Stream.check_ctr(num_blocks) {
|
|
||||||
panic('chacha20: internal counter overflow')
|
|
||||||
}
|
|
||||||
|
|
||||||
dst = unsafe { dst[..src_len] }
|
dst = unsafe { dst[..src_len] }
|
||||||
|
|
||||||
if subtle.inexact_overlap(dst, src) {
|
if subtle.inexact_overlap(dst, src) {
|
||||||
panic('chacha20: invalid buffer overlap')
|
panic('chacha20: invalid buffer overlap')
|
||||||
}
|
}
|
||||||
@ -129,21 +123,27 @@ pub fn (mut c Cipher) xor_key_stream(mut dst []u8, src []u8) {
|
|||||||
full := src_len - src_len % block_size
|
full := src_len - src_len % block_size
|
||||||
if full > 0 {
|
if full > 0 {
|
||||||
src_block := unsafe { src[idx..idx + full] }
|
src_block := unsafe { src[idx..idx + full] }
|
||||||
c.Stream.keystream_with_blocksize(mut dst[idx..idx + full], src_block)
|
c.Stream.keystream_with_blocksize(mut dst[idx..idx + full], src_block) or {
|
||||||
|
c.Stream.overflow = true
|
||||||
|
panic('chacha20: xor_key_stream leads to counter overflow')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
idx += full
|
idx += full
|
||||||
src_len -= full
|
src_len -= full
|
||||||
|
|
||||||
// If we have a partial block, pad it for chacha20_block_generic, and
|
// If we have a partial block, pad it for keystream_with_blocksize, and
|
||||||
// keep the leftover keystream for the next invocation.
|
// keep the leftover keystream for the next invocation.
|
||||||
if src_len > 0 {
|
if src_len > 0 {
|
||||||
// Make sure, internal buffer cleared or the old garbaged data from previous call still there
|
// Make sure, internal buffer cleared or the old garbaged data from previous call still there
|
||||||
// See the issue at https://github.com/vlang/v/issues/24043
|
// See the issue at https://github.com/vlang/v/issues/24043
|
||||||
unsafe { c.block.reset() } // = []u8{len: block_size}
|
unsafe { c.block.reset() } // = []u8{len: block_size}
|
||||||
// copy the last src block to internal buffer, and performs
|
// copy the last src block to internal buffer, and performs
|
||||||
// chacha20_block_generic on this buffer, and stores into remaining dst
|
// keystream_with_blocksize on this buffer, and stores into remaining dst
|
||||||
_ := copy(mut c.block, src[idx..])
|
_ := copy(mut c.block, src[idx..])
|
||||||
c.Stream.keystream_with_blocksize(mut c.block, c.block)
|
c.Stream.keystream_with_blocksize(mut c.block, c.block) or {
|
||||||
|
c.Stream.overflow = true
|
||||||
|
panic('chacha20: xor_key_stream leads to counter overflow')
|
||||||
|
}
|
||||||
n := copy(mut dst[idx..], c.block)
|
n := copy(mut dst[idx..], c.block)
|
||||||
// the length of remaining bytes of unprocessed keystream
|
// the length of remaining bytes of unprocessed keystream
|
||||||
c.length = block_size - n
|
c.length = block_size - n
|
||||||
@ -156,18 +156,18 @@ pub fn (mut c Cipher) xor_key_stream(mut dst []u8, src []u8) {
|
|||||||
// Its added to allow `chacha20poly1305` modules to work without key stream fashion.
|
// Its added to allow `chacha20poly1305` modules to work without key stream fashion.
|
||||||
// TODO: integrates it with the rest
|
// TODO: integrates it with the rest
|
||||||
@[direct_array_access]
|
@[direct_array_access]
|
||||||
pub fn (mut c Cipher) encrypt(mut dst []u8, src []u8) {
|
pub fn (mut c Cipher) encrypt(mut dst []u8, src []u8) ! {
|
||||||
if src.len == 0 {
|
if src.len == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if dst.len < src.len {
|
if dst.len < src.len {
|
||||||
panic('chacha20: dst buffer is to small')
|
return error('chacha20: dst buffer is to small')
|
||||||
}
|
}
|
||||||
if subtle.inexact_overlap(dst, src) {
|
if subtle.inexact_overlap(dst, src) {
|
||||||
panic('chacha20: invalid buffer overlap')
|
return error('chacha20: invalid buffer overlap')
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Stream.keystream_full(mut dst, src)
|
c.Stream.keystream_full(mut dst, src)!
|
||||||
}
|
}
|
||||||
|
|
||||||
// free the resources taken by the Cipher `c`. Dont use cipher after .free call
|
// free the resources taken by the Cipher `c`. Dont use cipher after .free call
|
||||||
|
@ -97,13 +97,13 @@ fn test_chacha20_encrypt_with_64bit_counter() ! {
|
|||||||
|
|
||||||
mut c := new_cipher(key, nonce)!
|
mut c := new_cipher(key, nonce)!
|
||||||
mut dst := []u8{len: plaintext.len}
|
mut dst := []u8{len: plaintext.len}
|
||||||
c.encrypt(mut dst, plaintext)
|
c.encrypt(mut dst, plaintext)!
|
||||||
assert dst == ciphertext
|
assert dst == ciphertext
|
||||||
|
|
||||||
// decrypts the ciphertext back
|
// decrypts the ciphertext back
|
||||||
// we need rekey the ciphers, because internal states has changed from previous invocations.
|
// we need rekey the ciphers, because internal states has changed from previous invocations.
|
||||||
c.rekey(key, nonce)!
|
c.rekey(key, nonce)!
|
||||||
c.encrypt(mut dst, ciphertext)
|
c.encrypt(mut dst, ciphertext)!
|
||||||
assert dst == plaintext
|
assert dst == plaintext
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,7 +129,7 @@ fn test_chacha20_cipher_encrypt_with_xor_keystream() ! {
|
|||||||
cs.set_counter(c.counter)
|
cs.set_counter(c.counter)
|
||||||
|
|
||||||
mut output := []u8{len: plaintext_bytes.len}
|
mut output := []u8{len: plaintext_bytes.len}
|
||||||
cs.encrypt(mut output, plaintext_bytes)
|
cs.encrypt(mut output, plaintext_bytes)!
|
||||||
|
|
||||||
expected := hex.decode(c.output)!
|
expected := hex.decode(c.output)!
|
||||||
assert output == expected
|
assert output == expected
|
||||||
@ -146,7 +146,7 @@ fn test_chacha20_cipher_decrypt_with_xor_keystream() ! {
|
|||||||
cs.set_counter(c.counter)
|
cs.set_counter(c.counter)
|
||||||
|
|
||||||
mut output := []u8{len: ciphertext.len}
|
mut output := []u8{len: ciphertext.len}
|
||||||
cs.encrypt(mut output, ciphertext)
|
cs.encrypt(mut output, ciphertext)!
|
||||||
|
|
||||||
expected_decrypted_message := hex.decode(c.plaintext)!
|
expected_decrypted_message := hex.decode(c.plaintext)!
|
||||||
assert output == expected_decrypted_message
|
assert output == expected_decrypted_message
|
||||||
|
@ -15,20 +15,23 @@ const max_32bit_counter = u64(max_u32)
|
|||||||
// default chacha20 quarter round number
|
// default chacha20 quarter round number
|
||||||
const default_qround_nr = 10
|
const default_qround_nr = 10
|
||||||
|
|
||||||
// ChaCha20 stream with internal counter
|
// Stream is an internal structure where main ChaCha20 algorithm operates on.
|
||||||
@[noinit]
|
@[noinit]
|
||||||
struct Stream {
|
struct Stream {
|
||||||
mut:
|
mut:
|
||||||
|
// underlying stream's key
|
||||||
|
key [8]u32
|
||||||
|
// underlying stream's nonce with internal counter
|
||||||
|
nonce [4]u32
|
||||||
|
|
||||||
// The mode (variant) of this ChaCha20 stream
|
// The mode (variant) of this ChaCha20 stream
|
||||||
// Standard IETF variant or original (from DJ Bernstein) variant, set on creation.
|
// Standard IETF variant or original (from DJ Bernstein) variant, set on creation.
|
||||||
mode CipherMode = .standard
|
mode CipherMode = .standard
|
||||||
// Flag that tells whether this stream was an extended XChaCha20 standard variant.
|
// Flag that tells whether this stream was an extended XChaCha20 standard variant.
|
||||||
// only make sense when mode == .standard
|
// only make sense when mode == .standard
|
||||||
extended bool
|
extended bool
|
||||||
// underlying stream's key
|
// Flag tells whether this stream has reached the counter limit
|
||||||
key [8]u32
|
overflow bool
|
||||||
// underlying stream's nonce with internal counter
|
|
||||||
nonce [4]u32
|
|
||||||
|
|
||||||
// counter-independent precomputed values
|
// counter-independent precomputed values
|
||||||
precomp bool
|
precomp bool
|
||||||
@ -64,7 +67,7 @@ fn new_stream(key []u8, nonce []u8) !Stream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if this an extended chacha20 construct, derives a new key and nonce
|
// if this an extended chacha20 construct, derives a new key and nonce
|
||||||
new_key, new_nonce := if extended {
|
new_key, new_nonce := if mode == .standard && extended {
|
||||||
xkey, xnonce := derive_xchacha20_key_nonce(key, nonce)!
|
xkey, xnonce := derive_xchacha20_key_nonce(key, nonce)!
|
||||||
xkey, xnonce
|
xkey, xnonce
|
||||||
} else {
|
} else {
|
||||||
@ -107,8 +110,6 @@ fn new_stream(key []u8, nonce []u8) !Stream {
|
|||||||
// reset resets internal stream
|
// reset resets internal stream
|
||||||
@[unsafe]
|
@[unsafe]
|
||||||
fn (mut s Stream) reset() {
|
fn (mut s Stream) reset() {
|
||||||
s.mode = .standard
|
|
||||||
s.extended = false
|
|
||||||
unsafe {
|
unsafe {
|
||||||
_ := vmemset(&s.key, 0, 32)
|
_ := vmemset(&s.key, 0, 32)
|
||||||
_ := vmemset(&s.nonce, 0, 16)
|
_ := vmemset(&s.nonce, 0, 16)
|
||||||
@ -148,18 +149,22 @@ fn (s Stream) new_curr_state() State {
|
|||||||
|
|
||||||
// keystream_full process with full size of src being processed
|
// keystream_full process with full size of src being processed
|
||||||
@[direct_array_access]
|
@[direct_array_access]
|
||||||
fn (mut s Stream) keystream_full(mut dst []u8, src []u8) {
|
fn (mut s Stream) keystream_full(mut dst []u8, src []u8) ! {
|
||||||
|
if s.overflow {
|
||||||
|
return error('chacha20: keystream_full counter has reached the limit')
|
||||||
|
}
|
||||||
// number of block to be processed
|
// number of block to be processed
|
||||||
nr_blocks := src.len / block_size
|
nr_blocks := src.len / block_size
|
||||||
// check for counter overflow
|
// check for counter overflow
|
||||||
if s.check_ctr(u64(nr_blocks)) {
|
if s.check_ctr(u64(nr_blocks)) {
|
||||||
panic('chacha20: internal counter overflow')
|
s.overflow = true
|
||||||
|
return error('chacha20: internal counter overflow')
|
||||||
}
|
}
|
||||||
// process for full block_size-d msg
|
// process for full block_size-d msg
|
||||||
for i := 0; i < nr_blocks; i++ {
|
for i := 0; i < nr_blocks; i++ {
|
||||||
block := unsafe { src[i * block_size..(i + 1) * block_size] }
|
block := unsafe { src[i * block_size..(i + 1) * block_size] }
|
||||||
// process with block_size keystream
|
// process with block_size keystream
|
||||||
s.keystream_with_blocksize(mut dst[i * block_size..(i + 1) * block_size], block)
|
s.keystream_with_blocksize(mut dst[i * block_size..(i + 1) * block_size], block)!
|
||||||
}
|
}
|
||||||
|
|
||||||
// process for remaining partial block
|
// process for remaining partial block
|
||||||
@ -170,7 +175,7 @@ fn (mut s Stream) keystream_full(mut dst []u8, src []u8) {
|
|||||||
_ := copy(mut last_bytes, last_block)
|
_ := copy(mut last_bytes, last_block)
|
||||||
|
|
||||||
// process the padded last block
|
// process the padded last block
|
||||||
s.keystream_with_blocksize(mut last_bytes, last_bytes)
|
s.keystream_with_blocksize(mut last_bytes, last_bytes)!
|
||||||
_ := copy(mut dst[nr_blocks * block_size..], last_bytes)
|
_ := copy(mut dst[nr_blocks * block_size..], last_bytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -178,7 +183,7 @@ fn (mut s Stream) keystream_full(mut dst []u8, src []u8) {
|
|||||||
// keystream_with_blocksize produces stream from src bytes that aligns with block_size,
|
// keystream_with_blocksize produces stream from src bytes that aligns with block_size,
|
||||||
// serialized in little-endian form and stored into dst buffer.
|
// serialized in little-endian form and stored into dst buffer.
|
||||||
@[direct_array_access]
|
@[direct_array_access]
|
||||||
fn (mut s Stream) keystream_with_blocksize(mut dst []u8, src []u8) {
|
fn (mut s Stream) keystream_with_blocksize(mut dst []u8, src []u8) ! {
|
||||||
// ChaCha20 keystream generator was relatively easy to understand.
|
// ChaCha20 keystream generator was relatively easy to understand.
|
||||||
// Its contains steps:
|
// Its contains steps:
|
||||||
// - loads current ChaCha20 into temporary state, used for later.
|
// - loads current ChaCha20 into temporary state, used for later.
|
||||||
@ -189,64 +194,54 @@ fn (mut s Stream) keystream_with_blocksize(mut dst []u8, src []u8) {
|
|||||||
//
|
//
|
||||||
// Makes sure its works for size of multiple of block_size
|
// Makes sure its works for size of multiple of block_size
|
||||||
if dst.len != src.len || dst.len % block_size != 0 {
|
if dst.len != src.len || dst.len % block_size != 0 {
|
||||||
panic('chacha20: internal error: wrong dst and/or src length')
|
return error('chacha20: internal error: wrong dst and/or src length')
|
||||||
|
}
|
||||||
|
// check if this stream has reached the counter limit
|
||||||
|
if s.overflow {
|
||||||
|
return error('chacha20: internal counter has reached the limit, please rekey')
|
||||||
|
}
|
||||||
|
// check for counter overflow when processing number of blocks
|
||||||
|
num_blocks := (u64(src.len) + block_size - 1) / block_size
|
||||||
|
if s.check_ctr(num_blocks) {
|
||||||
|
s.overflow = true
|
||||||
|
return error('chacha20.check_ctr: internal counter overflow')
|
||||||
}
|
}
|
||||||
|
|
||||||
// load state from current stream
|
// load state from current stream
|
||||||
st := s.new_curr_state()
|
mut st := s.new_curr_state()
|
||||||
// clone the state
|
// clone the state
|
||||||
mut st_c := clone_state(st)
|
mut st_c := clone_state(st)
|
||||||
|
|
||||||
// cache counter-independent precomputed values
|
// precomputes cache counter-independent values
|
||||||
if s.mode == .standard {
|
if s.mode == .standard && !s.precomp {
|
||||||
// first column round
|
s.precomp(st)
|
||||||
mut fcr := Quartet{st[0], st[4], st[8], st[12]}
|
|
||||||
// precomputes three first column rounds that do not depend on counter
|
|
||||||
if !s.precomp {
|
|
||||||
mut pcr1 := Quartet{st[1], st[5], st[9], st[13]}
|
|
||||||
mut pcr2 := Quartet{st[2], st[6], st[10], st[14]}
|
|
||||||
mut pcr3 := Quartet{st[3], st[7], st[11], st[15]}
|
|
||||||
|
|
||||||
qround_on_quartet(mut pcr1)
|
|
||||||
qround_on_quartet(mut pcr2)
|
|
||||||
qround_on_quartet(mut pcr3)
|
|
||||||
|
|
||||||
s.p1 = pcr1.e0
|
|
||||||
s.p5 = pcr1.e1
|
|
||||||
s.p9 = pcr1.e2
|
|
||||||
s.p13 = pcr1.e3
|
|
||||||
|
|
||||||
s.p2 = pcr2.e0
|
|
||||||
s.p6 = pcr2.e1
|
|
||||||
s.p10 = pcr2.e2
|
|
||||||
s.p14 = pcr2.e3
|
|
||||||
|
|
||||||
s.p3 = pcr3.e0
|
|
||||||
s.p7 = pcr3.e1
|
|
||||||
s.p11 = pcr3.e2
|
|
||||||
s.p15 = pcr3.e3
|
|
||||||
|
|
||||||
s.precomp = true
|
|
||||||
}
|
|
||||||
// remaining first column round
|
|
||||||
qround_on_quartet(mut fcr)
|
|
||||||
|
|
||||||
// First diagonal round.
|
|
||||||
qround_on_state_with_quartet(mut st_c, fcr.e0, s.p5, s.p10, s.p15, 0, 5, 10, 15)
|
|
||||||
qround_on_state_with_quartet(mut st_c, s.p1, s.p6, s.p11, fcr.e3, 1, 6, 11, 12)
|
|
||||||
qround_on_state_with_quartet(mut st_c, s.p2, s.p7, fcr.e2, s.p13, 2, 7, 8, 13)
|
|
||||||
qround_on_state_with_quartet(mut st_c, s.p3, fcr.e1, s.p9, s.p14, 3, 4, 9, 14)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mut idx := 0
|
mut idx := 0
|
||||||
mut src_len := src.len
|
mut src_len := src.len
|
||||||
for src_len >= block_size {
|
for src_len >= block_size {
|
||||||
|
if s.mode == .standard {
|
||||||
|
// remaining first column round
|
||||||
|
mut fcr := Quartet{st[0], st[4], st[8], st[12]}
|
||||||
|
qround_on_quartet(mut fcr)
|
||||||
|
|
||||||
|
// First diagonal round.
|
||||||
|
qround_on_state_with_quartet(mut st_c, fcr.e0, s.p5, s.p10, s.p15, 0, 5, 10,
|
||||||
|
15)
|
||||||
|
qround_on_state_with_quartet(mut st_c, s.p1, s.p6, s.p11, fcr.e3, 1, 6, 11,
|
||||||
|
12)
|
||||||
|
qround_on_state_with_quartet(mut st_c, s.p2, s.p7, fcr.e2, s.p13, 2, 7, 8,
|
||||||
|
13)
|
||||||
|
qround_on_state_with_quartet(mut st_c, s.p3, fcr.e1, s.p9, s.p14, 3, 4, 9,
|
||||||
|
14)
|
||||||
|
}
|
||||||
// The remaining rounds
|
// The remaining rounds
|
||||||
//
|
//
|
||||||
// For standard variant, the first column-round was already precomputed,
|
// For standard variant, the first column-round was already precomputed,
|
||||||
// For original variant, its use full quarter round number.
|
// For original variant, its use full quarter round number.
|
||||||
n := if s.mode == .standard { 9 } else { default_qround_nr }
|
|
||||||
// perform chacha20 quarter round n-times
|
// perform chacha20 quarter round n-times
|
||||||
|
n := if s.mode == .standard { 9 } else { default_qround_nr }
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
// Column-round
|
// Column-round
|
||||||
// 0 | 1 | 2 | 3
|
// 0 | 1 | 2 | 3
|
||||||
@ -272,12 +267,33 @@ fn (mut s Stream) keystream_with_blocksize(mut dst []u8, src []u8) {
|
|||||||
// add back keystream result to initial state, xor-ing with the src and stores into dst
|
// add back keystream result to initial state, xor-ing with the src and stores into dst
|
||||||
for i := 0; i < 16; i++ {
|
for i := 0; i < 16; i++ {
|
||||||
src_block := unsafe { src[idx + (i * 4)..idx + (i + 1) * 4] }
|
src_block := unsafe { src[idx + (i * 4)..idx + (i + 1) * 4] }
|
||||||
binary.little_endian_put_u32(mut dst[idx + (i * 4)..idx + (i + 1) * 4], binary.little_endian_u32(src_block) ^ (
|
add_xored := binary.little_endian_u32(src_block) ^ (st_c[i] + st[i])
|
||||||
st_c[i] + st[i]))
|
binary.little_endian_put_u32(mut dst[idx + (i * 4)..idx + (i + 1) * 4], add_xored)
|
||||||
}
|
}
|
||||||
|
|
||||||
// increases Stream's internal counter
|
// increases Stream's internal counter
|
||||||
s.inc_ctr()
|
if s.mode == .original {
|
||||||
|
st[12] += 1
|
||||||
|
// first counter reset ?
|
||||||
|
if st[12] == 0 {
|
||||||
|
// increase second counter, if reset, mark as an overflow and return error
|
||||||
|
st[13] += 1
|
||||||
|
if st[13] == 0 {
|
||||||
|
s.overflow = true
|
||||||
|
return error('chacha20.keystream_with_blocksize: 64-bit counter reached')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// store the counter
|
||||||
|
s.nonce[0] = st[12]
|
||||||
|
s.nonce[1] = st[13]
|
||||||
|
} else {
|
||||||
|
st[12] += 1
|
||||||
|
if st[12] == 0 {
|
||||||
|
s.overflow = true
|
||||||
|
return error('chacha20.keystream_with_blocksize: overflow 32-bit counter')
|
||||||
|
}
|
||||||
|
s.nonce[0] = st[12]
|
||||||
|
}
|
||||||
|
|
||||||
// updates index
|
// updates index
|
||||||
idx += block_size
|
idx += block_size
|
||||||
@ -285,6 +301,35 @@ fn (mut s Stream) keystream_with_blocksize(mut dst []u8, src []u8) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// precomp does quarter round on counter-independent quartet values on running state st.
|
||||||
|
@[direct_array_access; inline]
|
||||||
|
fn (mut s Stream) precomp(st State) {
|
||||||
|
mut pcr1 := Quartet{st[1], st[5], st[9], st[13]}
|
||||||
|
mut pcr2 := Quartet{st[2], st[6], st[10], st[14]}
|
||||||
|
mut pcr3 := Quartet{st[3], st[7], st[11], st[15]}
|
||||||
|
|
||||||
|
qround_on_quartet(mut pcr1)
|
||||||
|
qround_on_quartet(mut pcr2)
|
||||||
|
qround_on_quartet(mut pcr3)
|
||||||
|
|
||||||
|
s.p1 = pcr1.e0
|
||||||
|
s.p5 = pcr1.e1
|
||||||
|
s.p9 = pcr1.e2
|
||||||
|
s.p13 = pcr1.e3
|
||||||
|
|
||||||
|
s.p2 = pcr2.e0
|
||||||
|
s.p6 = pcr2.e1
|
||||||
|
s.p10 = pcr2.e2
|
||||||
|
s.p14 = pcr2.e3
|
||||||
|
|
||||||
|
s.p3 = pcr3.e0
|
||||||
|
s.p7 = pcr3.e1
|
||||||
|
s.p11 = pcr3.e2
|
||||||
|
s.p15 = pcr3.e3
|
||||||
|
|
||||||
|
s.precomp = true
|
||||||
|
}
|
||||||
|
|
||||||
// Handling of Stream's internal counter
|
// Handling of Stream's internal counter
|
||||||
//
|
//
|
||||||
|
|
||||||
@ -307,11 +352,6 @@ fn (b Stream) ctr() u64 {
|
|||||||
// set_ctr sets Stream's counter
|
// set_ctr sets Stream's counter
|
||||||
@[direct_array_access; inline]
|
@[direct_array_access; inline]
|
||||||
fn (mut b Stream) set_ctr(ctr u64) {
|
fn (mut b Stream) set_ctr(ctr u64) {
|
||||||
// if this set counter would overflow internal counter
|
|
||||||
// we do panic instead
|
|
||||||
if b.check_ctr(ctr) {
|
|
||||||
panic('set_ctr: invalid check, maybe would overflow')
|
|
||||||
}
|
|
||||||
match b.mode {
|
match b.mode {
|
||||||
.original {
|
.original {
|
||||||
b.nonce[0] = u32(ctr)
|
b.nonce[0] = u32(ctr)
|
||||||
@ -327,15 +367,6 @@ fn (mut b Stream) set_ctr(ctr u64) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// inc_ctr increases internal counter by one.
|
|
||||||
@[inline]
|
|
||||||
fn (mut b Stream) inc_ctr() {
|
|
||||||
mut curr_ctr := b.ctr()
|
|
||||||
curr_ctr += 1
|
|
||||||
|
|
||||||
b.set_ctr(curr_ctr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check_ctr checks for counter overflow when added by value.
|
// check_ctr checks for counter overflow when added by value.
|
||||||
// It returns true on counter overflow.
|
// It returns true on counter overflow.
|
||||||
@[inline]
|
@[inline]
|
||||||
@ -361,13 +392,6 @@ fn (b Stream) max_ctr() u64 {
|
|||||||
// State represents the running 64-bytes of chacha20 stream,
|
// State represents the running 64-bytes of chacha20 stream,
|
||||||
type State = [16]u32
|
type State = [16]u32
|
||||||
|
|
||||||
@[direct_array_access; inline; unsafe]
|
|
||||||
fn reset_state(mut s State) {
|
|
||||||
unsafe {
|
|
||||||
_ := vmemset(&s, 0, 64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@[direct_array_access; inline]
|
@[direct_array_access; inline]
|
||||||
fn clone_state(s State) State {
|
fn clone_state(s State) State {
|
||||||
mut sc := State{}
|
mut sc := State{}
|
||||||
|
@ -1,7 +1,38 @@
|
|||||||
module chacha20
|
module chacha20
|
||||||
|
|
||||||
|
import rand
|
||||||
import encoding.hex
|
import encoding.hex
|
||||||
|
|
||||||
|
// Test for Stream counter handling.
|
||||||
|
// See the discussion at [here](https://discord.com/channels/592103645835821068/592114487759470596/1417900997090607215)
|
||||||
|
fn test_stream_counter_handling() ! {
|
||||||
|
// creates a original mode of the cipher with 64-bit counter
|
||||||
|
mut ctx := new_cipher(rand.bytes(32)!, rand.bytes(8)!)!
|
||||||
|
// set the cipher's counter near the maximum of 64-bit counter
|
||||||
|
ctr := max_u64 - 2
|
||||||
|
ctx.set_counter(ctr)
|
||||||
|
|
||||||
|
// by setting internal counter into near of max 64-bit counter,
|
||||||
|
// it need a message with minimum length of 2*block_size bytes to reach the limit.
|
||||||
|
// let's build this message with 2 * block_size bytes in size
|
||||||
|
msg0 := []u8{len: 2 * block_size}
|
||||||
|
mut dst := []u8{len: msg0.len}
|
||||||
|
ctx.xor_key_stream(mut dst, msg0)
|
||||||
|
// at this step, the counter has reached the maximum_64bit_counter, but still not overflow
|
||||||
|
assert ctx.Stream.overflow == false
|
||||||
|
assert ctx.Stream.ctr() == max_64bit_counter
|
||||||
|
|
||||||
|
// after above process the counter should have at the maximum limit
|
||||||
|
// we use keystream_with_blocksize to test this counter handling, because
|
||||||
|
// xor_key_stream would panic on counter reset
|
||||||
|
msg1 := []u8{len: block_size}
|
||||||
|
ctx.Stream.keystream_with_blocksize(mut dst[..block_size], msg1) or {
|
||||||
|
assert ctx.Stream.overflow == true
|
||||||
|
assert err == error('chacha20.check_ctr: internal counter overflow')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn test_qround_on_state() {
|
fn test_qround_on_state() {
|
||||||
mut s := State{}
|
mut s := State{}
|
||||||
s[0] = 0x11111111
|
s[0] = 0x11111111
|
||||||
@ -27,7 +58,7 @@ fn test_state_of_chacha20_block_simple() ! {
|
|||||||
|
|
||||||
mut block := []u8{len: block_size}
|
mut block := []u8{len: block_size}
|
||||||
stream.set_ctr(1)
|
stream.set_ctr(1)
|
||||||
stream.keystream_with_blocksize(mut block, block)
|
stream.keystream_with_blocksize(mut block, block)!
|
||||||
|
|
||||||
expected_raw_bytes := '10f1e7e4d13b5915500fdd1fa32071c4c7d1f4c733c068030422aa9ac3d46c4ed2826446079faa0914c2d705d98b02a2b5129cd1de164eb9cbd083e8a2503c4e'
|
expected_raw_bytes := '10f1e7e4d13b5915500fdd1fa32071c4c7d1f4c733c068030422aa9ac3d46c4ed2826446079faa0914c2d705d98b02a2b5129cd1de164eb9cbd083e8a2503c4e'
|
||||||
exp_bytes := hex.decode(expected_raw_bytes)!
|
exp_bytes := hex.decode(expected_raw_bytes)!
|
||||||
@ -44,7 +75,7 @@ fn test_keystream_with_blocksize() ! {
|
|||||||
stream.set_ctr(val.counter)
|
stream.set_ctr(val.counter)
|
||||||
|
|
||||||
mut block := []u8{len: block_size}
|
mut block := []u8{len: block_size}
|
||||||
stream.keystream_with_blocksize(mut block, block)
|
stream.keystream_with_blocksize(mut block, block)!
|
||||||
exp_bytes := hex.decode(val.output)!
|
exp_bytes := hex.decode(val.output)!
|
||||||
|
|
||||||
assert block == exp_bytes
|
assert block == exp_bytes
|
||||||
|
@ -38,7 +38,7 @@ fn test_xchacha20_encrypt_vector_test_a321() ! {
|
|||||||
nonce_bytes := hex.decode(nonce)!
|
nonce_bytes := hex.decode(nonce)!
|
||||||
ciphertext_bytes := hex.decode(ciphertext)!
|
ciphertext_bytes := hex.decode(ciphertext)!
|
||||||
|
|
||||||
encrypted_message := encrypt(key_bytes, nonce_bytes, plaintext_bytes) or { return }
|
encrypted_message := encrypt(key_bytes, nonce_bytes, plaintext_bytes)!
|
||||||
|
|
||||||
assert encrypted_message == ciphertext_bytes
|
assert encrypted_message == ciphertext_bytes
|
||||||
}
|
}
|
||||||
@ -63,7 +63,7 @@ fn test_xchach20_encrypt_vector_test_a322() ! {
|
|||||||
c.set_counter(counter)
|
c.set_counter(counter)
|
||||||
|
|
||||||
mut encrypted_message := []u8{len: plaintext_bytes.len}
|
mut encrypted_message := []u8{len: plaintext_bytes.len}
|
||||||
c.encrypt(mut encrypted_message, plaintext_bytes)
|
c.encrypt(mut encrypted_message, plaintext_bytes)!
|
||||||
|
|
||||||
assert encrypted_message == ciphertext_bytes
|
assert encrypted_message == ciphertext_bytes
|
||||||
}
|
}
|
||||||
|
@ -127,13 +127,13 @@ fn (c Chacha20Poly1305) encrypt_generic(plaintext []u8, nonce []u8, ad []u8) ![]
|
|||||||
// see https://datatracker.ietf.org/doc/html/rfc8439#section-2.6
|
// see https://datatracker.ietf.org/doc/html/rfc8439#section-2.6
|
||||||
mut polykey := []u8{len: key_size}
|
mut polykey := []u8{len: key_size}
|
||||||
mut s := chacha20.new_cipher(c.key, nonce)!
|
mut s := chacha20.new_cipher(c.key, nonce)!
|
||||||
s.encrypt(mut polykey, polykey)
|
s.encrypt(mut polykey, polykey)!
|
||||||
|
|
||||||
// Next, the ChaCha20 encryption function is called to encrypt the plaintext,
|
// Next, the ChaCha20 encryption function is called to encrypt the plaintext,
|
||||||
// using the same key and nonce, and with the initial ChaCha20 counter set to 1.
|
// using the same key and nonce, and with the initial ChaCha20 counter set to 1.
|
||||||
mut ciphertext := []u8{len: plaintext.len}
|
mut ciphertext := []u8{len: plaintext.len}
|
||||||
s.set_counter(1)
|
s.set_counter(1)
|
||||||
s.encrypt(mut ciphertext, plaintext)
|
s.encrypt(mut ciphertext, plaintext)!
|
||||||
|
|
||||||
// Finally, the Poly1305 function is called with the generated Poly1305 one-time key
|
// Finally, the Poly1305 function is called with the generated Poly1305 one-time key
|
||||||
// calculated above, and a message constructed as described in
|
// calculated above, and a message constructed as described in
|
||||||
@ -177,7 +177,7 @@ fn (c Chacha20Poly1305) decrypt_generic(ciphertext []u8, nonce []u8, ad []u8) ![
|
|||||||
// generates poly1305 one-time key for later calculation
|
// generates poly1305 one-time key for later calculation
|
||||||
mut polykey := []u8{len: key_size}
|
mut polykey := []u8{len: key_size}
|
||||||
mut s := chacha20.new_cipher(c.key, nonce)!
|
mut s := chacha20.new_cipher(c.key, nonce)!
|
||||||
s.encrypt(mut polykey, polykey)
|
s.encrypt(mut polykey, polykey)!
|
||||||
|
|
||||||
// Remember, ciphertext is concatenation of associated cipher output plus tag (mac) bytes
|
// Remember, ciphertext is concatenation of associated cipher output plus tag (mac) bytes
|
||||||
encrypted := ciphertext[0..ciphertext.len - c.overhead()]
|
encrypted := ciphertext[0..ciphertext.len - c.overhead()]
|
||||||
@ -186,7 +186,7 @@ fn (c Chacha20Poly1305) decrypt_generic(ciphertext []u8, nonce []u8, ad []u8) ![
|
|||||||
mut plaintext := []u8{len: encrypted.len}
|
mut plaintext := []u8{len: encrypted.len}
|
||||||
s.set_counter(1)
|
s.set_counter(1)
|
||||||
// doing reverse encrypt on cipher output part produces plaintext
|
// doing reverse encrypt on cipher output part produces plaintext
|
||||||
s.encrypt(mut plaintext, encrypted)
|
s.encrypt(mut plaintext, encrypted)!
|
||||||
|
|
||||||
// authenticated messages part
|
// authenticated messages part
|
||||||
mut constructed_msg := []u8{}
|
mut constructed_msg := []u8{}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user