master
  1const std = @import("std");
  2const assert = std.debug.assert;
  3const crypto = std.crypto;
  4const debug = std.debug;
  5const Ghash = std.crypto.onetimeauth.Ghash;
  6const math = std.math;
  7const mem = std.mem;
  8const modes = crypto.core.modes;
  9const AuthenticationError = crypto.errors.AuthenticationError;
 10
 11pub const Aes128Gcm = AesGcm(crypto.core.aes.Aes128);
 12pub const Aes256Gcm = AesGcm(crypto.core.aes.Aes256);
 13
 14fn AesGcm(comptime Aes: anytype) type {
 15    debug.assert(Aes.block.block_length == 16);
 16
 17    return struct {
 18        pub const tag_length = 16;
 19        pub const nonce_length = 12;
 20        pub const key_length = Aes.key_bits / 8;
 21
 22        const zeros = [_]u8{0} ** 16;
 23
 24        /// `c`: The ciphertext buffer to write the encrypted data to.
 25        /// `tag`: The authentication tag buffer to write the computed tag to.
 26        /// `m`: The plaintext message to encrypt.
 27        /// `ad`: The associated data to authenticate.
 28        /// `npub`: The nonce to use for encryption.
 29        /// `key`: The encryption key.
 30        pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) void {
 31            debug.assert(c.len == m.len);
 32            debug.assert(m.len <= 16 * ((1 << 32) - 2));
 33
 34            const aes = Aes.initEnc(key);
 35            var h: [16]u8 = undefined;
 36            aes.encrypt(&h, &zeros);
 37
 38            var t: [16]u8 = undefined;
 39            var j: [16]u8 = undefined;
 40            j[0..nonce_length].* = npub;
 41            mem.writeInt(u32, j[nonce_length..][0..4], 1, .big);
 42            aes.encrypt(&t, &j);
 43
 44            const block_count = (math.divCeil(usize, ad.len, Ghash.block_length) catch unreachable) + (math.divCeil(usize, c.len, Ghash.block_length) catch unreachable) + 1;
 45            var mac = Ghash.initForBlockCount(&h, block_count);
 46            mac.update(ad);
 47            mac.pad();
 48
 49            mem.writeInt(u32, j[nonce_length..][0..4], 2, .big);
 50            modes.ctr(@TypeOf(aes), aes, c, m, j, .big);
 51            mac.update(c[0..m.len][0..]);
 52            mac.pad();
 53
 54            var final_block = h;
 55            mem.writeInt(u64, final_block[0..8], @as(u64, ad.len) * 8, .big);
 56            mem.writeInt(u64, final_block[8..16], @as(u64, m.len) * 8, .big);
 57            mac.update(&final_block);
 58            mac.final(tag);
 59            for (t, 0..) |x, i| {
 60                tag[i] ^= x;
 61            }
 62        }
 63
 64        /// `m`: Message
 65        /// `c`: Ciphertext
 66        /// `tag`: Authentication tag
 67        /// `ad`: Associated data
 68        /// `npub`: Public nonce
 69        /// `k`: Private key
 70        /// Asserts `c.len == m.len`.
 71        ///
 72        /// Contents of `m` are undefined if an error is returned.
 73        pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) AuthenticationError!void {
 74            assert(c.len == m.len);
 75
 76            const aes = Aes.initEnc(key);
 77            var h: [16]u8 = undefined;
 78            aes.encrypt(&h, &zeros);
 79
 80            var t: [16]u8 = undefined;
 81            var j: [16]u8 = undefined;
 82            j[0..nonce_length].* = npub;
 83            mem.writeInt(u32, j[nonce_length..][0..4], 1, .big);
 84            aes.encrypt(&t, &j);
 85
 86            const block_count = (math.divCeil(usize, ad.len, Ghash.block_length) catch unreachable) + (math.divCeil(usize, c.len, Ghash.block_length) catch unreachable) + 1;
 87            var mac = Ghash.initForBlockCount(&h, block_count);
 88            mac.update(ad);
 89            mac.pad();
 90
 91            mac.update(c);
 92            mac.pad();
 93
 94            var final_block = h;
 95            mem.writeInt(u64, final_block[0..8], @as(u64, ad.len) * 8, .big);
 96            mem.writeInt(u64, final_block[8..16], @as(u64, m.len) * 8, .big);
 97            mac.update(&final_block);
 98            var computed_tag: [Ghash.mac_length]u8 = undefined;
 99            mac.final(&computed_tag);
100            for (t, 0..) |x, i| {
101                computed_tag[i] ^= x;
102            }
103
104            const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag);
105            if (!verify) {
106                crypto.secureZero(u8, &computed_tag);
107                @memset(m, undefined);
108                return error.AuthenticationFailed;
109            }
110
111            mem.writeInt(u32, j[nonce_length..][0..4], 2, .big);
112            modes.ctr(@TypeOf(aes), aes, m, c, j, .big);
113        }
114    };
115}
116
117const htest = @import("test.zig");
118const testing = std.testing;
119
120test "Aes256Gcm - Empty message and no associated data" {
121    const key: [Aes256Gcm.key_length]u8 = [_]u8{0x69} ** Aes256Gcm.key_length;
122    const nonce: [Aes256Gcm.nonce_length]u8 = [_]u8{0x42} ** Aes256Gcm.nonce_length;
123    const ad = "";
124    const m = "";
125    var c: [m.len]u8 = undefined;
126    var tag: [Aes256Gcm.tag_length]u8 = undefined;
127
128    Aes256Gcm.encrypt(&c, &tag, m, ad, nonce, key);
129    try htest.assertEqual("6b6ff610a16fa4cd59f1fb7903154e92", &tag);
130}
131
132test "Aes256Gcm - Associated data only" {
133    const key: [Aes256Gcm.key_length]u8 = [_]u8{0x69} ** Aes256Gcm.key_length;
134    const nonce: [Aes256Gcm.nonce_length]u8 = [_]u8{0x42} ** Aes256Gcm.nonce_length;
135    const m = "";
136    const ad = "Test with associated data";
137    var c: [m.len]u8 = undefined;
138    var tag: [Aes256Gcm.tag_length]u8 = undefined;
139
140    Aes256Gcm.encrypt(&c, &tag, m, ad, nonce, key);
141    try htest.assertEqual("262ed164c2dfb26e080a9d108dd9dd4c", &tag);
142}
143
144test "Aes256Gcm - Message only" {
145    const key: [Aes256Gcm.key_length]u8 = [_]u8{0x69} ** Aes256Gcm.key_length;
146    const nonce: [Aes256Gcm.nonce_length]u8 = [_]u8{0x42} ** Aes256Gcm.nonce_length;
147    const m = "Test with message only";
148    const ad = "";
149    var c: [m.len]u8 = undefined;
150    var m2: [m.len]u8 = undefined;
151    var tag: [Aes256Gcm.tag_length]u8 = undefined;
152
153    Aes256Gcm.encrypt(&c, &tag, m, ad, nonce, key);
154    try Aes256Gcm.decrypt(&m2, &c, tag, ad, nonce, key);
155    try testing.expectEqualSlices(u8, m[0..], m2[0..]);
156
157    try htest.assertEqual("5ca1642d90009fea33d01f78cf6eefaf01d539472f7c", &c);
158    try htest.assertEqual("07cd7fc9103e2f9e9bf2dfaa319caff4", &tag);
159}
160
161test "Aes256Gcm - Message and associated data" {
162    const key: [Aes256Gcm.key_length]u8 = [_]u8{0x69} ** Aes256Gcm.key_length;
163    const nonce: [Aes256Gcm.nonce_length]u8 = [_]u8{0x42} ** Aes256Gcm.nonce_length;
164    const m = "Test with message";
165    const ad = "Test with associated data";
166    var c: [m.len]u8 = undefined;
167    var m2: [m.len]u8 = undefined;
168    var tag: [Aes256Gcm.tag_length]u8 = undefined;
169
170    Aes256Gcm.encrypt(&c, &tag, m, ad, nonce, key);
171    try Aes256Gcm.decrypt(&m2, &c, tag, ad, nonce, key);
172    try testing.expectEqualSlices(u8, m[0..], m2[0..]);
173
174    try htest.assertEqual("5ca1642d90009fea33d01f78cf6eefaf01", &c);
175    try htest.assertEqual("64accec679d444e2373bd9f6796c0d2c", &tag);
176}