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}