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
This commit is contained in:
Frank Denis
2022-11-22 18:16:04 +01:00
committed by GitHub
parent 114244f770
commit ea05223b63
3 changed files with 158 additions and 6 deletions

View File

@@ -37,6 +37,10 @@ pub const aead = struct {
pub const auth = struct {
pub const hmac = @import("crypto/hmac.zig");
pub const siphash = @import("crypto/siphash.zig");
pub const aegis = struct {
pub const Aegis128LMac = @import("crypto/aegis.zig").Aegis128LMac;
pub const Aegis256Mac = @import("crypto/aegis.zig").Aegis256Mac;
};
};
/// Core functions, that should rarely be used directly by applications.

View File

@@ -42,6 +42,12 @@ const State128L = struct {
blocks[4] = blocks[4].xorBlocks(d2);
}
fn absorb(state: *State128L, src: *const [32]u8) void {
const msg0 = AesBlock.fromBytes(src[0..16]);
const msg1 = AesBlock.fromBytes(src[16..32]);
state.update(msg0, msg1);
}
fn enc(state: *State128L, dst: *[32]u8, src: *const [32]u8) void {
const blocks = &state.blocks;
const msg0 = AesBlock.fromBytes(src[0..16]);
@@ -86,11 +92,14 @@ const State128L = struct {
/// The 128L variant of AEGIS has a 128 bit key, a 128 bit nonce, and processes 256 bit message blocks.
/// It was designed to fully exploit the parallelism and built-in AES support of recent Intel and ARM CPUs.
///
/// https://competitions.cr.yp.to/round3/aegisv11.pdf
/// https://datatracker.ietf.org/doc/draft-irtf-cfrg-aegis-aead/
pub const Aegis128L = struct {
pub const tag_length = 16;
pub const nonce_length = 16;
pub const key_length = 16;
pub const block_length = 32;
const State = State128L;
/// c: ciphertext: output buffer should be of size m.len
/// tag: authentication tag: output MAC
@@ -105,12 +114,12 @@ pub const Aegis128L = struct {
var dst: [32]u8 align(16) = undefined;
var i: usize = 0;
while (i + 32 <= ad.len) : (i += 32) {
state.enc(&dst, ad[i..][0..32]);
state.absorb(ad[i..][0..32]);
}
if (ad.len % 32 != 0) {
mem.set(u8, src[0..], 0);
mem.copy(u8, src[0 .. ad.len % 32], ad[i .. i + ad.len % 32]);
state.enc(&dst, &src);
state.absorb(&src);
}
i = 0;
while (i + 32 <= m.len) : (i += 32) {
@@ -138,12 +147,12 @@ pub const Aegis128L = struct {
var dst: [32]u8 align(16) = undefined;
var i: usize = 0;
while (i + 32 <= ad.len) : (i += 32) {
state.enc(&dst, ad[i..][0..32]);
state.absorb(ad[i..][0..32]);
}
if (ad.len % 32 != 0) {
mem.set(u8, src[0..], 0);
mem.copy(u8, src[0 .. ad.len % 32], ad[i .. i + ad.len % 32]);
state.enc(&dst, &src);
state.absorb(&src);
}
i = 0;
while (i + 32 <= m.len) : (i += 32) {
@@ -212,6 +221,11 @@ const State256 = struct {
blocks[0] = tmp.xorBlocks(d);
}
fn absorb(state: *State256, src: *const [16]u8) void {
const msg = AesBlock.fromBytes(src);
state.update(msg);
}
fn enc(state: *State256, dst: *[16]u8, src: *const [16]u8) void {
const blocks = &state.blocks;
const msg = AesBlock.fromBytes(src);
@@ -248,11 +262,14 @@ const State256 = struct {
///
/// The 256 bit variant of AEGIS has a 256 bit key, a 256 bit nonce, and processes 128 bit message blocks.
///
/// https://competitions.cr.yp.to/round3/aegisv11.pdf
/// https://datatracker.ietf.org/doc/draft-irtf-cfrg-aegis-aead/
pub const Aegis256 = struct {
pub const tag_length = 16;
pub const nonce_length = 32;
pub const key_length = 32;
pub const block_length = 16;
const State = State256;
/// c: ciphertext: output buffer should be of size m.len
/// tag: authentication tag: output MAC
@@ -332,6 +349,101 @@ pub const Aegis256 = struct {
}
};
/// The AEGIS-128L message authentication function outputs 128 bit tags.
/// In addition to being extremely fast, its large state, non-linearity
/// and non-invertibility provides the following properties:
/// - 128 bit security, stronger than GHash/Polyval/Poly1305.
/// - Recovering the secret key from the state would require ~2^128 attempts,
/// which is infeasible for any practical adversary.
/// - It has a large security margin against internal collisions.
pub const Aegis128LMac = AegisMac(Aegis128L);
/// The AEGIS-256 message authentication function has a 256-bit key size,
/// but outputs 128 bit tags. Unless theoretical multi-target attacks are a
/// concern, the AEGIS-128L variant should be preferred.
/// AEGIS' large state, non-linearity and non-invertibility provides the
/// following properties:
/// - 128 bit security, stronger than GHash/Polyval/Poly1305.
/// - Recovering the secret key from the state would require ~2^128 attempts,
/// which is infeasible for any practical adversary.
/// - It has a large security margin against internal collisions.
pub const Aegis256Mac = AegisMac(Aegis256);
fn AegisMac(comptime T: type) type {
return struct {
const Self = @This();
pub const mac_length = T.tag_length;
pub const key_length = T.key_length;
pub const block_length = T.block_length;
state: T.State,
buf: [block_length]u8 = undefined,
off: usize = 0,
msg_len: usize = 0,
/// Initialize a state for the MAC function
pub fn init(key: *const [key_length]u8) Self {
const nonce = [_]u8{0} ** T.nonce_length;
return Self{
.state = T.State.init(key.*, nonce),
};
}
/// Add data to the state
pub fn update(self: *Self, b: []const u8) void {
self.msg_len += b.len;
const len_partial = @min(b.len, block_length - self.off);
mem.copy(u8, self.buf[self.off..][0..len_partial], b[0..len_partial]);
self.off += len_partial;
if (self.off < block_length) {
return;
}
self.state.absorb(&self.buf);
var i = len_partial;
self.off = 0;
while (i + block_length <= b.len) : (i += block_length) {
self.state.absorb(b[i..][0..block_length]);
}
if (i != b.len) {
mem.copy(u8, self.buf[0..], b[i..]);
self.off = b.len - i;
}
}
/// Return an authentication tag for the current state
pub fn final(self: *Self, out: *[mac_length]u8) void {
if (self.off > 0) {
var pad = [_]u8{0} ** block_length;
mem.copy(u8, pad[0..], self.buf[0..self.off]);
self.state.absorb(&pad);
}
out.* = self.state.mac(self.msg_len, 0);
}
/// Return an authentication tag for a message and a key
pub fn create(out: *[mac_length]u8, msg: []const u8, key: *const [key_length]u8) void {
var ctx = Self.init(key);
ctx.update(msg);
ctx.final(out);
}
pub const Error = error{};
pub const Writer = std.io.Writer(*Self, Error, write);
fn write(self: *Self, bytes: []const u8) Error!usize {
self.update(bytes);
return bytes.len;
}
pub fn writer(self: *Self) Writer {
return .{ .context = self };
}
};
}
const htest = @import("test.zig");
const testing = std.testing;
@@ -446,3 +558,37 @@ test "Aegis256 test vector 3" {
try htest.assertEqual("f7a0878f68bd083e8065354071fc27c3", &tag);
}
test "Aegis MAC" {
const key = [_]u8{0x00} ** Aegis128LMac.key_length;
var msg: [64]u8 = undefined;
for (msg) |*m, i| {
m.* = @truncate(u8, i);
}
const st_init = Aegis128LMac.init(&key);
var st = st_init;
var tag: [Aegis128LMac.mac_length]u8 = undefined;
st.update(msg[0..32]);
st.update(msg[32..]);
st.final(&tag);
try htest.assertEqual("b4e8e46cee04a401ec67bad73df4aa60", &tag);
st = st_init;
st.update(msg[0..31]);
st.update(msg[31..]);
st.final(&tag);
try htest.assertEqual("b4e8e46cee04a401ec67bad73df4aa60", &tag);
st = st_init;
st.update(msg[0..14]);
st.update(msg[14..30]);
st.update(msg[30..]);
st.final(&tag);
try htest.assertEqual("b4e8e46cee04a401ec67bad73df4aa60", &tag);
var empty: [0]u8 = undefined;
const nonce = [_]u8{0x00} ** Aegis128L.nonce_length;
Aegis128L.encrypt(&empty, &tag, &empty, &msg, nonce, key);
try htest.assertEqual("b4e8e46cee04a401ec67bad73df4aa60", &tag);
}

View File

@@ -64,6 +64,8 @@ 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.Aegis128LMac, .name = "aegis-128l mac" },
Crypto{ .ty = crypto.auth.aegis.Aegis256Mac, .name = "aegis-256 mac" },
};
pub fn benchmarkMac(comptime Mac: anytype, comptime bytes: comptime_int) !u64 {