authorgravatar for david@vortan.devDavid Rubin <david@vortan.dev> 2026-03-22 06:55:03-07:00
committergravatar for david@vortan.devDavid Rubin <david@vortan.dev> 2026-03-22 07:21:41-07:00
log8efd539305f9beea2da940d9e174a5a38f9c7edf
tree88a655244d4cf8b6d10d2dafb6cbdac0dff0afea
parentccf8e223f4ba8ae2b0ae1128274ee963a97c7484
signaturelock-open Commit is signed but in an unrecognized format.

crypto: correct aes-siv s2v

The first issue is that when len(Sn) >= 128, we perform Sn xor D instead of the Sn xorend D that is specified in RFC 5297. The second issue is that we truncate the Sn if it is larger than 4096 bytes, which could lead to collisions between inputs. We solve this by absoring the Sn into the CMAC state perform the last 16 bytes, xoring those 16 bytes with D as described in the first issue, and then updating and squeezing the CMAC.

1 files changed, 53 insertions(+), 8 deletions(-)

lib/std/crypto/aes_siv.zig+53-8
...@@ -88,16 +88,19 @@ fn AesSiv(comptime Aes: anytype) type {...@@ -88,16 +88,19 @@ fn AesSiv(comptime Aes: anytype) type {
88 // Process the final string88 // Process the final string
89 const sn = strings[strings.len - 1];89 const sn = strings[strings.len - 1];
90 if (sn.len >= 16) {90 if (sn.len >= 16) {
91 // XOR d with the first 16 bytes of Sn91 // XOR d with the last 16 bytes of Sn,
92 var xored_msg_buf: [4096]u8 = undefined;92 // and give the entire Sn to CMAC incrementally.
93 const xored_len = @min(sn.len, xored_msg_buf.len);93 var cmac = CmacImpl.init(&key);
94 @memcpy(xored_msg_buf[0..xored_len], sn[0..xored_len]);94 const prefix = sn.len - 16;
9595 cmac.update(sn[0..prefix]);
96 for (d, 0..) |b, j| {96
97 xored_msg_buf[j] ^= b;97 var tail: [16]u8 = undefined;
98 for (&tail, sn[prefix..][0..16], d) |*out, s, db| {
99 out.* = s ^ db;
98 }100 }
101 cmac.update(&tail);
99102
100 CmacImpl.create(iv, xored_msg_buf[0..xored_len], &key);103 cmac.final(iv);
101 } else {104 } else {
102 // Pad and XOR105 // Pad and XOR
103 d = dbl(d);106 d = dbl(d);
...@@ -355,6 +358,48 @@ test "Aes128Siv - RFC 5297 Test Vector A.1" {...@@ -355,6 +358,48 @@ test "Aes128Siv - RFC 5297 Test Vector A.1" {
355 try testing.expectEqualSlices(u8, &plaintext, &decrypted);358 try testing.expectEqualSlices(u8, &plaintext, &decrypted);
356}359}
357360
361test "Aes128Siv - RFC 5297 Test Vector A.2" {
362 // Test vector from RFC 5297 Appendix A.2
363 const key: [32]u8 = .{
364 0x7f, 0x7e, 0x7d, 0x7c, 0x7b, 0x7a, 0x79, 0x78,
365 0x77, 0x76, 0x75, 0x74, 0x73, 0x72, 0x71, 0x70,
366 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
367 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
368 };
369 const ad1 = [_]u8{
370 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
371 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
372 0xde, 0xad, 0xda, 0xda, 0xde, 0xad, 0xda, 0xda,
373 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
374 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00,
375 };
376 const ad2 = [_]u8{
377 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80,
378 0x90, 0xa0,
379 };
380 const nonce: [16]u8 = .{
381 0x09, 0xf9, 0x11, 0x02, 0x9d, 0x74, 0xe3, 0x5b,
382 0xd8, 0x41, 0x56, 0xc5, 0x63, 0x56, 0x88, 0xc0,
383 };
384 const plaintext = [_]u8{
385 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20,
386 0x73, 0x6f, 0x6d, 0x65, 0x20, 0x70, 0x6c, 0x61,
387 0x69, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x20, 0x74,
388 0x6f, 0x20, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70,
389 0x74, 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20,
390 0x53, 0x49, 0x56, 0x2d, 0x41, 0x45, 0x53,
391 };
392
393 var ciphertext: [plaintext.len]u8 = undefined;
394 var tag: [16]u8 = undefined;
395
396 Aes128Siv.encryptWithAdVector(&ciphertext, &tag, &plaintext, &.{ &ad1, &ad2, &nonce }, key);
397
398 // Expected values from RFC 5297
399 try htest.assertEqual("7bdb6e3b432667eb06f4d14bff2fbd0f", &tag);
400 try htest.assertEqual("cb900f2fddbe404326601965c889bf17dba77ceb094fa663b7a3f748ba8af829ea64ad544a272e9c485b62a3fd5c0d", &ciphertext);
401}
402
358test "Aes128Siv - empty plaintext" {403test "Aes128Siv - empty plaintext" {
359 const key: [32]u8 = @splat(0x42);404 const key: [32]u8 = @splat(0x42);
360 const plaintext = "";405 const plaintext = "";