Commit 295c5a64f5

Frank Denis <124872+jedisct1@users.noreply.github.com>
2024-12-11 18:52:43
Reinstantiates AEGIS-MAC with the final construction (#22205)
This reverts commit c9d6f8b5058ba0df3bf281a3be3a3570c2219754.
1 parent c172877
Changed files (3)
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);
 }
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" },
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");