authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2023-05-23 16:36:44+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-05-23 14:36:44+00:00
log057d30bacc4d97f53c2e69585dc8d50186cfc7f5
tree0611d5cf2910f3f90707b71dbdac044d75ac030b
parent7cb3a6750762ffc6a17a76a0924079cbf4613a82
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

std.crypto.chacha: remove the hack for ChaCha with a 64-bit counter (#15818)

Support for 64-bit counters was a hack built upon the version with a 32-bit counter, that emulated a larger counter by splitting the input into large blocks. This is fragile, particularily if the initial counter is set to a non-default value and if we have parallelism. Simply add a comptime parameter to check if we have a 32 bit or a 64 bit counter instead. Also convert a couple while() loops to for(), and change @panic() to @compileError().

1 files changed, 68 insertions(+), 68 deletions(-)

lib/std/crypto/chacha20.zig+68-68
......@@ -109,14 +109,18 @@ fn ChaChaVecImpl(comptime rounds_nb: usize, comptime degree: comptime_int) type
109109 mem.readIntLittle(u32, c[8..12]),
110110 mem.readIntLittle(u32, c[12..16]),
111111 };
112 const n1 = @addWithOverflow(d[0], 1);
112113 return BlockVec{
113114 constant_le,
114115 Lane{ key[0], key[1], key[2], key[3], key[0], key[1], key[2], key[3] },
115116 Lane{ key[4], key[5], key[6], key[7], key[4], key[5], key[6], key[7] },
116 Lane{ d[0], d[1], d[2], d[3], d[0] +% 1, d[1], d[2], d[3] },
117 Lane{ d[0], d[1], d[2], d[3], n1[0], d[1] +% n1[1], d[2], d[3] },
117118 };
118119 },
119120 4 => {
121 const n1 = @addWithOverflow(d[0], 1);
122 const n2 = @addWithOverflow(d[0], 2);
123 const n3 = @addWithOverflow(d[0], 3);
120124 const constant_le = Lane{
121125 mem.readIntLittle(u32, c[0..4]),
122126 mem.readIntLittle(u32, c[4..8]),
......@@ -139,10 +143,10 @@ fn ChaChaVecImpl(comptime rounds_nb: usize, comptime degree: comptime_int) type
139143 constant_le,
140144 Lane{ key[0], key[1], key[2], key[3], key[0], key[1], key[2], key[3], key[0], key[1], key[2], key[3], key[0], key[1], key[2], key[3] },
141145 Lane{ key[4], key[5], key[6], key[7], key[4], key[5], key[6], key[7], key[4], key[5], key[6], key[7], key[4], key[5], key[6], key[7] },
142 Lane{ d[0], d[1], d[2], d[3], d[0] +% 1, d[1], d[2], d[3], d[0] +% 2, d[1], d[2], d[3], d[0] +% 3, d[1], d[2], d[3] },
146 Lane{ d[0], d[1], d[2], d[3], n1[0], d[1] +% n1[1], d[2], d[3], n2[0], d[1] +% n2[1], d[2], d[3], n3[0], d[1] +% n3[1], d[2], d[3] },
143147 };
144148 },
145 else => @panic("invalid degree"),
149 else => @compileError("invalid degree"),
146150 }
147151 }
148152
......@@ -153,19 +157,19 @@ fn ChaChaVecImpl(comptime rounds_nb: usize, comptime degree: comptime_int) type
153157 1 => [_]i32{ 3, 0, 1, 2 },
154158 2 => [_]i32{ 3, 0, 1, 2 } ++ [_]i32{ 7, 4, 5, 6 },
155159 4 => [_]i32{ 3, 0, 1, 2 } ++ [_]i32{ 7, 4, 5, 6 } ++ [_]i32{ 11, 8, 9, 10 } ++ [_]i32{ 15, 12, 13, 14 },
156 else => @panic("invalid degree"),
160 else => @compileError("invalid degree"),
157161 };
158162 const m1 = switch (degree) {
159163 1 => [_]i32{ 2, 3, 0, 1 },
160164 2 => [_]i32{ 2, 3, 0, 1 } ++ [_]i32{ 6, 7, 4, 5 },
161165 4 => [_]i32{ 2, 3, 0, 1 } ++ [_]i32{ 6, 7, 4, 5 } ++ [_]i32{ 10, 11, 8, 9 } ++ [_]i32{ 14, 15, 12, 13 },
162 else => @panic("invalid degree"),
166 else => @compileError("invalid degree"),
163167 };
164168 const m2 = switch (degree) {
165169 1 => [_]i32{ 1, 2, 3, 0 },
166170 2 => [_]i32{ 1, 2, 3, 0 } ++ [_]i32{ 5, 6, 7, 4 },
167171 4 => [_]i32{ 1, 2, 3, 0 } ++ [_]i32{ 5, 6, 7, 4 } ++ [_]i32{ 9, 10, 11, 8 } ++ [_]i32{ 13, 14, 15, 12 },
168 else => @panic("invalid degree"),
172 else => @compileError("invalid degree"),
169173 };
170174
171175 var r: usize = 0;
......@@ -212,8 +216,7 @@ fn ChaChaVecImpl(comptime rounds_nb: usize, comptime degree: comptime_int) type
212216
213217 inline fn hashToBytes(comptime dm: usize, out: *[64 * dm]u8, x: BlockVec) void {
214218 for (0..dm) |d| {
215 var i: usize = 0;
216 while (i < 4) : (i += 1) {
219 for (0..4) |i| {
217220 mem.writeIntLittle(u32, out[64 * d + 16 * i + 0 ..][0..4], x[i][0 + 4 * d]);
218221 mem.writeIntLittle(u32, out[64 * d + 16 * i + 4 ..][0..4], x[i][1 + 4 * d]);
219222 mem.writeIntLittle(u32, out[64 * d + 16 * i + 8 ..][0..4], x[i][2 + 4 * d]);
......@@ -229,8 +232,8 @@ fn ChaChaVecImpl(comptime rounds_nb: usize, comptime degree: comptime_int) type
229232 x[3] +%= ctx[3];
230233 }
231234
232 fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void {
233 var ctx = initContext(key, counter);
235 fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, nonce_and_counter: [4]u32, comptime count64: bool) void {
236 var ctx = initContext(key, nonce_and_counter);
234237 var x: BlockVec = undefined;
235238 var buf: [64 * degree]u8 = undefined;
236239 var i: usize = 0;
......@@ -242,16 +245,20 @@ fn ChaChaVecImpl(comptime rounds_nb: usize, comptime degree: comptime_int) type
242245
243246 var xout = out[i..];
244247 const xin = in[i..];
245 var j: usize = 0;
246 while (j < 64 * d) : (j += 1) {
248 for (0..64 * d) |j| {
247249 xout[j] = xin[j];
248250 }
249 j = 0;
250 while (j < 64 * d) : (j += 1) {
251 for (0..64 * d) |j| {
251252 xout[j] ^= buf[j];
252253 }
253254 inline for (0..d) |d_| {
254 ctx[3][4 * d_] += @intCast(u32, d);
255 if (count64) {
256 const next = @addWithOverflow(ctx[3][4 * d_], d);
257 ctx[3][4 * d_] = next[0];
258 ctx[3][4 * d_ + 1] +%= next[1];
259 } else {
260 ctx[3][4 * d_] +%= d;
261 }
255262 }
256263 }
257264 }
......@@ -262,15 +269,14 @@ fn ChaChaVecImpl(comptime rounds_nb: usize, comptime degree: comptime_int) type
262269
263270 var xout = out[i..];
264271 const xin = in[i..];
265 var j: usize = 0;
266 while (j < in.len % 64) : (j += 1) {
272 for (0..in.len % 64) |j| {
267273 xout[j] = xin[j] ^ buf[j];
268274 }
269275 }
270276 }
271277
272 fn chacha20Stream(out: []u8, key: [8]u32, counter: [4]u32) void {
273 var ctx = initContext(key, counter);
278 fn chacha20Stream(out: []u8, key: [8]u32, nonce_and_counter: [4]u32, comptime count64: bool) void {
279 var ctx = initContext(key, nonce_and_counter);
274280 var x: BlockVec = undefined;
275281 var i: usize = 0;
276282 inline for ([_]comptime_int{ 4, 2, 1 }) |d| {
......@@ -279,7 +285,13 @@ fn ChaChaVecImpl(comptime rounds_nb: usize, comptime degree: comptime_int) type
279285 contextFeedback(&x, ctx);
280286 hashToBytes(d, out[i..][0 .. 64 * d], x);
281287 inline for (0..d) |d_| {
282 ctx[3][4 * d_] += @intCast(u32, d);
288 if (count64) {
289 const next = @addWithOverflow(ctx[3][4 * d_], d);
290 ctx[3][4 * d_] = next[0];
291 ctx[3][4 * d_ + 1] +%= next[1];
292 } else {
293 ctx[3][4 * d_] +%= d;
294 }
283295 }
284296 }
285297 }
......@@ -382,8 +394,7 @@ fn ChaChaNonVecImpl(comptime rounds_nb: usize) type {
382394 }
383395
384396 inline fn hashToBytes(out: *[64]u8, x: BlockVec) void {
385 var i: usize = 0;
386 while (i < 4) : (i += 1) {
397 for (0..4) |i| {
387398 mem.writeIntLittle(u32, out[16 * i + 0 ..][0..4], x[i * 4 + 0]);
388399 mem.writeIntLittle(u32, out[16 * i + 4 ..][0..4], x[i * 4 + 1]);
389400 mem.writeIntLittle(u32, out[16 * i + 8 ..][0..4], x[i * 4 + 2]);
......@@ -392,14 +403,13 @@ fn ChaChaNonVecImpl(comptime rounds_nb: usize) type {
392403 }
393404
394405 inline fn contextFeedback(x: *BlockVec, ctx: BlockVec) void {
395 var i: usize = 0;
396 while (i < 16) : (i += 1) {
406 for (0..16) |i| {
397407 x[i] +%= ctx[i];
398408 }
399409 }
400410
401 fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void {
402 var ctx = initContext(key, counter);
411 fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, nonce_and_counter: [4]u32, comptime count64: bool) void {
412 var ctx = initContext(key, nonce_and_counter);
403413 var x: BlockVec = undefined;
404414 var buf: [64]u8 = undefined;
405415 var i: usize = 0;
......@@ -410,15 +420,19 @@ fn ChaChaNonVecImpl(comptime rounds_nb: usize) type {
410420
411421 var xout = out[i..];
412422 const xin = in[i..];
413 var j: usize = 0;
414 while (j < 64) : (j += 1) {
423 for (0..64) |j| {
415424 xout[j] = xin[j];
416425 }
417 j = 0;
418 while (j < 64) : (j += 1) {
426 for (0..64) |j| {
419427 xout[j] ^= buf[j];
420428 }
421 ctx[12] += 1;
429 if (count64) {
430 const next = @addWithOverflow(ctx[12], 1);
431 ctx[12] = next[0];
432 ctx[13] +%= next[1];
433 } else {
434 ctx[12] +%= 1;
435 }
422436 }
423437 if (i < in.len) {
424438 chacha20Core(x[0..], ctx);
......@@ -427,22 +441,27 @@ fn ChaChaNonVecImpl(comptime rounds_nb: usize) type {
427441
428442 var xout = out[i..];
429443 const xin = in[i..];
430 var j: usize = 0;
431 while (j < in.len % 64) : (j += 1) {
444 for (0..in.len % 64) |j| {
432445 xout[j] = xin[j] ^ buf[j];
433446 }
434447 }
435448 }
436449
437 fn chacha20Stream(out: []u8, key: [8]u32, counter: [4]u32) void {
438 var ctx = initContext(key, counter);
450 fn chacha20Stream(out: []u8, key: [8]u32, nonce_and_counter: [4]u32, comptime count64: bool) void {
451 var ctx = initContext(key, nonce_and_counter);
439452 var x: BlockVec = undefined;
440453 var i: usize = 0;
441454 while (i + 64 <= out.len) : (i += 64) {
442455 chacha20Core(x[0..], ctx);
443456 contextFeedback(&x, ctx);
444457 hashToBytes(out[i..][0..64], x);
445 ctx[12] += 1;
458 if (count64) {
459 const next = @addWithOverflow(ctx[12], 1);
460 ctx[12] = next[0];
461 ctx[13] +%= next[1];
462 } else {
463 ctx[12] +%= 1;
464 }
446465 }
447466 if (i < out.len) {
448467 chacha20Core(x[0..], ctx);
......@@ -496,8 +515,7 @@ fn ChaChaImpl(comptime rounds_nb: usize) type {
496515
497516fn keyToWords(key: [32]u8) [8]u32 {
498517 var k: [8]u32 = undefined;
499 var i: usize = 0;
500 while (i < 8) : (i += 1) {
518 for (0..8) |i| {
501519 k[i] = mem.readIntLittle(u32, key[i * 4 ..][0..4]);
502520 }
503521 return k;
......@@ -527,26 +545,26 @@ fn ChaChaIETF(comptime rounds_nb: usize) type {
527545 /// Using the AEAD or one of the `box` versions is usually preferred.
528546 pub fn xor(out: []u8, in: []const u8, counter: u32, key: [key_length]u8, nonce: [nonce_length]u8) void {
529547 assert(in.len == out.len);
530 assert(in.len / 64 <= (1 << 32 - 1) - counter);
548 assert(in.len <= 64 * (@as(u39, 1 << 32) - counter));
531549
532550 var d: [4]u32 = undefined;
533551 d[0] = counter;
534552 d[1] = mem.readIntLittle(u32, nonce[0..4]);
535553 d[2] = mem.readIntLittle(u32, nonce[4..8]);
536554 d[3] = mem.readIntLittle(u32, nonce[8..12]);
537 ChaChaImpl(rounds_nb).chacha20Xor(out, in, keyToWords(key), d);
555 ChaChaImpl(rounds_nb).chacha20Xor(out, in, keyToWords(key), d, false);
538556 }
539557
540558 /// Write the output of the ChaCha20 stream cipher into `out`.
541559 pub fn stream(out: []u8, counter: u32, key: [key_length]u8, nonce: [nonce_length]u8) void {
542 assert(out.len / 64 <= (1 << 32 - 1) - counter);
560 assert(out.len <= 64 * (@as(u39, 1 << 32) - counter));
543561
544562 var d: [4]u32 = undefined;
545563 d[0] = counter;
546564 d[1] = mem.readIntLittle(u32, nonce[0..4]);
547565 d[2] = mem.readIntLittle(u32, nonce[4..8]);
548566 d[3] = mem.readIntLittle(u32, nonce[8..12]);
549 ChaChaImpl(rounds_nb).chacha20Stream(out, keyToWords(key), d);
567 ChaChaImpl(rounds_nb).chacha20Stream(out, keyToWords(key), d, false);
550568 }
551569 };
552570}
......@@ -565,47 +583,28 @@ fn ChaChaWith64BitNonce(comptime rounds_nb: usize) type {
565583 /// Using the AEAD or one of the `box` versions is usually preferred.
566584 pub fn xor(out: []u8, in: []const u8, counter: u64, key: [key_length]u8, nonce: [nonce_length]u8) void {
567585 assert(in.len == out.len);
568 assert(in.len / 64 <= (1 << 64 - 1) - counter);
586 assert(in.len <= 64 * (@as(u71, 1 << 64) - counter));
569587
570 var cursor: usize = 0;
571588 const k = keyToWords(key);
572589 var c: [4]u32 = undefined;
573590 c[0] = @truncate(u32, counter);
574591 c[1] = @truncate(u32, counter >> 32);
575592 c[2] = mem.readIntLittle(u32, nonce[0..4]);
576593 c[3] = mem.readIntLittle(u32, nonce[4..8]);
577
578 // The full block size is greater than the address space on a 32bit machine
579 const big_block = if (@sizeOf(usize) > 4) (block_length << 32) else maxInt(usize);
580
581 // first partial big block
582 if (((@intCast(u64, maxInt(u32) - @truncate(u32, counter)) + 1) << 6) < in.len) {
583 ChaChaImpl(rounds_nb).chacha20Xor(out[cursor..big_block], in[cursor..big_block], k, c);
584 cursor = big_block - cursor;
585 c[1] += 1;
586 if (comptime @sizeOf(usize) > 4) {
587 // A big block is giant: 256 GiB, but we can avoid this limitation
588 var remaining_blocks: u32 = @intCast(u32, (in.len / big_block));
589 while (remaining_blocks > 0) : (remaining_blocks -= 1) {
590 ChaChaImpl(rounds_nb).chacha20Xor(out[cursor .. cursor + big_block], in[cursor .. cursor + big_block], k, c);
591 c[1] += 1; // upper 32-bit of counter, generic chacha20Xor() doesn't know about this.
592 cursor += big_block;
593 }
594 }
595 }
596 ChaChaImpl(rounds_nb).chacha20Xor(out[cursor..], in[cursor..], k, c);
594 ChaChaImpl(rounds_nb).chacha20Xor(out, in, k, c, true);
597595 }
598596
599597 /// Write the output of the ChaCha20 stream cipher into `out`.
600598 pub fn stream(out: []u8, counter: u32, key: [key_length]u8, nonce: [nonce_length]u8) void {
601 assert(out.len / 64 <= (1 << 32 - 1) - counter);
599 assert(out.len <= 64 * (@as(u71, 1 << 64) - counter));
600
602601 const k = keyToWords(key);
603602 var c: [4]u32 = undefined;
604603 c[0] = @truncate(u32, counter);
605604 c[1] = @truncate(u32, counter >> 32);
606605 c[2] = mem.readIntLittle(u32, nonce[0..4]);
607606 c[3] = mem.readIntLittle(u32, nonce[4..8]);
608 ChaChaImpl(rounds_nb).chacha20Stream(out, k, c);
607 ChaChaImpl(rounds_nb).chacha20Stream(out, k, c, true);
609608 }
610609 };
611610}
......@@ -649,6 +648,7 @@ fn ChaChaPoly1305(comptime rounds_nb: usize) type {
649648 /// k: private key
650649 pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
651650 assert(c.len == m.len);
651 assert(m.len <= 64 * (@as(u39, 1 << 32) - 1));
652652
653653 var polyKey = [_]u8{0} ** 32;
654654 ChaChaIETF(rounds_nb).xor(polyKey[0..], polyKey[0..], 0, k, npub);
......@@ -766,7 +766,7 @@ test "chacha20 AEAD API" {
766766 aead.encrypt(c[0..], tag[0..], m, ad, nonce, key);
767767 try aead.decrypt(out[0..], c[0..], tag, ad[0..], nonce, key);
768768 try testing.expectEqualSlices(u8, out[0..], m);
769 c[0] += 1;
769 c[0] +%= 1;
770770 try testing.expectError(error.AuthenticationFailed, aead.decrypt(out[0..], c[0..], tag, ad[0..], nonce, key));
771771 }
772772}
......@@ -1154,7 +1154,7 @@ test "crypto.xchacha20" {
11541154 var buf: [2 * c.len]u8 = undefined;
11551155 try testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&c)}), "994D2DD32333F48E53650C02C7A2ABB8E018B0836D7175AEC779F52E961780768F815C58F1AA52D211498DB89B9216763F569C9433A6BBFCEFB4D4A49387A4C5207FBB3B5A92B5941294DF30588C6740D39DC16FA1F0E634F7246CF7CDCB978E44347D89381B7A74EB7084F754B90BDE9AAF5A94B8F2A85EFD0B50692AE2D425E234");
11561156 try testing.expectEqualSlices(u8, out[0..], m);
1157 c[0] += 1;
1157 c[0] +%= 1;
11581158 try testing.expectError(error.AuthenticationFailed, XChaCha20Poly1305.decrypt(out[0..], c[0..m.len], c[m.len..].*, ad, nonce, key));
11591159 }
11601160}