diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index 5699f7db36..8c225aa719 100644 --- a/lib/std/crypto.zig +++ b/lib/std/crypto.zig @@ -13,6 +13,8 @@ pub const aead = struct { pub const XChaCha20Poly1305 = chacha20.XChacha20Poly1305; pub const AEGIS128L = @import("crypto/aegis.zig").AEGIS128L; pub const AEGIS256 = @import("crypto/aegis.zig").AEGIS256; + pub const AES128GCM = @import("crypto/aes_gcm.zig").AES128GCM; + pub const AES256GCM = @import("crypto/aes_gcm.zig").AES256GCM; }; /// Authentication (MAC) functions. diff --git a/lib/std/crypto/aes_gcm.zig b/lib/std/crypto/aes_gcm.zig new file mode 100644 index 0000000000..c7093a7593 --- /dev/null +++ b/lib/std/crypto/aes_gcm.zig @@ -0,0 +1,161 @@ +const std = @import("std"); +const assert = std.debug.assert; +const builtin = std.builtin; +const crypto = std.crypto; +const debug = std.debug; +const Ghash = std.crypto.onetimeauth.Ghash; +const mem = std.mem; +const modes = crypto.core.modes; + +pub const AES128GCM = AESGCM(crypto.core.aes.AES128); +pub const AES256GCM = AESGCM(crypto.core.aes.AES256); + +fn AESGCM(comptime AES: anytype) type { + debug.assert(AES.block.block_size == 16); + + return struct { + pub const tag_length = 16; + pub const nonce_length = 12; + pub const key_length = AES.key_bits / 8; + + const zeros = [_]u8{0} ** 16; + + pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) void { + debug.assert(c.len == m.len); + debug.assert(m.len <= 16 * ((1 << 32) - 2)); + + const aes = AES.initEnc(key); + var h: [16]u8 = undefined; + aes.encrypt(&h, &zeros); + + var t: [16]u8 = undefined; + var j: [16]u8 = undefined; + mem.copy(u8, j[0..nonce_length], npub[0..]); + mem.writeIntBig(u32, j[nonce_length..][0..4], 1); + aes.encrypt(&t, &j); + + var mac = Ghash.init(&h); + mac.update(ad); + mac.pad(); + + mem.writeIntBig(u32, j[nonce_length..][0..4], 2); + modes.ctr(@TypeOf(aes), aes, c, m, j, builtin.Endian.Big); + mac.update(c[0..m.len][0..]); + mac.pad(); + + var final_block = h; + mem.writeIntBig(u64, final_block[0..8], ad.len * 8); + mem.writeIntBig(u64, final_block[8..16], m.len * 8); + mac.update(&final_block); + mac.final(tag); + for (t) |x, i| { + tag[i] ^= x; + } + } + + pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) !void { + assert(c.len == m.len); + + const aes = AES.initEnc(key); + var h: [16]u8 = undefined; + aes.encrypt(&h, &zeros); + + var t: [16]u8 = undefined; + var j: [16]u8 = undefined; + mem.copy(u8, j[0..nonce_length], npub[0..]); + mem.writeIntBig(u32, j[nonce_length..][0..4], 1); + aes.encrypt(&t, &j); + + var mac = Ghash.init(&h); + mac.update(ad); + mac.pad(); + + mac.update(c); + mac.pad(); + + var final_block = h; + mem.writeIntBig(u64, final_block[0..8], ad.len * 8); + mem.writeIntBig(u64, final_block[8..16], m.len * 8); + mac.update(&final_block); + var computed_tag: [Ghash.mac_length]u8 = undefined; + mac.final(&computed_tag); + for (t) |x, i| { + computed_tag[i] ^= x; + } + + var acc: u8 = 0; + for (computed_tag) |_, p| { + acc |= (computed_tag[p] ^ tag[p]); + } + if (acc != 0) { + mem.set(u8, m, 0xaa); + return error.AuthenticationFailed; + } + + mem.writeIntBig(u32, j[nonce_length..][0..4], 2); + modes.ctr(@TypeOf(aes), aes, m, c, j, builtin.Endian.Big); + } + }; +} + +const htest = @import("test.zig"); +const testing = std.testing; + +test "AES256GCM - Empty message and no associated data" { + const key: [AES256GCM.key_length]u8 = [_]u8{0x69} ** AES256GCM.key_length; + const nonce: [AES256GCM.nonce_length]u8 = [_]u8{0x42} ** AES256GCM.nonce_length; + const ad = ""; + const m = ""; + var c: [m.len]u8 = undefined; + var m2: [m.len]u8 = undefined; + var tag: [AES256GCM.tag_length]u8 = undefined; + + AES256GCM.encrypt(&c, &tag, m, ad, nonce, key); + htest.assertEqual("6b6ff610a16fa4cd59f1fb7903154e92", &tag); +} + +test "AES256GCM - Associated data only" { + const key: [AES256GCM.key_length]u8 = [_]u8{0x69} ** AES256GCM.key_length; + const nonce: [AES256GCM.nonce_length]u8 = [_]u8{0x42} ** AES256GCM.nonce_length; + const m = ""; + const ad = "Test with associated data"; + var c: [m.len]u8 = undefined; + var tag: [AES256GCM.tag_length]u8 = undefined; + + AES256GCM.encrypt(&c, &tag, m, ad, nonce, key); + htest.assertEqual("262ed164c2dfb26e080a9d108dd9dd4c", &tag); +} + +test "AES256GCM - Message only" { + const key: [AES256GCM.key_length]u8 = [_]u8{0x69} ** AES256GCM.key_length; + const nonce: [AES256GCM.nonce_length]u8 = [_]u8{0x42} ** AES256GCM.nonce_length; + const m = "Test with message only"; + const ad = ""; + var c: [m.len]u8 = undefined; + var m2: [m.len]u8 = undefined; + var tag: [AES256GCM.tag_length]u8 = undefined; + + AES256GCM.encrypt(&c, &tag, m, ad, nonce, key); + try AES256GCM.decrypt(&m2, &c, tag, ad, nonce, key); + testing.expectEqualSlices(u8, m[0..], m2[0..]); + + htest.assertEqual("5ca1642d90009fea33d01f78cf6eefaf01d539472f7c", &c); + htest.assertEqual("07cd7fc9103e2f9e9bf2dfaa319caff4", &tag); +} + +test "AES256GCM - Message and associated data" { + const key: [AES256GCM.key_length]u8 = [_]u8{0x69} ** AES256GCM.key_length; + const nonce: [AES256GCM.nonce_length]u8 = [_]u8{0x42} ** AES256GCM.nonce_length; + const m = "Test with message"; + const ad = "Test with associated data"; + var c: [m.len]u8 = undefined; + var m2: [m.len]u8 = undefined; + var tag: [AES256GCM.tag_length]u8 = undefined; + + AES256GCM.encrypt(&c, &tag, m, ad, nonce, key); + try AES256GCM.decrypt(&m2, &c, tag, ad, nonce, key); + testing.expectEqualSlices(u8, m[0..], m2[0..]); + + htest.assertEqual("5ca1642d90009fea33d01f78cf6eefaf01", &c); + htest.assertEqual("64accec679d444e2373bd9f6796c0d2c", &tag); +} diff --git a/lib/std/crypto/benchmark.zig b/lib/std/crypto/benchmark.zig index d0ff29e896..1db3a1e870 100644 --- a/lib/std/crypto/benchmark.zig +++ b/lib/std/crypto/benchmark.zig @@ -152,6 +152,8 @@ const aeads = [_]Crypto{ Crypto{ .ty = crypto.aead.Gimli, .name = "gimli-aead" }, Crypto{ .ty = crypto.aead.AEGIS128L, .name = "aegis-128l" }, Crypto{ .ty = crypto.aead.AEGIS256, .name = "aegis-256" }, + Crypto{ .ty = crypto.aead.AES128GCM, .name = "aes128-gcm" }, + Crypto{ .ty = crypto.aead.AES256GCM, .name = "aes256-gcm" }, }; pub fn benchmarkAead(comptime Aead: anytype, comptime bytes: comptime_int) !u64 { diff --git a/lib/std/crypto/ghash.zig b/lib/std/crypto/ghash.zig index 6a1bf7c186..04bc6a8275 100644 --- a/lib/std/crypto/ghash.zig +++ b/lib/std/crypto/ghash.zig @@ -250,7 +250,7 @@ pub const Ghash = struct { } mb = mb[want..]; st.leftover += want; - if (st.leftover > block_size) { + if (st.leftover < block_size) { return; } st.blocks(&st.buf); @@ -269,14 +269,21 @@ pub const Ghash = struct { } } - pub fn final(st: *Ghash, out: *[mac_length]u8) void { - if (st.leftover > 0) { - var i = st.leftover; - while (i < block_size) : (i += 1) { - st.buf[i] = 0; - } - st.blocks(&st.buf); + /// Zero-pad to align the next input to the first byte of a block + pub fn pad(st: *Ghash) void { + if (st.leftover == 0) { + return; } + var i = st.leftover; + while (i < block_size) : (i += 1) { + st.buf[i] = 0; + } + st.blocks(&st.buf); + st.leftover = 0; + } + + pub fn final(st: *Ghash, out: *[mac_length]u8) void { + st.pad(); mem.writeIntBig(u64, out[0..8], st.y1); mem.writeIntBig(u64, out[8..16], st.y0); diff --git a/lib/std/crypto/poly1305.zig b/lib/std/crypto/poly1305.zig index 31d1d6ba5a..c6613f64ba 100644 --- a/lib/std/crypto/poly1305.zig +++ b/lib/std/crypto/poly1305.zig @@ -91,7 +91,7 @@ pub const Poly1305 = struct { } mb = mb[want..]; st.leftover += want; - if (st.leftover > block_size) { + if (st.leftover < block_size) { return; } st.blocks(&st.buf, false); @@ -114,6 +114,19 @@ pub const Poly1305 = struct { } } + /// Zero-pad to align the next input to the first byte of a block + pub fn pad(st: *Poly1305) void { + if (st.leftover == 0) { + return; + } + var i = st.leftover; + while (i < block_size) : (i += 1) { + st.buf[i] = 0; + } + st.blocks(&st.buf); + st.leftover = 0; + } + pub fn final(st: *Poly1305, out: *[mac_length]u8) void { if (st.leftover > 0) { var i = st.leftover;