Skip to content

Commit bf41714

Browse files
authored
x.crypto.chacha20: makes 64-bit counter cipher work too (#25363)
1 parent cd15216 commit bf41714

File tree

4 files changed

+172
-171
lines changed

4 files changed

+172
-171
lines changed

vlib/x/crypto/chacha20/bench/bench.v

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@
1919
// -----------
2020
// Iterations: 10000 Total Duration: 48.242ms ns/op: 4824 B/op: 3 allocs/op: 4
2121
//
22+
// Chacha20 old xor_key_stream_backup
23+
// -----------
24+
// Iterations: 10000 Total Duration: 53.430ms ns/op: 5343 B/op: 11 allocs/op: 12
25+
// ChaCha20 new xor_key_stream
26+
// -----------
27+
// Iterations: 10000 Total Duration: 43.668ms ns/op: 4366 B/op: 0 allocs/op: 1
28+
//
2229
import x.benchmark
2330
import encoding.hex
2431
import x.crypto.chacha20
@@ -40,6 +47,12 @@ fn bench_chacha20_decrypt() ! {
4047
_ := chacha20.decrypt(key, nonce, ciphertext)!
4148
}
4249

50+
fn bench_chacha20_xor_key_stream() ! {
51+
mut dst := []u8{len: plaintext.len}
52+
mut cs := chacha20.new_cipher(key, nonce)!
53+
cs.xor_key_stream(mut dst, plaintext)
54+
}
55+
4356
fn main() {
4457
cf := benchmark.BenchmarkDefaults{
4558
n: 10000
@@ -49,9 +62,13 @@ fn main() {
4962
mut b0 := benchmark.setup(bench_chacha20_encrypt, cf)!
5063
b0.run()
5164

52-
println('')
5365
println('ChaCha20 Decryption')
5466
println('-----------')
5567
mut b1 := benchmark.setup(bench_chacha20_decrypt, cf)!
5668
b1.run()
69+
70+
println('ChaCha20 new xor_key_stream')
71+
println('-----------')
72+
mut b3 := benchmark.setup(bench_chacha20_xor_key_stream, cf)!
73+
b3.run()
5774
}

vlib/x/crypto/chacha20/chacha.v

Lines changed: 40 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -91,62 +91,60 @@ pub fn (mut c Cipher) xor_key_stream(mut dst []u8, src []u8) {
9191
if dst.len < src.len {
9292
panic('chacha20/chacha: dst buffer is to small')
9393
}
94-
95-
mut idx := 0
96-
mut src_len := src.len
97-
98-
dst = unsafe { dst[..src_len] }
94+
dst = unsafe { dst[..src.len] }
9995
if subtle.inexact_overlap(dst, src) {
10096
panic('chacha20: invalid buffer overlap')
10197
}
98+
// index of position within src bytes
99+
mut idx := 0
102100

103-
// We adapt and ports the go version here
104-
// First, drain any remaining key stream
101+
// First, try to drain any remaining key stream from internal buffer
105102
if c.length != 0 {
106103
// remaining keystream on internal buffer
107104
mut kstream := c.block[block_size - c.length..]
108-
if src_len < kstream.len {
109-
kstream = unsafe { kstream[..src_len] }
105+
if src.len < kstream.len {
106+
kstream = unsafe { kstream[..src.len] }
110107
}
108+
// xors every bytes in src with bytes from key stream and stored into dst
111109
for i, b in kstream {
112110
dst[idx + i] = src[idx + i] ^ b
113111
}
114-
// updates the idx for dst and src
112+
// updates position and internal buffer length.
113+
// when c.length reaches the block_size, we reset it for future use.
115114
c.length -= kstream.len
116115
idx += kstream.len
117-
src_len -= kstream.len
116+
if c.length == block_size {
117+
unsafe { c.block.reset() }
118+
c.length = 0
119+
}
118120
}
119-
120-
// take the most full bytes of multiples block_size from the src,
121-
// build the keystream from the cipher's state and stores the result
122-
// into dst
123-
full := src_len - src_len % block_size
124-
if full > 0 {
125-
src_block := unsafe { src[idx..idx + full] }
126-
c.Stream.keystream_with_blocksize(mut dst[idx..idx + full], src_block) or {
127-
c.Stream.overflow = true
128-
panic('chacha20: xor_key_stream leads to counter overflow')
121+
// process for remaining unprocessed src bytes
122+
mut remains := unsafe { src[idx..] }
123+
nr_blocks := remains.len / block_size
124+
125+
// process for full block_size-d message
126+
for i := 0; i < nr_blocks; i++ {
127+
// for every block_sized message, we generates 64-bytes block key stream
128+
// and then xor-ing this block with generated key stream
129+
block := unsafe { remains[i * block_size..(i + 1) * block_size] }
130+
ks := c.keystream() or { panic(err) }
131+
for j, b in ks {
132+
dst[idx + j] = block[j] ^ b
129133
}
134+
// updates position
135+
idx += block_size
130136
}
131-
idx += full
132-
src_len -= full
133-
134-
// If we have a partial block, pad it for keystream_with_blocksize, and
135-
// keep the leftover keystream for the next invocation.
136-
if src_len > 0 {
137-
// Make sure, internal buffer cleared or the old garbaged data from previous call still there
138-
// See the issue at https://github.com/vlang/v/issues/24043
139-
unsafe { c.block.reset() } // = []u8{len: block_size}
140-
// copy the last src block to internal buffer, and performs
141-
// keystream_with_blocksize on this buffer, and stores into remaining dst
142-
_ := copy(mut c.block, src[idx..])
143-
c.Stream.keystream_with_blocksize(mut c.block, c.block) or {
144-
c.Stream.overflow = true
145-
panic('chacha20: xor_key_stream leads to counter overflow')
137+
138+
// process for remaining partial block
139+
if remains.len % block_size != 0 {
140+
last_block := unsafe { remains[nr_blocks * block_size..] }
141+
// generates one 64-bytes keystream block
142+
c.block = c.keystream() or { panic(err) }
143+
for i, b in last_block {
144+
dst[idx + i] = b ^ c.block[i]
146145
}
147-
n := copy(mut dst[idx..], c.block)
148-
// the length of remaining bytes of unprocessed keystream
149-
c.length = block_size - n
146+
c.length = block_size - last_block.len
147+
idx += last_block.len
150148
}
151149
}
152150

@@ -181,7 +179,9 @@ pub fn (mut c Cipher) free() {
181179
}
182180
}
183181

184-
// reset quickly sets all Cipher's fields to default value
182+
// reset quickly sets all Cipher's fields to default value.
183+
// This method will be deprecated.
184+
@[deprecated_after: '2025-11-30']
185185
@[unsafe]
186186
pub fn (mut c Cipher) reset() {
187187
c.Stream.reset()

0 commit comments

Comments
 (0)