From f73e891c06907e4157509b83f599febd8ed2df7c Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Wed, 11 Dec 2024 11:57:24 +0100 Subject: [PATCH] Reinstantiates AEGIS-MAC with the final construction This reverts commit c9d6f8b5058ba0df3bf281a3be3a3570c2219754. --- lib/std/crypto.zig | 11 ++ lib/std/crypto/aegis.zig | 241 ++++++++++++++++++++++++++++++++--- lib/std/crypto/benchmark.zig | 4 + 3 files changed, 235 insertions(+), 21 deletions(-) diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index 921b7113e44a..7b167a467a30 100644 --- a/lib/std/crypto.zig +++ b/lib/std/crypto.zig @@ -58,9 +58,20 @@ pub const auth = struct { pub const siphash = @import("crypto/siphash.zig"); pub const aegis = struct { const variants = @import("crypto/aegis.zig"); + pub const Aegis128X4Mac = variants.Aegis128X4Mac; + pub const Aegis128X2Mac = variants.Aegis128X2Mac; pub const Aegis128LMac = variants.Aegis128LMac; + + pub const Aegis256X4Mac = variants.Aegis256X4Mac; + pub const Aegis256X2Mac = variants.Aegis256X2Mac; pub const Aegis256Mac = variants.Aegis256Mac; + + pub const Aegis128X4Mac_128 = variants.Aegis128X4Mac_128; + pub const Aegis128X2Mac_128 = variants.Aegis128X2Mac_128; pub const Aegis128LMac_128 = variants.Aegis128LMac_128; + + pub const Aegis256X4Mac_128 = variants.Aegis256X4Mac_128; + pub const Aegis256X2Mac_128 = variants.Aegis256X2Mac_128; pub const Aegis256Mac_128 = variants.Aegis256Mac_128; }; pub const cmac = @import("crypto/cmac.zig"); diff --git a/lib/std/crypto/aegis.zig b/lib/std/crypto/aegis.zig index c695bc8001ff..84a33557f3a6 100644 --- a/lib/std/crypto/aegis.zig +++ b/lib/std/crypto/aegis.zig @@ -161,7 +161,7 @@ fn State128X(comptime degree: u7) type { state.update(msg0, msg1); } - fn mac(state: *State, comptime tag_bits: u9, adlen: usize, mlen: usize) [tag_bits / 8]u8 { + fn finalize(state: *State, comptime tag_bits: u9, adlen: usize, mlen: usize) [tag_bits / 8]u8 { const blocks = &state.blocks; var sizes: [aes_block_length]u8 = undefined; mem.writeInt(u64, sizes[0..8], @as(u64, adlen) * 8, .little); @@ -200,6 +200,59 @@ fn State128X(comptime degree: u7) type { else => unreachable, } } + + fn finalizeMac(state: *State, comptime tag_bits: u9, datalen: usize) [tag_bits / 8]u8 { + const blocks = &state.blocks; + var sizes: [aes_block_length]u8 = undefined; + mem.writeInt(u64, sizes[0..8], @as(u64, datalen) * 8, .little); + mem.writeInt(u64, sizes[8..16], tag_bits, .little); + for (1..degree) |i| { + @memcpy(sizes[i * 16 ..][0..16], sizes[0..16]); + } + var t = blocks[2].xorBlocks(AesBlockVec.fromBytes(&sizes)); + for (0..7) |_| { + state.update(t, t); + } + if (degree > 1) { + var v = [_]u8{0} ** rate; + switch (tag_bits) { + 128 => { + const tags = blocks[0].xorBlocks(blocks[1]).xorBlocks(blocks[2]).xorBlocks(blocks[3]).xorBlocks(blocks[4]).xorBlocks(blocks[5]).xorBlocks(blocks[6]).toBytes(); + for (0..degree / 2) |d| { + v[0..32].* = tags[d * 32 ..][0..32].*; + state.absorb(&v); + } + }, + 256 => { + const tags_0 = blocks[0].xorBlocks(blocks[1]).xorBlocks(blocks[2]).xorBlocks(blocks[3]).toBytes(); + const tags_1 = blocks[4].xorBlocks(blocks[5]).xorBlocks(blocks[6]).xorBlocks(blocks[7]).toBytes(); + for (1..degree) |d| { + v[0..32].* = tags_0[d * 16 ..][0..16].* ++ tags_1[d * 16 ..][0..16].*; + state.absorb(&v); + } + }, + else => unreachable, + } + mem.writeInt(u64, sizes[0..8], degree, .little); + mem.writeInt(u64, sizes[8..16], tag_bits, .little); + t = blocks[2].xorBlocks(AesBlockVec.fromBytes(&sizes)); + for (0..7) |_| { + state.update(t, t); + } + } + switch (tag_bits) { + 128 => { + const tags = blocks[0].xorBlocks(blocks[1]).xorBlocks(blocks[2]).xorBlocks(blocks[3]).xorBlocks(blocks[4]).xorBlocks(blocks[5]).xorBlocks(blocks[6]).toBytes(); + return tags[0..16].*; + }, + 256 => { + const tags_0 = blocks[0].xorBlocks(blocks[1]).xorBlocks(blocks[2]).xorBlocks(blocks[3]).toBytes(); + const tags_1 = blocks[4].xorBlocks(blocks[5]).xorBlocks(blocks[6]).xorBlocks(blocks[7]).toBytes(); + return tags_0[0..16].* ++ tags_1[0..16].*; + }, + else => unreachable, + } + } }; } @@ -252,7 +305,7 @@ fn Aegis128XGeneric(comptime degree: u7, comptime tag_bits: u9) type { state.enc(&dst, &src); @memcpy(c[i..][0 .. m.len % block_length], dst[0 .. m.len % block_length]); } - tag.* = state.mac(tag_bits, ad.len, m.len); + tag.* = state.finalize(tag_bits, ad.len, m.len); } /// `m`: Message @@ -284,7 +337,7 @@ fn Aegis128XGeneric(comptime degree: u7, comptime tag_bits: u9) type { if (m.len % block_length != 0) { state.decLast(m[i..], c[i..]); } - var computed_tag = state.mac(tag_bits, ad.len, m.len); + var computed_tag = state.finalize(tag_bits, ad.len, m.len); const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag); if (!verify) { crypto.secureZero(u8, &computed_tag); @@ -401,7 +454,7 @@ fn State256X(comptime degree: u7) type { state.update(msg); } - fn mac(state: *State, comptime tag_bits: u9, adlen: usize, mlen: usize) [tag_bits / 8]u8 { + fn finalize(state: *State, comptime tag_bits: u9, adlen: usize, mlen: usize) [tag_bits / 8]u8 { const blocks = &state.blocks; var sizes: [aes_block_length]u8 = undefined; mem.writeInt(u64, sizes[0..8], @as(u64, adlen) * 8, .little); @@ -440,6 +493,61 @@ fn State256X(comptime degree: u7) type { else => unreachable, } } + + fn finalizeMac(state: *State, comptime tag_bits: u9, datalen: usize) [tag_bits / 8]u8 { + const blocks = &state.blocks; + var sizes: [aes_block_length]u8 = undefined; + mem.writeInt(u64, sizes[0..8], @as(u64, datalen) * 8, .little); + mem.writeInt(u64, sizes[8..16], tag_bits, .little); + for (1..degree) |i| { + @memcpy(sizes[i * 16 ..][0..16], sizes[0..16]); + } + var t = blocks[3].xorBlocks(AesBlockVec.fromBytes(&sizes)); + for (0..7) |_| { + state.update(t); + } + if (degree > 1) { + var v = [_]u8{0} ** rate; + switch (tag_bits) { + 128 => { + const tags = blocks[0].xorBlocks(blocks[1]).xorBlocks(blocks[2]).xorBlocks(blocks[3]).xorBlocks(blocks[4]).xorBlocks(blocks[5]).toBytes(); + for (1..degree) |d| { + v[0..16].* = tags[d * 16 ..][0..16].*; + state.absorb(&v); + } + }, + 256 => { + const tags_0 = blocks[0].xorBlocks(blocks[1]).xorBlocks(blocks[2]).toBytes(); + const tags_1 = blocks[3].xorBlocks(blocks[4]).xorBlocks(blocks[5]).toBytes(); + for (1..degree) |d| { + v[0..16].* = tags_0[d * 16 ..][0..16].*; + state.absorb(&v); + v[0..16].* = tags_1[d * 16 ..][0..16].*; + state.absorb(&v); + } + }, + else => unreachable, + } + mem.writeInt(u64, sizes[0..8], degree, .little); + mem.writeInt(u64, sizes[8..16], tag_bits, .little); + t = blocks[3].xorBlocks(AesBlockVec.fromBytes(&sizes)); + for (0..7) |_| { + state.update(t); + } + } + switch (tag_bits) { + 128 => { + const tags = blocks[0].xorBlocks(blocks[1]).xorBlocks(blocks[2]).xorBlocks(blocks[3]).xorBlocks(blocks[4]).xorBlocks(blocks[5]).toBytes(); + return tags[0..16].*; + }, + 256 => { + const tags_0 = blocks[0].xorBlocks(blocks[1]).xorBlocks(blocks[2]).toBytes(); + const tags_1 = blocks[3].xorBlocks(blocks[4]).xorBlocks(blocks[5]).toBytes(); + return tags_0[0..16].* ++ tags_1[0..16].*; + }, + else => unreachable, + } + } }; } @@ -492,7 +600,7 @@ fn Aegis256XGeneric(comptime degree: u7, comptime tag_bits: u9) type { state.enc(&dst, &src); @memcpy(c[i..][0 .. m.len % block_length], dst[0 .. m.len % block_length]); } - tag.* = state.mac(tag_bits, ad.len, m.len); + tag.* = state.finalize(tag_bits, ad.len, m.len); } /// `m`: Message @@ -524,7 +632,7 @@ fn Aegis256XGeneric(comptime degree: u7, comptime tag_bits: u9) type { if (m.len % block_length != 0) { state.decLast(m[i..], c[i..]); } - var computed_tag = state.mac(tag_bits, ad.len, m.len); + var computed_tag = state.finalize(tag_bits, ad.len, m.len); const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag); if (!verify) { crypto.secureZero(u8, &computed_tag); @@ -562,9 +670,31 @@ pub const Aegis128X2Mac = AegisMac(Aegis128X2_256); /// - It has a large security margin against internal collisions. pub const Aegis128LMac = AegisMac(Aegis128L_256); +/// The `Aegis256X4Mac` message authentication function has a 256-bit key size, +/// and outputs 256 bit tags. +/// The key size is the main practical difference with `Aegis128X4Mac`. +/// AEGIS' large state, non-linearity and non-invertibility provides the +/// following properties: +/// - 256 bit security against forgery. +/// - Recovering the secret key from the state would require ~2^256 attempts, +/// which is infeasible for any practical adversary. +/// - It has a large security margin against internal collisions. +pub const Aegis256X4Mac = AegisMac(Aegis256X4_256); + +/// The `Aegis256X2Mac` message authentication function has a 256-bit key size, +/// and outputs 256 bit tags. +/// The key size is the main practical difference with `Aegis128X2Mac`. +/// AEGIS' large state, non-linearity and non-invertibility provides the +/// following properties: +/// - 256 bit security against forgery. +/// - Recovering the secret key from the state would require ~2^256 attempts, +/// which is infeasible for any practical adversary. +/// - It has a large security margin against internal collisions. +pub const Aegis256X2Mac = AegisMac(Aegis256X2_256); + /// The `Aegis256Mac` message authentication function has a 256-bit key size, -/// and outputs 256 bit tags. Unless theoretical multi-target attacks are a -/// concern, the AEGIS-128L variant should be preferred. +/// and outputs 256 bit tags. +/// The key size is the main practical difference with `Aegis128LMac`. /// AEGIS' large state, non-linearity and non-invertibility provides the /// following properties: /// - 256 bit security against forgery. @@ -573,9 +703,21 @@ pub const Aegis128LMac = AegisMac(Aegis128L_256); /// - It has a large security margin against internal collisions. pub const Aegis256Mac = AegisMac(Aegis256_256); +/// AEGIS-128X4 MAC with 128-bit tags +pub const Aegis128X4Mac_128 = AegisMac(Aegis128X4); + +/// AEGIS-128X2 MAC with 128-bit tags +pub const Aegis128X2Mac_128 = AegisMac(Aegis128X2); + /// AEGIS-128L MAC with 128-bit tags pub const Aegis128LMac_128 = AegisMac(Aegis128L); +/// AEGIS-256X4 MAC with 128-bit tags +pub const Aegis256X4Mac_128 = AegisMac(Aegis256X4); + +/// AEGIS-256X2 MAC with 128-bit tags +pub const Aegis256X2Mac_128 = AegisMac(Aegis256X2); + /// AEGIS-256 MAC with 128-bit tags pub const Aegis256Mac_128 = AegisMac(Aegis256); @@ -585,6 +727,7 @@ fn AegisMac(comptime T: type) type { pub const mac_length = T.tag_length; pub const key_length = T.key_length; + pub const nonce_length = T.nonce_length; pub const block_length = T.block_length; state: T.State, @@ -592,11 +735,17 @@ fn AegisMac(comptime T: type) type { off: usize = 0, msg_len: usize = 0, - /// Initialize a state for the MAC function + /// Initialize a state for the MAC function, with a key and a nonce + pub fn initWithNonce(key: *const [key_length]u8, nonce: *const [nonce_length]u8) Mac { + return Mac{ + .state = T.State.init(key.*, nonce.*), + }; + } + + /// Initialize a state for the MAC function, with a default nonce pub fn init(key: *const [key_length]u8) Mac { - const nonce = [_]u8{0} ** T.nonce_length; return Mac{ - .state = T.State.init(key.*, nonce), + .state = T.State.init(key.*, [_]u8{0} ** nonce_length), }; } @@ -634,7 +783,14 @@ fn AegisMac(comptime T: type) type { @memcpy(pad[0..self.off], self.buf[0..self.off]); self.state.absorb(&pad); } - out.* = self.state.mac(T.tag_length * 8, self.msg_len, 0); + out.* = self.state.finalizeMac(T.tag_length * 8, self.msg_len); + } + + /// Return an authentication tag for a message, a key and a nonce + pub fn createWithNonce(out: *[mac_length]u8, msg: []const u8, key: *const [key_length]u8, nonce: *const [nonce_length]u8) void { + var ctx = Mac.initWithNonce(key, nonce); + ctx.update(msg); + ctx.final(out); } /// Return an authentication tag for a message and a key @@ -820,29 +976,72 @@ test "Aegis MAC" { st.update(msg[0..32]); st.update(msg[32..]); st.final(&tag); - try htest.assertEqual("f8840849602738d81037cbaa0f584ea95759e2ac60263ce77346bcdc79fe4319", &tag); + try htest.assertEqual("f5eb88d90b7d31c9a679eb94ed1374cd14816b19cdb77930d1a5158f8595983b", &tag); st = st_init; st.update(msg[0..31]); st.update(msg[31..]); st.final(&tag); - try htest.assertEqual("f8840849602738d81037cbaa0f584ea95759e2ac60263ce77346bcdc79fe4319", &tag); + try htest.assertEqual("f5eb88d90b7d31c9a679eb94ed1374cd14816b19cdb77930d1a5158f8595983b", &tag); st = st_init; st.update(msg[0..14]); st.update(msg[14..30]); st.update(msg[30..]); st.final(&tag); - try htest.assertEqual("f8840849602738d81037cbaa0f584ea95759e2ac60263ce77346bcdc79fe4319", &tag); - - var empty: [0]u8 = undefined; - const nonce = [_]u8{0x00} ** Aegis128L_256.nonce_length; - Aegis128L_256.encrypt(&empty, &tag, &empty, &msg, nonce, key); - try htest.assertEqual("f8840849602738d81037cbaa0f584ea95759e2ac60263ce77346bcdc79fe4319", &tag); + try htest.assertEqual("f5eb88d90b7d31c9a679eb94ed1374cd14816b19cdb77930d1a5158f8595983b", &tag); // An update whose size is not a multiple of the block size st = st_init; st.update(msg[0..33]); st.final(&tag); - try htest.assertEqual("c7cf649a844c1a6676cf6d91b1658e0aee54a4da330b0a8d3bc7ea4067551d1b", &tag); + try htest.assertEqual("07b3ba5ad9ceee5ef1906e3396f0fa540fbcd2f33833ef97c35bdc2ae9ae0535", &tag); +} + +test "AEGISMAC-128* test vectors" { + const key = [_]u8{ 0x10, 0x01 } ++ [_]u8{0x00} ** (16 - 2); + const nonce = [_]u8{ 0x10, 0x00, 0x02 } ++ [_]u8{0x00} ** (16 - 3); + var msg: [35]u8 = undefined; + for (&msg, 0..) |*byte, i| byte.* = @truncate(i); + var mac128: [16]u8 = undefined; + var mac256: [32]u8 = undefined; + + Aegis128LMac.createWithNonce(&mac256, &msg, &key, &nonce); + Aegis128LMac_128.createWithNonce(&mac128, &msg, &key, &nonce); + try htest.assertEqual("d3f09b2842ad301687d6902c921d7818", &mac128); + try htest.assertEqual("9490e7c89d420c9f37417fa625eb38e8cad53c5cbec55285e8499ea48377f2a3", &mac256); + + Aegis128X2Mac.createWithNonce(&mac256, &msg, &key, &nonce); + Aegis128X2Mac_128.createWithNonce(&mac128, &msg, &key, &nonce); + try htest.assertEqual("7aa41edfd57a95c1108d83c63b8d4d01", &mac128); + try htest.assertEqual("55b6449929cd2b01d04786e57698b3ddfb5cbf6e421bbd022637a33d60f40294", &mac256); + + Aegis128X4Mac.createWithNonce(&mac256, &msg, &key, &nonce); + Aegis128X4Mac_128.createWithNonce(&mac128, &msg, &key, &nonce); + try htest.assertEqual("46a194ea4337bb32c2186a99e312f3a7", &mac128); + try htest.assertEqual("ea884072699569532fb68ae9fb2653c9ffef3e974333d3a17d77be02453cc12f", &mac256); +} + +test "AEGISMAC-256* test vectors" { + const key = [_]u8{ 0x10, 0x01 } ++ [_]u8{0x00} ** (32 - 2); + const nonce = [_]u8{ 0x10, 0x00, 0x02 } ++ [_]u8{0x00} ** (32 - 3); + var msg: [35]u8 = undefined; + for (&msg, 0..) |*byte, i| byte.* = @truncate(i); + var mac128: [16]u8 = undefined; + var mac256: [32]u8 = undefined; + + Aegis256Mac.createWithNonce(&mac256, &msg, &key, &nonce); + Aegis256Mac_128.createWithNonce(&mac128, &msg, &key, &nonce); + try htest.assertEqual("c08e20cfc56f27195a46c9cef5c162d4", &mac128); + try htest.assertEqual("a5c906ede3d69545c11e20afa360b221f936e946ed2dba3d7c75ad6dc2784126", &mac256); + + Aegis256X2Mac.createWithNonce(&mac256, &msg, &key, &nonce); + Aegis256X2Mac_128.createWithNonce(&mac128, &msg, &key, &nonce); + try htest.assertEqual("fb319cb6dd728a764606fb14d37f2a5e", &mac128); + try htest.assertEqual("0844b20ed5147ceae89c7a160263afd4b1382d6b154ecf560ce8a342cb6a8fd1", &mac256); + + Aegis256X4Mac.createWithNonce(&mac256, &msg, &key, &nonce); + Aegis256X4Mac_128.createWithNonce(&mac128, &msg, &key, &nonce); + try htest.assertEqual("a51f9bc5beae60cce77f0dbc60761edd", &mac128); + try htest.assertEqual("b36a16ef07c36d75a91f437502f24f545b8dfa88648ed116943c29fead3bf10c", &mac256); } diff --git a/lib/std/crypto/benchmark.zig b/lib/std/crypto/benchmark.zig index fcb8e7a4622e..c3dcd9b8cbd7 100644 --- a/lib/std/crypto/benchmark.zig +++ b/lib/std/crypto/benchmark.zig @@ -72,6 +72,10 @@ const macs = [_]Crypto{ Crypto{ .ty = crypto.auth.siphash.SipHash64(1, 3), .name = "siphash-1-3" }, Crypto{ .ty = crypto.auth.siphash.SipHash128(2, 4), .name = "siphash128-2-4" }, Crypto{ .ty = crypto.auth.siphash.SipHash128(1, 3), .name = "siphash128-1-3" }, + Crypto{ .ty = crypto.auth.aegis.Aegis128X4Mac, .name = "aegis-128x4 mac" }, + Crypto{ .ty = crypto.auth.aegis.Aegis256X4Mac, .name = "aegis-256x4 mac" }, + Crypto{ .ty = crypto.auth.aegis.Aegis128X2Mac, .name = "aegis-128x2 mac" }, + Crypto{ .ty = crypto.auth.aegis.Aegis256X2Mac, .name = "aegis-256x2 mac" }, Crypto{ .ty = crypto.auth.aegis.Aegis128LMac, .name = "aegis-128l mac" }, Crypto{ .ty = crypto.auth.aegis.Aegis256Mac, .name = "aegis-256 mac" }, Crypto{ .ty = crypto.auth.cmac.CmacAes128, .name = "aes-cmac" },