Heavy lifting for GCM implementation.
This commit is contained in:
5
src/error.zig
Normal file
5
src/error.zig
Normal file
@ -0,0 +1,5 @@
|
||||
pub const CryptoError = error{
|
||||
MessageLengthLimitExceeded,
|
||||
BufferSizeMismatch,
|
||||
InvalidIVLength,
|
||||
};
|
||||
@ -1,2 +1,6 @@
|
||||
pub const aes = @import("aes.zig");
|
||||
pub const des = @import("des.zig");
|
||||
|
||||
pub const operation_mode = struct {
|
||||
pub const gcm = @import("mode_gcm.zig");
|
||||
};
|
||||
|
||||
177
src/primitive/blockcipher/mode_gcm.zig
Normal file
177
src/primitive/blockcipher/mode_gcm.zig
Normal file
@ -0,0 +1,177 @@
|
||||
const std = @import("std");
|
||||
|
||||
const CryptoError = @import("../../error.zig").CryptoError;
|
||||
|
||||
const Gcm128Ctx = struct {
|
||||
const BLOCK_SIZE = 128 / 8;
|
||||
|
||||
cipher_key_size: comptime_int,
|
||||
cipher_encrypt_fn: fn (*const [.cipher_key_size]u8, *const [BLOCK_SIZE]u8, *[BLOCK_SIZE]u8) void,
|
||||
key: [.cipher_key_size]u8,
|
||||
counter: [BLOCK_SIZE]u8,
|
||||
ghash_x: [BLOCK_SIZE]u8,
|
||||
text_buffer: [BLOCK_SIZE]u8,
|
||||
text_length: u64,
|
||||
aad_length: u64,
|
||||
};
|
||||
|
||||
pub fn gcm_128_new(
|
||||
cipher_key_size: comptime_int,
|
||||
cipher_encrypt_fn: fn (key: *const [cipher_key_size]u8, block_in: *const [Gcm128Ctx.BLOCK_SIZE]u8, block_out: *[Gcm128Ctx.BLOCK_SIZE]u8) void,
|
||||
key: *const [cipher_key_size]u8,
|
||||
iv: []const u8,
|
||||
) Gcm128Ctx {
|
||||
if (iv.len == 0) {
|
||||
return CryptoError.InvalidIVLength;
|
||||
}
|
||||
|
||||
var ctx = Gcm128Ctx{
|
||||
.cipher_key_size = cipher_key_size,
|
||||
.cipher_encrypt_fn = cipher_encrypt_fn,
|
||||
.key = undefined,
|
||||
.counter = undefined,
|
||||
.ghash_x = undefined,
|
||||
.text_buffer = undefined,
|
||||
.text_length = 0,
|
||||
.aad_length = 0,
|
||||
};
|
||||
|
||||
// Set the encryption/decryption key.
|
||||
@memcpy(&ctx.key, key);
|
||||
|
||||
// Set the counter initial value (`Y_0`).
|
||||
if (iv.len == 96 / 8) {
|
||||
@memcpy(ctx.counter[0 .. 96 / 8], iv);
|
||||
@memset(ctx.counter[(96 / 8)..(ctx.counter.len - 1)], 0x00);
|
||||
ctx.counter[ctx.counter.len - 1] = 0x01;
|
||||
} else {
|
||||
gcm_128_ghash(&ctx.h, &.{}, iv, &ctx.counter);
|
||||
}
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
pub fn gcm_128_destroy(ctx: *Gcm128Ctx) void {
|
||||
@memset(&ctx, 0);
|
||||
}
|
||||
|
||||
// Important: this function must only be called at most once
|
||||
// AFTER the context is initialized but BEFORE any data is en/decrypted.
|
||||
// TODO: Enforce at API level.
|
||||
pub fn gcm_128_authenticate_data_once(
|
||||
ctx: *Gcm128Ctx,
|
||||
data: []const u8,
|
||||
) !void {
|
||||
// The maximum bit length of the AAD is 2^64.
|
||||
if (data.len > (1 << 61))
|
||||
return CryptoError.MessageLengthLimitExceeded;
|
||||
|
||||
// Compute `H` temporarily.
|
||||
const h = std.mem.zeroes([Gcm128Ctx.BLOCK_SIZE]u8);
|
||||
ctx.cipher_encrypt_fn(&ctx.key, &h, &h);
|
||||
|
||||
// Since `H` depends on the key, destroy it before leaving the function.
|
||||
defer @memset(&h, 0);
|
||||
|
||||
// Compute and store `X_m` where m is the length in blocks of the AAD.
|
||||
gcm_128_ghash_padded_chunk(&std.mem.zeroes([]u8), &h, data, &ctx.ghash_x);
|
||||
|
||||
// Store the AAD length for the tag computation.
|
||||
ctx.aad_length = data.len;
|
||||
}
|
||||
|
||||
pub fn gcm_128_encrypt(ctx: *Gcm128Ctx, plaintext: []const u8, ciphertext: []u8) void {
|
||||
if (plaintext.len != ciphertext.len)
|
||||
return CryptoError.BufferSizeMismatch;
|
||||
|
||||
// TODO
|
||||
_ = .{ ctx, plaintext, ciphertext };
|
||||
}
|
||||
|
||||
pub fn gcm_128_encrypt_final(ctx: *Gcm128Ctx, tag_length: u4, tag: []u8) void {
|
||||
// TODO
|
||||
_ = .{ ctx, tag_length, tag };
|
||||
}
|
||||
|
||||
pub fn gcm_128_incr(ctr: *[Gcm128Ctx.BLOCK_SIZE]u8) void {
|
||||
const val32 = std.mem.readInt(u32, ctr[ctr.len - 4 .. ctr.len], .big);
|
||||
std.mem.writeInt(u32, ctr[ctr.len - 4 .. ctr.len], val32 +% 1, .big);
|
||||
}
|
||||
|
||||
pub fn gcm_128_ghash_padded_chunk(
|
||||
iv: *const [Gcm128Ctx.BLOCK_SIZE]u8,
|
||||
h: *const [Gcm128Ctx.BLOCK_SIZE]u8,
|
||||
chunk: []const u8,
|
||||
out: *[Gcm128Ctx.BLOCK_SIZE]u8,
|
||||
) void {
|
||||
const bs = Gcm128Ctx.BLOCK_SIZE;
|
||||
|
||||
const m = chunk.len / bs;
|
||||
// Note: This definition of `v` is different from the one in the standard,
|
||||
// it is the number of residue *bytes* in the last block, not bits.
|
||||
const v = chunk.len % bs;
|
||||
|
||||
var x_i: [Gcm128Ctx.BLOCK_SIZE]u8 = *iv;
|
||||
var i: usize = 0;
|
||||
|
||||
while (i < m) : (i += 1) {
|
||||
xor(bs, x_i[0..], chunk[i * bs .. (i + 1) * bs], &x_i);
|
||||
gcm_128_mult(&x_i, h, &x_i);
|
||||
}
|
||||
|
||||
var padded: [Gcm128Ctx.BLOCK_SIZE]u8 = undefined;
|
||||
@memcpy(padded[0..v], chunk[i * bs ..]);
|
||||
@memset(padded[v..], 0x00);
|
||||
|
||||
xor(bs, x_i[0..], padded[0..], &x_i);
|
||||
gcm_128_mult(&x_i, h, out);
|
||||
}
|
||||
|
||||
pub fn gcm_128_ghash(
|
||||
h: *const [Gcm128Ctx.BLOCK_SIZE]u8,
|
||||
aad_chunk: []const u8,
|
||||
ciphertext_chunk: []const u8,
|
||||
out: *[Gcm128Ctx.BLOCK_SIZE]u8,
|
||||
) void {
|
||||
gcm_128_ghash_padded_chunk(&std.mem.zeroes([Gcm128Ctx.BLOCK_SIZE]u8), h, aad_chunk, out);
|
||||
gcm_128_ghash_padded_chunk(out, h, ciphertext_chunk, out);
|
||||
|
||||
const bs = Gcm128Ctx.BLOCK_SIZE;
|
||||
|
||||
var lengths: [bs]u8 = undefined;
|
||||
std.mem.writeInt(u64, lengths[0 .. bs / 2], aad_chunk.len * 8, .big);
|
||||
std.mem.writeInt(u64, lengths[bs / 2 ..], ciphertext_chunk.len * 8, .big);
|
||||
|
||||
xor(bs, lengths[0..], out[0..], out);
|
||||
gcm_128_mult(out, h, out);
|
||||
}
|
||||
|
||||
// TODO: Naive algorithm: implement optimized versions with table lookups.
|
||||
pub fn gcm_128_mult(
|
||||
a: *const [128 / 8]u8,
|
||||
b: *const [128 / 8]u8,
|
||||
out: *[128 / 8]u8,
|
||||
) void {
|
||||
const gcm_128_irreducible_R: u128 = 0xe100_0000_0000_0000_0000_0000_0000_0000;
|
||||
|
||||
var z: u128 = 0;
|
||||
var v = std.mem.readInt(u128, a, .big);
|
||||
const y = std.mem.readInt(u128, b, .big);
|
||||
|
||||
for (0..128) |i| {
|
||||
if ((y >> (127 - i)) & 1 == 1)
|
||||
z ^= v;
|
||||
|
||||
if (v & 1 == 0)
|
||||
v = v >> 1
|
||||
else
|
||||
v = (v >> 1) ^ gcm_128_irreducible_R;
|
||||
}
|
||||
|
||||
std.mem.writeInt(u128, out, z, .big);
|
||||
}
|
||||
|
||||
fn xor(L: comptime_int, a: *const [L]u8, b: *const [L]u8, out: *[L]u8) void {
|
||||
for (0..L) |i|
|
||||
out[i] = a[i] ^ b[i];
|
||||
}
|
||||
Reference in New Issue
Block a user