| 1 | const std = @import("std"); |
| 2 | const assert = std.debug.assert; |
| 3 | const crypto = std.crypto; |
| 4 | const debug = std.debug; |
| 5 | const Ghash = std.crypto.onetimeauth.Ghash; |
| 6 | const math = std.math; |
| 7 | const mem = std.mem; |
| 8 | const modes = crypto.core.modes; |
| 9 | const AuthenticationError = crypto.errors.AuthenticationError; |
| 10 | |
| 11 | pub const Aes128Gcm = AesGcm(crypto.core.aes.Aes128); |
| 12 | pub const Aes256Gcm = AesGcm(crypto.core.aes.Aes256); |
| 13 | |
| 14 | fn 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 | |
| 115 | const htest = @import("test.zig"); |
| 116 | const testing = std.testing; |
| 117 | |
| 118 | test "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 | |
| 130 | test "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 | |
| 142 | test "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 | |
| 159 | test "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 | } |