mirror of
https://github.com/ziglang/zig.git
synced 2024-11-27 23:52:31 +00:00
153ba46a5b
These systems write the number of *bits* of their inputs as a u64. However if `@sizeOf(usize) == 4`, an input message or associated data whose size is > 512 MiB could overflow. On 64-bit systems, it is safe to assume that no machine has more than 2 EiB of memory.
171 lines
6.4 KiB
Zig
171 lines
6.4 KiB
Zig
const std = @import("std");
|
|
const assert = std.debug.assert;
|
|
const crypto = std.crypto;
|
|
const debug = std.debug;
|
|
const Ghash = std.crypto.onetimeauth.Ghash;
|
|
const math = std.math;
|
|
const mem = std.mem;
|
|
const modes = crypto.core.modes;
|
|
const AuthenticationError = crypto.errors.AuthenticationError;
|
|
|
|
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_length == 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;
|
|
j[0..nonce_length].* = npub;
|
|
mem.writeInt(u32, j[nonce_length..][0..4], 1, .big);
|
|
aes.encrypt(&t, &j);
|
|
|
|
const block_count = (math.divCeil(usize, ad.len, Ghash.block_length) catch unreachable) + (math.divCeil(usize, c.len, Ghash.block_length) catch unreachable) + 1;
|
|
var mac = Ghash.initForBlockCount(&h, block_count);
|
|
mac.update(ad);
|
|
mac.pad();
|
|
|
|
mem.writeInt(u32, j[nonce_length..][0..4], 2, .big);
|
|
modes.ctr(@TypeOf(aes), aes, c, m, j, .big);
|
|
mac.update(c[0..m.len][0..]);
|
|
mac.pad();
|
|
|
|
var final_block = h;
|
|
mem.writeInt(u64, final_block[0..8], @as(u64, ad.len) * 8, .big);
|
|
mem.writeInt(u64, final_block[8..16], @as(u64, m.len) * 8, .big);
|
|
mac.update(&final_block);
|
|
mac.final(tag);
|
|
for (t, 0..) |x, i| {
|
|
tag[i] ^= x;
|
|
}
|
|
}
|
|
|
|
/// `m`: Message
|
|
/// `c`: Ciphertext
|
|
/// `tag`: Authentication tag
|
|
/// `ad`: Associated data
|
|
/// `npub`: Public nonce
|
|
/// `k`: Private key
|
|
/// Asserts `c.len == m.len`.
|
|
///
|
|
/// Contents of `m` are undefined if an error is returned.
|
|
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 {
|
|
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;
|
|
j[0..nonce_length].* = npub;
|
|
mem.writeInt(u32, j[nonce_length..][0..4], 1, .big);
|
|
aes.encrypt(&t, &j);
|
|
|
|
const block_count = (math.divCeil(usize, ad.len, Ghash.block_length) catch unreachable) + (math.divCeil(usize, c.len, Ghash.block_length) catch unreachable) + 1;
|
|
var mac = Ghash.initForBlockCount(&h, block_count);
|
|
mac.update(ad);
|
|
mac.pad();
|
|
|
|
mac.update(c);
|
|
mac.pad();
|
|
|
|
var final_block = h;
|
|
mem.writeInt(u64, final_block[0..8], @as(u64, ad.len) * 8, .big);
|
|
mem.writeInt(u64, final_block[8..16], @as(u64, m.len) * 8, .big);
|
|
mac.update(&final_block);
|
|
var computed_tag: [Ghash.mac_length]u8 = undefined;
|
|
mac.final(&computed_tag);
|
|
for (t, 0..) |x, i| {
|
|
computed_tag[i] ^= x;
|
|
}
|
|
|
|
const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag);
|
|
if (!verify) {
|
|
crypto.utils.secureZero(u8, &computed_tag);
|
|
@memset(m, undefined);
|
|
return error.AuthenticationFailed;
|
|
}
|
|
|
|
mem.writeInt(u32, j[nonce_length..][0..4], 2, .big);
|
|
modes.ctr(@TypeOf(aes), aes, m, c, j, .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 tag: [Aes256Gcm.tag_length]u8 = undefined;
|
|
|
|
Aes256Gcm.encrypt(&c, &tag, m, ad, nonce, key);
|
|
try 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);
|
|
try 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);
|
|
try testing.expectEqualSlices(u8, m[0..], m2[0..]);
|
|
|
|
try htest.assertEqual("5ca1642d90009fea33d01f78cf6eefaf01d539472f7c", &c);
|
|
try 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);
|
|
try testing.expectEqualSlices(u8, m[0..], m2[0..]);
|
|
|
|
try htest.assertEqual("5ca1642d90009fea33d01f78cf6eefaf01", &c);
|
|
try htest.assertEqual("64accec679d444e2373bd9f6796c0d2c", &tag);
|
|
}
|