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