authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2022-11-22 18:16:04+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2022-11-22 18:16:04+01:00
logea05223b638789f2ef5b35081e66b5f4f5a45d77
treeda1ee4a9d48e6c102be51066e068b53eb26cd1fb
parent114244f7701c5bda996e852fbadcab76a488bdf6
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

std.crypto.auth: add AEGIS MAC (#13607)

* Update the AEGIS specification URL to the current draft * std.crypto.auth: add AEGIS MAC The Pelican-based authentication function of the AEGIS construction can be used independently from authenticated encryption, as a faster and more secure alternative to GHASH/POLYVAL/Poly1305. We already expose GHASH, POLYVAL and Poly1305 for use outside AES-GCM and ChaChaPoly, so there are no reasons not to expose the MAC from AEGIS as well. Like other 128-bit hash functions, finding a collision only requires ~2^64 attempts or inputs, which may still be acceptable for many practical applications. Benchmark (Apple M1): siphash128-1-3: 3222 MiB/s ghash: 8682 MiB/s aegis-128l mac: 12544 MiB/s Benchmark (Zen 2): siphash128-1-3: 4732 MiB/s ghash: 5563 MiB/s aegis-128l mac: 19270 MiB/s

3 files changed, 158 insertions(+), 6 deletions(-)

lib/std/crypto.zig+4
...@@ -37,6 +37,10 @@ pub const aead = struct {...@@ -37,6 +37,10 @@ pub const aead = struct {
37pub const auth = struct {37pub const auth = struct {
38 pub const hmac = @import("crypto/hmac.zig");38 pub const hmac = @import("crypto/hmac.zig");
39 pub const siphash = @import("crypto/siphash.zig");39 pub const siphash = @import("crypto/siphash.zig");
40 pub const aegis = struct {
41 pub const Aegis128LMac = @import("crypto/aegis.zig").Aegis128LMac;
42 pub const Aegis256Mac = @import("crypto/aegis.zig").Aegis256Mac;
43 };
40};44};
4145
42/// Core functions, that should rarely be used directly by applications.46/// Core functions, that should rarely be used directly by applications.
lib/std/crypto/aegis.zig+152-6
...@@ -42,6 +42,12 @@ const State128L = struct {...@@ -42,6 +42,12 @@ const State128L = struct {
42 blocks[4] = blocks[4].xorBlocks(d2);42 blocks[4] = blocks[4].xorBlocks(d2);
43 }43 }
4444
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 fn enc(state: *State128L, dst: *[32]u8, src: *const [32]u8) void {51 fn enc(state: *State128L, dst: *[32]u8, src: *const [32]u8) void {
46 const blocks = &state.blocks;52 const blocks = &state.blocks;
47 const msg0 = AesBlock.fromBytes(src[0..16]);53 const msg0 = AesBlock.fromBytes(src[0..16]);
...@@ -86,11 +92,14 @@ const State128L = struct {...@@ -86,11 +92,14 @@ const State128L = struct {
86/// The 128L variant of AEGIS has a 128 bit key, a 128 bit nonce, and processes 256 bit message blocks.92/// The 128L variant of AEGIS has a 128 bit key, a 128 bit nonce, and processes 256 bit message blocks.
87/// It was designed to fully exploit the parallelism and built-in AES support of recent Intel and ARM CPUs.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.pdf95/// https://datatracker.ietf.org/doc/draft-irtf-cfrg-aegis-aead/
90pub const Aegis128L = struct {96pub const Aegis128L = struct {
91 pub const tag_length = 16;97 pub const tag_length = 16;
92 pub const nonce_length = 16;98 pub const nonce_length = 16;
93 pub const key_length = 16;99 pub const key_length = 16;
100 pub const block_length = 32;
101
102 const State = State128L;
94103
95 /// c: ciphertext: output buffer should be of size m.len104 /// c: ciphertext: output buffer should be of size m.len
96 /// tag: authentication tag: output MAC105 /// tag: authentication tag: output MAC
...@@ -105,12 +114,12 @@ pub const Aegis128L = struct {...@@ -105,12 +114,12 @@ pub const Aegis128L = struct {
105 var dst: [32]u8 align(16) = undefined;114 var dst: [32]u8 align(16) = undefined;
106 var i: usize = 0;115 var i: usize = 0;
107 while (i + 32 <= ad.len) : (i += 32) {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 if (ad.len % 32 != 0) {119 if (ad.len % 32 != 0) {
111 mem.set(u8, src[0..], 0);120 mem.set(u8, src[0..], 0);
112 mem.copy(u8, src[0 .. ad.len % 32], ad[i .. i + ad.len % 32]);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 i = 0;124 i = 0;
116 while (i + 32 <= m.len) : (i += 32) {125 while (i + 32 <= m.len) : (i += 32) {
...@@ -138,12 +147,12 @@ pub const Aegis128L = struct {...@@ -138,12 +147,12 @@ pub const Aegis128L = struct {
138 var dst: [32]u8 align(16) = undefined;147 var dst: [32]u8 align(16) = undefined;
139 var i: usize = 0;148 var i: usize = 0;
140 while (i + 32 <= ad.len) : (i += 32) {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 if (ad.len % 32 != 0) {152 if (ad.len % 32 != 0) {
144 mem.set(u8, src[0..], 0);153 mem.set(u8, src[0..], 0);
145 mem.copy(u8, src[0 .. ad.len % 32], ad[i .. i + ad.len % 32]);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 i = 0;157 i = 0;
149 while (i + 32 <= m.len) : (i += 32) {158 while (i + 32 <= m.len) : (i += 32) {
...@@ -212,6 +221,11 @@ const State256 = struct {...@@ -212,6 +221,11 @@ const State256 = struct {
212 blocks[0] = tmp.xorBlocks(d);221 blocks[0] = tmp.xorBlocks(d);
213 }222 }
214223
224 fn absorb(state: *State256, src: *const [16]u8) void {
225 const msg = AesBlock.fromBytes(src);
226 state.update(msg);
227 }
228
215 fn enc(state: *State256, dst: *[16]u8, src: *const [16]u8) void {229 fn enc(state: *State256, dst: *[16]u8, src: *const [16]u8) void {
216 const blocks = &state.blocks;230 const blocks = &state.blocks;
217 const msg = AesBlock.fromBytes(src);231 const msg = AesBlock.fromBytes(src);
...@@ -248,11 +262,14 @@ const State256 = struct {...@@ -248,11 +262,14 @@ const State256 = struct {
248///262///
249/// The 256 bit variant of AEGIS has a 256 bit key, a 256 bit nonce, and processes 128 bit message blocks.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.pdf265/// https://datatracker.ietf.org/doc/draft-irtf-cfrg-aegis-aead/
252pub const Aegis256 = struct {266pub const Aegis256 = struct {
253 pub const tag_length = 16;267 pub const tag_length = 16;
254 pub const nonce_length = 32;268 pub const nonce_length = 32;
255 pub const key_length = 32;269 pub const key_length = 32;
270 pub const block_length = 16;
271
272 const State = State256;
256273
257 /// c: ciphertext: output buffer should be of size m.len274 /// c: ciphertext: output buffer should be of size m.len
258 /// tag: authentication tag: output MAC275 /// tag: authentication tag: output MAC
...@@ -332,6 +349,101 @@ pub const Aegis256 = struct {...@@ -332,6 +349,101 @@ pub const Aegis256 = struct {
332 }349 }
333};350};
334351
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.
359pub 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.
370pub const Aegis256Mac = AegisMac(Aegis256);
371
372fn 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
335const htest = @import("test.zig");447const htest = @import("test.zig");
336const testing = std.testing;448const testing = std.testing;
337449
...@@ -446,3 +558,37 @@ test "Aegis256 test vector 3" {...@@ -446,3 +558,37 @@ test "Aegis256 test vector 3" {
446558
447 try htest.assertEqual("f7a0878f68bd083e8065354071fc27c3", &tag);559 try htest.assertEqual("f7a0878f68bd083e8065354071fc27c3", &tag);
448}560}
561
562test "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}
lib/std/crypto/benchmark.zig+2
...@@ -64,6 +64,8 @@ const macs = [_]Crypto{...@@ -64,6 +64,8 @@ const macs = [_]Crypto{
64 Crypto{ .ty = crypto.auth.siphash.SipHash64(1, 3), .name = "siphash-1-3" },64 Crypto{ .ty = crypto.auth.siphash.SipHash64(1, 3), .name = "siphash-1-3" },
65 Crypto{ .ty = crypto.auth.siphash.SipHash128(2, 4), .name = "siphash128-2-4" },65 Crypto{ .ty = crypto.auth.siphash.SipHash128(2, 4), .name = "siphash128-2-4" },
66 Crypto{ .ty = crypto.auth.siphash.SipHash128(1, 3), .name = "siphash128-1-3" },66 Crypto{ .ty = crypto.auth.siphash.SipHash128(1, 3), .name = "siphash128-1-3" },
67 Crypto{ .ty = crypto.auth.aegis.Aegis128LMac, .name = "aegis-128l mac" },
68 Crypto{ .ty = crypto.auth.aegis.Aegis256Mac, .name = "aegis-256 mac" },
67};69};
6870
69pub fn benchmarkMac(comptime Mac: anytype, comptime bytes: comptime_int) !u64 {71pub fn benchmarkMac(comptime Mac: anytype, comptime bytes: comptime_int) !u64 {