| ... | ... | @@ -42,6 +42,12 @@ const State128L = struct { |
| 42 | 42 | blocks[4] = blocks[4].xorBlocks(d2); |
| 43 | 43 | } |
| 44 | 44 | |
| 45 | fn absorb(state: *State128L, src: *const [32]u8) void { |
| 46 | const msg0 = AesBlock.fromBytes(src[0..16]); |
| 47 | const msg1 = AesBlock.fromBytes(src[16..32]); |
| 48 | state.update(msg0, msg1); |
| 49 | } |
| 50 | |
| 45 | 51 | fn enc(state: *State128L, dst: *[32]u8, src: *const [32]u8) void { |
| 46 | 52 | const blocks = &state.blocks; |
| 47 | 53 | const msg0 = AesBlock.fromBytes(src[0..16]); |
| ... | ... | @@ -86,11 +92,14 @@ const State128L = struct { |
| 86 | 92 | /// The 128L variant of AEGIS has a 128 bit key, a 128 bit nonce, and processes 256 bit message blocks. |
| 87 | 93 | /// It was designed to fully exploit the parallelism and built-in AES support of recent Intel and ARM CPUs. |
| 88 | 94 | /// |
| 89 | | /// https://competitions.cr.yp.to/round3/aegisv11.pdf |
| 95 | /// https://datatracker.ietf.org/doc/draft-irtf-cfrg-aegis-aead/ |
| 90 | 96 | pub const Aegis128L = struct { |
| 91 | 97 | pub const tag_length = 16; |
| 92 | 98 | pub const nonce_length = 16; |
| 93 | 99 | pub const key_length = 16; |
| 100 | pub const block_length = 32; |
| 101 | |
| 102 | const State = State128L; |
| 94 | 103 | |
| 95 | 104 | /// c: ciphertext: output buffer should be of size m.len |
| 96 | 105 | /// tag: authentication tag: output MAC |
| ... | ... | @@ -105,12 +114,12 @@ pub const Aegis128L = struct { |
| 105 | 114 | var dst: [32]u8 align(16) = undefined; |
| 106 | 115 | var i: usize = 0; |
| 107 | 116 | while (i + 32 <= ad.len) : (i += 32) { |
| 108 | | state.enc(&dst, ad[i..][0..32]); |
| 117 | state.absorb(ad[i..][0..32]); |
| 109 | 118 | } |
| 110 | 119 | if (ad.len % 32 != 0) { |
| 111 | 120 | mem.set(u8, src[0..], 0); |
| 112 | 121 | mem.copy(u8, src[0 .. ad.len % 32], ad[i .. i + ad.len % 32]); |
| 113 | | state.enc(&dst, &src); |
| 122 | state.absorb(&src); |
| 114 | 123 | } |
| 115 | 124 | i = 0; |
| 116 | 125 | while (i + 32 <= m.len) : (i += 32) { |
| ... | ... | @@ -138,12 +147,12 @@ pub const Aegis128L = struct { |
| 138 | 147 | var dst: [32]u8 align(16) = undefined; |
| 139 | 148 | var i: usize = 0; |
| 140 | 149 | while (i + 32 <= ad.len) : (i += 32) { |
| 141 | | state.enc(&dst, ad[i..][0..32]); |
| 150 | state.absorb(ad[i..][0..32]); |
| 142 | 151 | } |
| 143 | 152 | if (ad.len % 32 != 0) { |
| 144 | 153 | mem.set(u8, src[0..], 0); |
| 145 | 154 | mem.copy(u8, src[0 .. ad.len % 32], ad[i .. i + ad.len % 32]); |
| 146 | | state.enc(&dst, &src); |
| 155 | state.absorb(&src); |
| 147 | 156 | } |
| 148 | 157 | i = 0; |
| 149 | 158 | while (i + 32 <= m.len) : (i += 32) { |
| ... | ... | @@ -212,6 +221,11 @@ const State256 = struct { |
| 212 | 221 | blocks[0] = tmp.xorBlocks(d); |
| 213 | 222 | } |
| 214 | 223 | |
| 224 | fn absorb(state: *State256, src: *const [16]u8) void { |
| 225 | const msg = AesBlock.fromBytes(src); |
| 226 | state.update(msg); |
| 227 | } |
| 228 | |
| 215 | 229 | fn enc(state: *State256, dst: *[16]u8, src: *const [16]u8) void { |
| 216 | 230 | const blocks = &state.blocks; |
| 217 | 231 | const msg = AesBlock.fromBytes(src); |
| ... | ... | @@ -248,11 +262,14 @@ const State256 = struct { |
| 248 | 262 | /// |
| 249 | 263 | /// The 256 bit variant of AEGIS has a 256 bit key, a 256 bit nonce, and processes 128 bit message blocks. |
| 250 | 264 | /// |
| 251 | | /// https://competitions.cr.yp.to/round3/aegisv11.pdf |
| 265 | /// https://datatracker.ietf.org/doc/draft-irtf-cfrg-aegis-aead/ |
| 252 | 266 | pub const Aegis256 = struct { |
| 253 | 267 | pub const tag_length = 16; |
| 254 | 268 | pub const nonce_length = 32; |
| 255 | 269 | pub const key_length = 32; |
| 270 | pub const block_length = 16; |
| 271 | |
| 272 | const State = State256; |
| 256 | 273 | |
| 257 | 274 | /// c: ciphertext: output buffer should be of size m.len |
| 258 | 275 | /// tag: authentication tag: output MAC |
| ... | ... | @@ -332,6 +349,101 @@ pub const Aegis256 = struct { |
| 332 | 349 | } |
| 333 | 350 | }; |
| 334 | 351 | |
| 352 | /// The AEGIS-128L message authentication function outputs 128 bit tags. |
| 353 | /// In addition to being extremely fast, its large state, non-linearity |
| 354 | /// and non-invertibility provides the following properties: |
| 355 | /// - 128 bit security, stronger than GHash/Polyval/Poly1305. |
| 356 | /// - Recovering the secret key from the state would require ~2^128 attempts, |
| 357 | /// which is infeasible for any practical adversary. |
| 358 | /// - It has a large security margin against internal collisions. |
| 359 | pub const Aegis128LMac = AegisMac(Aegis128L); |
| 360 | |
| 361 | /// The AEGIS-256 message authentication function has a 256-bit key size, |
| 362 | /// but outputs 128 bit tags. Unless theoretical multi-target attacks are a |
| 363 | /// concern, the AEGIS-128L variant should be preferred. |
| 364 | /// AEGIS' large state, non-linearity and non-invertibility provides the |
| 365 | /// following properties: |
| 366 | /// - 128 bit security, stronger than GHash/Polyval/Poly1305. |
| 367 | /// - Recovering the secret key from the state would require ~2^128 attempts, |
| 368 | /// which is infeasible for any practical adversary. |
| 369 | /// - It has a large security margin against internal collisions. |
| 370 | pub const Aegis256Mac = AegisMac(Aegis256); |
| 371 | |
| 372 | fn AegisMac(comptime T: type) type { |
| 373 | return struct { |
| 374 | const Self = @This(); |
| 375 | |
| 376 | pub const mac_length = T.tag_length; |
| 377 | pub const key_length = T.key_length; |
| 378 | pub const block_length = T.block_length; |
| 379 | |
| 380 | state: T.State, |
| 381 | buf: [block_length]u8 = undefined, |
| 382 | off: usize = 0, |
| 383 | msg_len: usize = 0, |
| 384 | |
| 385 | /// Initialize a state for the MAC function |
| 386 | pub fn init(key: *const [key_length]u8) Self { |
| 387 | const nonce = [_]u8{0} ** T.nonce_length; |
| 388 | return Self{ |
| 389 | .state = T.State.init(key.*, nonce), |
| 390 | }; |
| 391 | } |
| 392 | |
| 393 | /// Add data to the state |
| 394 | pub fn update(self: *Self, b: []const u8) void { |
| 395 | self.msg_len += b.len; |
| 396 | |
| 397 | const len_partial = @min(b.len, block_length - self.off); |
| 398 | mem.copy(u8, self.buf[self.off..][0..len_partial], b[0..len_partial]); |
| 399 | self.off += len_partial; |
| 400 | if (self.off < block_length) { |
| 401 | return; |
| 402 | } |
| 403 | self.state.absorb(&self.buf); |
| 404 | |
| 405 | var i = len_partial; |
| 406 | self.off = 0; |
| 407 | while (i + block_length <= b.len) : (i += block_length) { |
| 408 | self.state.absorb(b[i..][0..block_length]); |
| 409 | } |
| 410 | if (i != b.len) { |
| 411 | mem.copy(u8, self.buf[0..], b[i..]); |
| 412 | self.off = b.len - i; |
| 413 | } |
| 414 | } |
| 415 | |
| 416 | /// Return an authentication tag for the current state |
| 417 | pub fn final(self: *Self, out: *[mac_length]u8) void { |
| 418 | if (self.off > 0) { |
| 419 | var pad = [_]u8{0} ** block_length; |
| 420 | mem.copy(u8, pad[0..], self.buf[0..self.off]); |
| 421 | self.state.absorb(&pad); |
| 422 | } |
| 423 | out.* = self.state.mac(self.msg_len, 0); |
| 424 | } |
| 425 | |
| 426 | /// Return an authentication tag for a message and a key |
| 427 | pub fn create(out: *[mac_length]u8, msg: []const u8, key: *const [key_length]u8) void { |
| 428 | var ctx = Self.init(key); |
| 429 | ctx.update(msg); |
| 430 | ctx.final(out); |
| 431 | } |
| 432 | |
| 433 | pub const Error = error{}; |
| 434 | pub const Writer = std.io.Writer(*Self, Error, write); |
| 435 | |
| 436 | fn write(self: *Self, bytes: []const u8) Error!usize { |
| 437 | self.update(bytes); |
| 438 | return bytes.len; |
| 439 | } |
| 440 | |
| 441 | pub fn writer(self: *Self) Writer { |
| 442 | return .{ .context = self }; |
| 443 | } |
| 444 | }; |
| 445 | } |
| 446 | |
| 335 | 447 | const htest = @import("test.zig"); |
| 336 | 448 | const testing = std.testing; |
| 337 | 449 | |
| ... | ... | @@ -446,3 +558,37 @@ test "Aegis256 test vector 3" { |
| 446 | 558 | |
| 447 | 559 | try htest.assertEqual("f7a0878f68bd083e8065354071fc27c3", &tag); |
| 448 | 560 | } |
| 561 | |
| 562 | test "Aegis MAC" { |
| 563 | const key = [_]u8{0x00} ** Aegis128LMac.key_length; |
| 564 | var msg: [64]u8 = undefined; |
| 565 | for (msg) |*m, i| { |
| 566 | m.* = @truncate(u8, i); |
| 567 | } |
| 568 | const st_init = Aegis128LMac.init(&key); |
| 569 | var st = st_init; |
| 570 | var tag: [Aegis128LMac.mac_length]u8 = undefined; |
| 571 | |
| 572 | st.update(msg[0..32]); |
| 573 | st.update(msg[32..]); |
| 574 | st.final(&tag); |
| 575 | try htest.assertEqual("b4e8e46cee04a401ec67bad73df4aa60", &tag); |
| 576 | |
| 577 | st = st_init; |
| 578 | st.update(msg[0..31]); |
| 579 | st.update(msg[31..]); |
| 580 | st.final(&tag); |
| 581 | try htest.assertEqual("b4e8e46cee04a401ec67bad73df4aa60", &tag); |
| 582 | |
| 583 | st = st_init; |
| 584 | st.update(msg[0..14]); |
| 585 | st.update(msg[14..30]); |
| 586 | st.update(msg[30..]); |
| 587 | st.final(&tag); |
| 588 | try htest.assertEqual("b4e8e46cee04a401ec67bad73df4aa60", &tag); |
| 589 | |
| 590 | var empty: [0]u8 = undefined; |
| 591 | const nonce = [_]u8{0x00} ** Aegis128L.nonce_length; |
| 592 | Aegis128L.encrypt(&empty, &tag, &empty, &msg, nonce, key); |
| 593 | try htest.assertEqual("b4e8e46cee04a401ec67bad73df4aa60", &tag); |
| 594 | } |