2021-10-05 07:47:27 +01:00
|
|
|
//! Gimli is a 384-bit permutation designed to achieve high security with high
|
|
|
|
//! performance across a broad range of platforms, including 64-bit Intel/AMD
|
|
|
|
//! server CPUs, 64-bit and 32-bit ARM smartphone CPUs, 32-bit ARM
|
|
|
|
//! microcontrollers, 8-bit AVR microcontrollers, FPGAs, ASICs without
|
|
|
|
//! side-channel protection, and ASICs with side-channel protection.
|
|
|
|
//!
|
|
|
|
//! https://gimli.cr.yp.to/
|
|
|
|
//! https://csrc.nist.gov/CSRC/media/Projects/Lightweight-Cryptography/documents/round-1/spec-doc/gimli-spec.pdf
|
2019-06-15 18:02:47 +01:00
|
|
|
|
|
|
|
const std = @import("../std.zig");
|
2021-10-05 07:47:27 +01:00
|
|
|
const builtin = @import("builtin");
|
2019-06-15 18:02:47 +01:00
|
|
|
const mem = std.mem;
|
|
|
|
const math = std.math;
|
|
|
|
const debug = std.debug;
|
|
|
|
const assert = std.debug.assert;
|
|
|
|
const testing = std.testing;
|
|
|
|
const htest = @import("test.zig");
|
2020-10-11 12:05:59 +01:00
|
|
|
const Vector = std.meta.Vector;
|
2021-04-20 18:57:27 +01:00
|
|
|
const AuthenticationError = std.crypto.errors.AuthenticationError;
|
2019-06-15 18:02:47 +01:00
|
|
|
|
|
|
|
pub const State = struct {
|
|
|
|
pub const BLOCKBYTES = 48;
|
|
|
|
pub const RATE = 16;
|
|
|
|
|
2020-10-11 12:05:59 +01:00
|
|
|
data: [BLOCKBYTES / 4]u32 align(16),
|
2019-06-15 18:02:47 +01:00
|
|
|
|
|
|
|
const Self = @This();
|
|
|
|
|
std.rand: set DefaultCsprng to Gimli, and require a larger seed
`DefaultCsprng` is documented as a cryptographically secure RNG.
While `ISAAC` is a CSPRNG, the variant we have, `ISAAC64` is not.
A 64 bit seed is a bit small to satisfy that claim.
We also saw it being used with the current date as a seed, that
also defeats the point of a CSPRNG.
Set `DefaultCsprng` to `Gimli` instead of `ISAAC64`, rename
the parameter from `init_s` to `secret_seed` + add a comment to
clarify what kind of seed is expected here.
Instead of directly touching the internals of the Gimli implementation
(which can change/be architecture-specific), add an `init()` function
to the state.
Our Gimli-based CSPRNG was also not backtracking resistant. Gimli
is a permutation; it can be reverted. So, if the state was ever leaked,
future secrets, but also all the previously generated ones could be
recovered. Clear the rate after a squeeze in order to prevent this.
Finally, a dumb test was added just to exercise `DefaultCsprng` since
we don't use it anywhere.
2020-10-09 13:33:16 +01:00
|
|
|
pub fn init(initial_state: [State.BLOCKBYTES]u8) Self {
|
|
|
|
var data: [BLOCKBYTES / 4]u32 = undefined;
|
|
|
|
var i: usize = 0;
|
|
|
|
while (i < State.BLOCKBYTES) : (i += 4) {
|
2020-11-03 01:01:48 +00:00
|
|
|
data[i / 4] = mem.readIntNative(u32, initial_state[i..][0..4]);
|
std.rand: set DefaultCsprng to Gimli, and require a larger seed
`DefaultCsprng` is documented as a cryptographically secure RNG.
While `ISAAC` is a CSPRNG, the variant we have, `ISAAC64` is not.
A 64 bit seed is a bit small to satisfy that claim.
We also saw it being used with the current date as a seed, that
also defeats the point of a CSPRNG.
Set `DefaultCsprng` to `Gimli` instead of `ISAAC64`, rename
the parameter from `init_s` to `secret_seed` + add a comment to
clarify what kind of seed is expected here.
Instead of directly touching the internals of the Gimli implementation
(which can change/be architecture-specific), add an `init()` function
to the state.
Our Gimli-based CSPRNG was also not backtracking resistant. Gimli
is a permutation; it can be reverted. So, if the state was ever leaked,
future secrets, but also all the previously generated ones could be
recovered. Clear the rate after a squeeze in order to prevent this.
Finally, a dumb test was added just to exercise `DefaultCsprng` since
we don't use it anywhere.
2020-10-09 13:33:16 +01:00
|
|
|
}
|
|
|
|
return Self{ .data = data };
|
|
|
|
}
|
|
|
|
|
2020-03-30 19:23:22 +01:00
|
|
|
/// TODO follow the span() convention instead of having this and `toSliceConst`
|
2020-10-26 10:03:12 +00:00
|
|
|
pub fn toSlice(self: *Self) *[BLOCKBYTES]u8 {
|
|
|
|
return mem.asBytes(&self.data);
|
2019-06-15 18:02:47 +01:00
|
|
|
}
|
|
|
|
|
2020-03-30 19:23:22 +01:00
|
|
|
/// TODO follow the span() convention instead of having this and `toSlice`
|
2020-10-26 10:03:12 +00:00
|
|
|
pub fn toSliceConst(self: *const Self) *const [BLOCKBYTES]u8 {
|
|
|
|
return mem.asBytes(&self.data);
|
2019-06-15 18:02:47 +01:00
|
|
|
}
|
|
|
|
|
2021-05-20 16:07:06 +01:00
|
|
|
inline fn endianSwap(self: *Self) void {
|
2020-11-02 13:58:43 +00:00
|
|
|
for (self.data) |*w| {
|
|
|
|
w.* = mem.littleToNative(u32, w.*);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-29 13:01:08 +01:00
|
|
|
fn permute_unrolled(self: *Self) void {
|
2020-11-02 13:58:43 +00:00
|
|
|
self.endianSwap();
|
2019-06-15 18:02:47 +01:00
|
|
|
const state = &self.data;
|
2020-09-28 22:23:32 +01:00
|
|
|
comptime var round = @as(u32, 24);
|
|
|
|
inline while (round > 0) : (round -= 1) {
|
2019-11-07 04:25:57 +00:00
|
|
|
var column = @as(usize, 0);
|
2019-06-15 18:02:47 +01:00
|
|
|
while (column < 4) : (column += 1) {
|
|
|
|
const x = math.rotl(u32, state[column], 24);
|
|
|
|
const y = math.rotl(u32, state[4 + column], 9);
|
|
|
|
const z = state[8 + column];
|
|
|
|
state[8 + column] = ((x ^ (z << 1)) ^ ((y & z) << 2));
|
|
|
|
state[4 + column] = ((y ^ x) ^ ((x | z) << 1));
|
|
|
|
state[column] = ((z ^ y) ^ ((x & y) << 3));
|
|
|
|
}
|
|
|
|
switch (round & 3) {
|
|
|
|
0 => {
|
|
|
|
mem.swap(u32, &state[0], &state[1]);
|
|
|
|
mem.swap(u32, &state[2], &state[3]);
|
|
|
|
state[0] ^= round | 0x9e377900;
|
|
|
|
},
|
|
|
|
2 => {
|
|
|
|
mem.swap(u32, &state[0], &state[2]);
|
|
|
|
mem.swap(u32, &state[1], &state[3]);
|
|
|
|
},
|
|
|
|
else => {},
|
|
|
|
}
|
|
|
|
}
|
2020-11-02 13:58:43 +00:00
|
|
|
self.endianSwap();
|
2019-06-15 18:02:47 +01:00
|
|
|
}
|
|
|
|
|
2020-09-29 13:01:08 +01:00
|
|
|
fn permute_small(self: *Self) void {
|
2020-11-02 13:58:43 +00:00
|
|
|
self.endianSwap();
|
2020-09-29 12:09:11 +01:00
|
|
|
const state = &self.data;
|
|
|
|
var round = @as(u32, 24);
|
|
|
|
while (round > 0) : (round -= 1) {
|
|
|
|
var column = @as(usize, 0);
|
|
|
|
while (column < 4) : (column += 1) {
|
|
|
|
const x = math.rotl(u32, state[column], 24);
|
|
|
|
const y = math.rotl(u32, state[4 + column], 9);
|
|
|
|
const z = state[8 + column];
|
|
|
|
state[8 + column] = ((x ^ (z << 1)) ^ ((y & z) << 2));
|
|
|
|
state[4 + column] = ((y ^ x) ^ ((x | z) << 1));
|
|
|
|
state[column] = ((z ^ y) ^ ((x & y) << 3));
|
|
|
|
}
|
|
|
|
switch (round & 3) {
|
|
|
|
0 => {
|
|
|
|
mem.swap(u32, &state[0], &state[1]);
|
|
|
|
mem.swap(u32, &state[2], &state[3]);
|
|
|
|
state[0] ^= round | 0x9e377900;
|
|
|
|
},
|
|
|
|
2 => {
|
|
|
|
mem.swap(u32, &state[0], &state[2]);
|
|
|
|
mem.swap(u32, &state[1], &state[3]);
|
|
|
|
},
|
|
|
|
else => {},
|
|
|
|
}
|
|
|
|
}
|
2020-11-02 13:58:43 +00:00
|
|
|
self.endianSwap();
|
2020-09-29 12:09:11 +01:00
|
|
|
}
|
|
|
|
|
2020-10-11 12:05:59 +01:00
|
|
|
const Lane = Vector(4, u32);
|
|
|
|
|
2021-05-20 16:07:06 +01:00
|
|
|
inline fn shift(x: Lane, comptime n: comptime_int) Lane {
|
2020-10-11 12:05:59 +01:00
|
|
|
return x << @splat(4, @as(u5, n));
|
|
|
|
}
|
|
|
|
|
|
|
|
fn permute_vectorized(self: *Self) void {
|
2020-11-02 13:58:43 +00:00
|
|
|
self.endianSwap();
|
2020-10-11 12:05:59 +01:00
|
|
|
const state = &self.data;
|
|
|
|
var x = Lane{ state[0], state[1], state[2], state[3] };
|
|
|
|
var y = Lane{ state[4], state[5], state[6], state[7] };
|
|
|
|
var z = Lane{ state[8], state[9], state[10], state[11] };
|
|
|
|
var round = @as(u32, 24);
|
|
|
|
while (round > 0) : (round -= 1) {
|
2020-11-02 20:56:45 +00:00
|
|
|
x = math.rotl(Lane, x, 24);
|
|
|
|
y = math.rotl(Lane, y, 9);
|
2020-10-11 12:05:59 +01:00
|
|
|
const newz = x ^ shift(z, 1) ^ shift(y & z, 2);
|
|
|
|
const newy = y ^ x ^ shift(x | z, 1);
|
|
|
|
const newx = z ^ y ^ shift(x & y, 3);
|
|
|
|
x = newx;
|
|
|
|
y = newy;
|
|
|
|
z = newz;
|
|
|
|
switch (round & 3) {
|
|
|
|
0 => {
|
|
|
|
x = @shuffle(u32, x, undefined, [_]i32{ 1, 0, 3, 2 });
|
|
|
|
x[0] ^= round | 0x9e377900;
|
|
|
|
},
|
|
|
|
2 => {
|
|
|
|
x = @shuffle(u32, x, undefined, [_]i32{ 2, 3, 0, 1 });
|
|
|
|
},
|
|
|
|
else => {},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
comptime var i: usize = 0;
|
|
|
|
inline while (i < 4) : (i += 1) {
|
|
|
|
state[0 + i] = x[i];
|
|
|
|
state[4 + i] = y[i];
|
|
|
|
state[8 + i] = z[i];
|
|
|
|
}
|
2020-11-02 13:58:43 +00:00
|
|
|
self.endianSwap();
|
2020-10-11 12:05:59 +01:00
|
|
|
}
|
|
|
|
|
2021-10-05 07:47:27 +01:00
|
|
|
pub const permute = if (builtin.cpu.arch == .x86_64) impl: {
|
2020-10-11 12:05:59 +01:00
|
|
|
break :impl permute_vectorized;
|
2021-10-05 07:47:27 +01:00
|
|
|
} else if (builtin.mode == .ReleaseSmall) impl: {
|
2020-10-11 12:05:59 +01:00
|
|
|
break :impl permute_small;
|
|
|
|
} else impl: {
|
|
|
|
break :impl permute_unrolled;
|
|
|
|
};
|
2020-09-29 12:09:11 +01:00
|
|
|
|
2019-06-15 18:02:47 +01:00
|
|
|
pub fn squeeze(self: *Self, out: []u8) void {
|
2019-11-07 04:25:57 +00:00
|
|
|
var i = @as(usize, 0);
|
2019-06-15 18:02:47 +01:00
|
|
|
while (i + RATE <= out.len) : (i += RATE) {
|
|
|
|
self.permute();
|
|
|
|
mem.copy(u8, out[i..], self.toSliceConst()[0..RATE]);
|
|
|
|
}
|
|
|
|
const leftover = out.len - i;
|
|
|
|
if (leftover != 0) {
|
|
|
|
self.permute();
|
|
|
|
mem.copy(u8, out[i..], self.toSliceConst()[0..leftover]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
test "permute" {
|
|
|
|
// test vector from gimli-20170627
|
2020-11-03 08:13:14 +00:00
|
|
|
const tv_input = [3][4]u32{
|
|
|
|
[4]u32{ 0x00000000, 0x9e3779ba, 0x3c6ef37a, 0xdaa66d46 },
|
|
|
|
[4]u32{ 0x78dde724, 0x1715611a, 0xb54cdb2e, 0x53845566 },
|
|
|
|
[4]u32{ 0xf1bbcfc8, 0x8ff34a5a, 0x2e2ac522, 0xcc624026 },
|
|
|
|
};
|
2020-11-03 01:01:48 +00:00
|
|
|
var input: [48]u8 = undefined;
|
2020-11-03 08:13:14 +00:00
|
|
|
var i: usize = 0;
|
|
|
|
while (i < 12) : (i += 1) {
|
|
|
|
mem.writeIntLittle(u32, input[i * 4 ..][0..4], tv_input[i / 4][i % 4]);
|
|
|
|
}
|
|
|
|
|
2020-11-03 01:01:48 +00:00
|
|
|
var state = State.init(input);
|
2019-06-15 18:02:47 +01:00
|
|
|
state.permute();
|
2020-11-03 08:13:14 +00:00
|
|
|
|
|
|
|
const tv_output = [3][4]u32{
|
|
|
|
[4]u32{ 0xba11c85a, 0x91bad119, 0x380ce880, 0xd24c2c68 },
|
|
|
|
[4]u32{ 0x3eceffea, 0x277a921c, 0x4f73a0bd, 0xda5a9cd8 },
|
|
|
|
[4]u32{ 0x84b673f0, 0x34e52ff7, 0x9e2bef49, 0xf41bb8d6 },
|
|
|
|
};
|
|
|
|
var expected_output: [48]u8 = undefined;
|
|
|
|
i = 0;
|
|
|
|
while (i < 12) : (i += 1) {
|
|
|
|
mem.writeIntLittle(u32, expected_output[i * 4 ..][0..4], tv_output[i / 4][i % 4]);
|
|
|
|
}
|
2021-05-04 18:47:26 +01:00
|
|
|
try testing.expectEqualSlices(u8, state.toSliceConst(), expected_output[0..]);
|
2019-06-15 18:02:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
pub const Hash = struct {
|
|
|
|
state: State,
|
|
|
|
buf_off: usize,
|
|
|
|
|
2020-08-19 15:21:05 +01:00
|
|
|
pub const block_length = State.RATE;
|
2020-10-16 18:10:20 +01:00
|
|
|
pub const digest_length = 32;
|
2020-08-20 23:51:14 +01:00
|
|
|
pub const Options = struct {};
|
2020-08-19 15:21:05 +01:00
|
|
|
|
2019-06-15 18:02:47 +01:00
|
|
|
const Self = @This();
|
|
|
|
|
2020-08-20 23:51:14 +01:00
|
|
|
pub fn init(options: Options) Self {
|
2021-06-20 02:10:22 +01:00
|
|
|
_ = options;
|
2019-06-15 18:02:47 +01:00
|
|
|
return Self{
|
2020-08-19 15:21:05 +01:00
|
|
|
.state = State{ .data = [_]u32{0} ** (State.BLOCKBYTES / 4) },
|
2019-06-15 18:02:47 +01:00
|
|
|
.buf_off = 0,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Also known as 'absorb'
|
|
|
|
pub fn update(self: *Self, data: []const u8) void {
|
|
|
|
const buf = self.state.toSlice();
|
|
|
|
var in = data;
|
|
|
|
while (in.len > 0) {
|
2020-12-23 04:32:01 +00:00
|
|
|
const left = State.RATE - self.buf_off;
|
2019-06-15 18:02:47 +01:00
|
|
|
const ps = math.min(in.len, left);
|
|
|
|
for (buf[self.buf_off .. self.buf_off + ps]) |*p, i| {
|
|
|
|
p.* ^= in[i];
|
|
|
|
}
|
|
|
|
self.buf_off += ps;
|
|
|
|
in = in[ps..];
|
2020-12-23 04:32:01 +00:00
|
|
|
if (self.buf_off == State.RATE) {
|
|
|
|
self.state.permute();
|
|
|
|
self.buf_off = 0;
|
|
|
|
}
|
2019-06-15 18:02:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Finish the current hashing operation, writing the hash to `out`
|
|
|
|
///
|
|
|
|
/// From 4.9 "Application to hashing"
|
|
|
|
/// By default, Gimli-Hash provides a fixed-length output of 32 bytes
|
|
|
|
/// (the concatenation of two 16-byte blocks). However, Gimli-Hash can
|
|
|
|
/// be used as an “extendable one-way function” (XOF).
|
2020-11-02 22:56:26 +00:00
|
|
|
pub fn final(self: *Self, out: []u8) void {
|
2019-06-15 18:02:47 +01:00
|
|
|
const buf = self.state.toSlice();
|
|
|
|
|
|
|
|
// XOR 1 into the next byte of the state
|
|
|
|
buf[self.buf_off] ^= 1;
|
|
|
|
// XOR 1 into the last byte of the state, position 47.
|
|
|
|
buf[buf.len - 1] ^= 1;
|
|
|
|
|
|
|
|
self.state.squeeze(out);
|
|
|
|
}
|
2021-11-20 09:37:17 +00:00
|
|
|
|
|
|
|
pub const Error = error{};
|
|
|
|
pub const Writer = std.io.Writer(*Self, Error, write);
|
|
|
|
|
|
|
|
fn write(self: *Self, bytes: []const u8) Error!usize {
|
|
|
|
self.update(bytes);
|
|
|
|
return bytes.len;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn writer(self: *Self) Writer {
|
|
|
|
return .{ .context = self };
|
|
|
|
}
|
2019-06-15 18:02:47 +01:00
|
|
|
};
|
|
|
|
|
2020-11-02 22:56:26 +00:00
|
|
|
pub fn hash(out: []u8, in: []const u8, options: Hash.Options) void {
|
2020-08-20 23:51:14 +01:00
|
|
|
var st = Hash.init(options);
|
2019-06-15 18:02:47 +01:00
|
|
|
st.update(in);
|
|
|
|
st.final(out);
|
|
|
|
}
|
|
|
|
|
|
|
|
test "hash" {
|
|
|
|
// a test vector (30) from NIST KAT submission.
|
|
|
|
var msg: [58 / 2]u8 = undefined;
|
2021-02-18 19:28:59 +00:00
|
|
|
_ = try std.fmt.hexToBytes(&msg, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C");
|
2019-06-15 18:02:47 +01:00
|
|
|
var md: [32]u8 = undefined;
|
2020-08-20 23:51:14 +01:00
|
|
|
hash(&md, &msg, .{});
|
2021-05-04 18:47:26 +01:00
|
|
|
try htest.assertEqual("1C9A03DC6A5DDC5444CFC6F4B154CFF5CF081633B2CEA4D7D0AE7CCFED5AAA44", &md);
|
2019-06-15 18:02:47 +01:00
|
|
|
}
|
2019-06-16 09:10:23 +01:00
|
|
|
|
2020-12-23 04:32:01 +00:00
|
|
|
test "hash test vector 17" {
|
|
|
|
var msg: [32 / 2]u8 = undefined;
|
2021-02-18 19:28:59 +00:00
|
|
|
_ = try std.fmt.hexToBytes(&msg, "000102030405060708090A0B0C0D0E0F");
|
2020-12-23 04:32:01 +00:00
|
|
|
var md: [32]u8 = undefined;
|
|
|
|
hash(&md, &msg, .{});
|
2021-05-04 18:47:26 +01:00
|
|
|
try htest.assertEqual("404C130AF1B9023A7908200919F690FFBB756D5176E056FFDE320016A37C7282", &md);
|
2020-12-23 04:32:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test "hash test vector 33" {
|
|
|
|
var msg: [32]u8 = undefined;
|
2021-02-18 19:28:59 +00:00
|
|
|
_ = try std.fmt.hexToBytes(&msg, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
|
2020-12-23 04:32:01 +00:00
|
|
|
var md: [32]u8 = undefined;
|
|
|
|
hash(&md, &msg, .{});
|
2021-05-04 18:47:26 +01:00
|
|
|
try htest.assertEqual("A8F4FA28708BDA7EFB4C1914CA4AFA9E475B82D588D36504F87DBB0ED9AB3C4B", &md);
|
2020-12-23 04:32:01 +00:00
|
|
|
}
|
|
|
|
|
2019-06-16 09:10:23 +01:00
|
|
|
pub const Aead = struct {
|
2020-08-25 15:20:57 +01:00
|
|
|
pub const tag_length = State.RATE;
|
|
|
|
pub const nonce_length = 16;
|
|
|
|
pub const key_length = 32;
|
|
|
|
|
2019-06-16 09:10:23 +01:00
|
|
|
/// ad: Associated Data
|
|
|
|
/// npub: public nonce
|
|
|
|
/// k: private key
|
2020-08-25 15:20:57 +01:00
|
|
|
fn init(ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) State {
|
2019-06-16 09:10:23 +01:00
|
|
|
var state = State{
|
|
|
|
.data = undefined,
|
|
|
|
};
|
|
|
|
const buf = state.toSlice();
|
|
|
|
|
|
|
|
// Gimli-Cipher initializes a 48-byte Gimli state to a 16-byte nonce
|
|
|
|
// followed by a 32-byte key.
|
|
|
|
assert(npub.len + k.len == State.BLOCKBYTES);
|
|
|
|
std.mem.copy(u8, buf[0..npub.len], &npub);
|
|
|
|
std.mem.copy(u8, buf[npub.len .. npub.len + k.len], &k);
|
|
|
|
|
|
|
|
// It then applies the Gimli permutation.
|
|
|
|
state.permute();
|
|
|
|
|
|
|
|
{
|
|
|
|
// Gimli-Cipher then handles each block of associated data, including
|
|
|
|
// exactly one final non-full block, in the same way as Gimli-Hash.
|
|
|
|
var data = ad;
|
|
|
|
while (data.len >= State.RATE) : (data = data[State.RATE..]) {
|
|
|
|
for (buf[0..State.RATE]) |*p, i| {
|
|
|
|
p.* ^= data[i];
|
|
|
|
}
|
|
|
|
state.permute();
|
|
|
|
}
|
|
|
|
for (buf[0..data.len]) |*p, i| {
|
|
|
|
p.* ^= data[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
// XOR 1 into the next byte of the state
|
|
|
|
buf[data.len] ^= 1;
|
|
|
|
// XOR 1 into the last byte of the state, position 47.
|
|
|
|
buf[buf.len - 1] ^= 1;
|
|
|
|
|
|
|
|
state.permute();
|
|
|
|
}
|
|
|
|
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// c: ciphertext: output buffer should be of size m.len
|
2020-08-25 19:17:56 +01:00
|
|
|
/// tag: authentication tag: output MAC
|
2019-06-16 09:10:23 +01:00
|
|
|
/// m: message
|
|
|
|
/// ad: Associated Data
|
|
|
|
/// npub: public nonce
|
|
|
|
/// k: private key
|
2020-08-25 19:17:56 +01:00
|
|
|
pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
|
2019-06-16 09:10:23 +01:00
|
|
|
assert(c.len == m.len);
|
|
|
|
|
|
|
|
var state = Aead.init(ad, npub, k);
|
|
|
|
const buf = state.toSlice();
|
|
|
|
|
|
|
|
// Gimli-Cipher then handles each block of plaintext, including
|
|
|
|
// exactly one final non-full block, in the same way as Gimli-Hash.
|
|
|
|
// Whenever a plaintext byte is XORed into a state byte, the new state
|
|
|
|
// byte is output as ciphertext.
|
|
|
|
var in = m;
|
|
|
|
var out = c;
|
|
|
|
while (in.len >= State.RATE) : ({
|
|
|
|
in = in[State.RATE..];
|
|
|
|
out = out[State.RATE..];
|
|
|
|
}) {
|
2020-09-28 23:41:37 +01:00
|
|
|
for (in[0..State.RATE]) |v, i| {
|
2020-09-28 22:23:32 +01:00
|
|
|
buf[i] ^= v;
|
|
|
|
}
|
2020-09-28 23:41:37 +01:00
|
|
|
mem.copy(u8, out[0..State.RATE], buf[0..State.RATE]);
|
2019-06-16 09:10:23 +01:00
|
|
|
state.permute();
|
|
|
|
}
|
2020-09-28 23:41:37 +01:00
|
|
|
for (in[0..]) |v, i| {
|
2020-09-28 22:23:32 +01:00
|
|
|
buf[i] ^= v;
|
|
|
|
out[i] = buf[i];
|
2019-06-16 09:10:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// XOR 1 into the next byte of the state
|
|
|
|
buf[in.len] ^= 1;
|
|
|
|
// XOR 1 into the last byte of the state, position 47.
|
|
|
|
buf[buf.len - 1] ^= 1;
|
|
|
|
|
|
|
|
state.permute();
|
|
|
|
|
|
|
|
// After the final non-full block of plaintext, the first 16 bytes
|
|
|
|
// of the state are output as an authentication tag.
|
2020-08-25 19:17:56 +01:00
|
|
|
std.mem.copy(u8, tag, buf[0..State.RATE]);
|
2019-06-16 09:10:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// m: message: output buffer should be of size c.len
|
|
|
|
/// c: ciphertext
|
2020-08-25 19:17:56 +01:00
|
|
|
/// tag: authentication tag
|
2019-06-16 09:10:23 +01:00
|
|
|
/// ad: Associated Data
|
|
|
|
/// npub: public nonce
|
|
|
|
/// k: private key
|
|
|
|
/// NOTE: the check of the authentication tag is currently not done in constant time
|
2021-04-20 18:57:27 +01:00
|
|
|
pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) AuthenticationError!void {
|
2019-06-16 09:10:23 +01:00
|
|
|
assert(c.len == m.len);
|
|
|
|
|
|
|
|
var state = Aead.init(ad, npub, k);
|
|
|
|
const buf = state.toSlice();
|
|
|
|
|
|
|
|
var in = c;
|
|
|
|
var out = m;
|
|
|
|
while (in.len >= State.RATE) : ({
|
|
|
|
in = in[State.RATE..];
|
|
|
|
out = out[State.RATE..];
|
|
|
|
}) {
|
2020-09-28 22:23:32 +01:00
|
|
|
const d = in[0..State.RATE].*;
|
|
|
|
for (d) |v, i| {
|
|
|
|
out[i] = buf[i] ^ v;
|
|
|
|
}
|
2020-09-28 23:41:37 +01:00
|
|
|
mem.copy(u8, buf[0..State.RATE], d[0..State.RATE]);
|
2019-06-16 09:10:23 +01:00
|
|
|
state.permute();
|
|
|
|
}
|
|
|
|
for (buf[0..in.len]) |*p, i| {
|
2020-09-28 22:23:32 +01:00
|
|
|
const d = in[i];
|
|
|
|
out[i] = p.* ^ d;
|
|
|
|
p.* = d;
|
2019-06-16 09:10:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// XOR 1 into the next byte of the state
|
|
|
|
buf[in.len] ^= 1;
|
|
|
|
// XOR 1 into the last byte of the state, position 47.
|
|
|
|
buf[buf.len - 1] ^= 1;
|
|
|
|
|
|
|
|
state.permute();
|
|
|
|
|
|
|
|
// After the final non-full block of plaintext, the first 16 bytes
|
|
|
|
// of the state are the authentication tag.
|
|
|
|
// TODO: use a constant-time equality check here, see https://github.com/ziglang/zig/issues/1776
|
2020-08-25 19:17:56 +01:00
|
|
|
if (!mem.eql(u8, buf[0..State.RATE], &tag)) {
|
2019-06-16 09:10:23 +01:00
|
|
|
@memset(m.ptr, undefined, m.len);
|
2021-03-13 14:11:35 +00:00
|
|
|
return error.AuthenticationFailed;
|
2019-06-16 09:10:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
test "cipher" {
|
|
|
|
var key: [32]u8 = undefined;
|
2021-02-18 19:28:59 +00:00
|
|
|
_ = try std.fmt.hexToBytes(&key, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
|
2019-06-16 09:10:23 +01:00
|
|
|
var nonce: [16]u8 = undefined;
|
2021-02-18 19:28:59 +00:00
|
|
|
_ = try std.fmt.hexToBytes(&nonce, "000102030405060708090A0B0C0D0E0F");
|
2019-06-16 09:10:23 +01:00
|
|
|
{ // test vector (1) from NIST KAT submission.
|
|
|
|
const ad: [0]u8 = undefined;
|
|
|
|
const pt: [0]u8 = undefined;
|
|
|
|
|
|
|
|
var ct: [pt.len]u8 = undefined;
|
2020-08-25 19:17:56 +01:00
|
|
|
var tag: [16]u8 = undefined;
|
|
|
|
Aead.encrypt(&ct, &tag, &pt, &ad, nonce, key);
|
2021-05-04 18:47:26 +01:00
|
|
|
try htest.assertEqual("", &ct);
|
|
|
|
try htest.assertEqual("14DA9BB7120BF58B985A8E00FDEBA15B", &tag);
|
2019-06-16 09:10:23 +01:00
|
|
|
|
|
|
|
var pt2: [pt.len]u8 = undefined;
|
2020-08-25 19:17:56 +01:00
|
|
|
try Aead.decrypt(&pt2, &ct, tag, &ad, nonce, key);
|
2021-05-04 18:47:26 +01:00
|
|
|
try testing.expectEqualSlices(u8, &pt, &pt2);
|
2019-06-16 09:10:23 +01:00
|
|
|
}
|
|
|
|
{ // test vector (34) from NIST KAT submission.
|
|
|
|
const ad: [0]u8 = undefined;
|
|
|
|
var pt: [2 / 2]u8 = undefined;
|
2021-02-18 19:28:59 +00:00
|
|
|
_ = try std.fmt.hexToBytes(&pt, "00");
|
2019-06-16 09:10:23 +01:00
|
|
|
|
|
|
|
var ct: [pt.len]u8 = undefined;
|
2020-08-25 19:17:56 +01:00
|
|
|
var tag: [16]u8 = undefined;
|
|
|
|
Aead.encrypt(&ct, &tag, &pt, &ad, nonce, key);
|
2021-05-04 18:47:26 +01:00
|
|
|
try htest.assertEqual("7F", &ct);
|
|
|
|
try htest.assertEqual("80492C317B1CD58A1EDC3A0D3E9876FC", &tag);
|
2019-06-16 09:10:23 +01:00
|
|
|
|
|
|
|
var pt2: [pt.len]u8 = undefined;
|
2020-08-25 19:17:56 +01:00
|
|
|
try Aead.decrypt(&pt2, &ct, tag, &ad, nonce, key);
|
2021-05-04 18:47:26 +01:00
|
|
|
try testing.expectEqualSlices(u8, &pt, &pt2);
|
2019-06-16 09:10:23 +01:00
|
|
|
}
|
|
|
|
{ // test vector (106) from NIST KAT submission.
|
|
|
|
var ad: [12 / 2]u8 = undefined;
|
2021-02-18 19:28:59 +00:00
|
|
|
_ = try std.fmt.hexToBytes(&ad, "000102030405");
|
2019-06-16 09:10:23 +01:00
|
|
|
var pt: [6 / 2]u8 = undefined;
|
2021-02-18 19:28:59 +00:00
|
|
|
_ = try std.fmt.hexToBytes(&pt, "000102");
|
2019-06-16 09:10:23 +01:00
|
|
|
|
|
|
|
var ct: [pt.len]u8 = undefined;
|
2020-08-25 19:17:56 +01:00
|
|
|
var tag: [16]u8 = undefined;
|
|
|
|
Aead.encrypt(&ct, &tag, &pt, &ad, nonce, key);
|
2021-05-04 18:47:26 +01:00
|
|
|
try htest.assertEqual("484D35", &ct);
|
|
|
|
try htest.assertEqual("030BBEA23B61C00CED60A923BDCF9147", &tag);
|
2019-06-16 09:10:23 +01:00
|
|
|
|
|
|
|
var pt2: [pt.len]u8 = undefined;
|
2020-08-25 19:17:56 +01:00
|
|
|
try Aead.decrypt(&pt2, &ct, tag, &ad, nonce, key);
|
2021-05-04 18:47:26 +01:00
|
|
|
try testing.expectEqualSlices(u8, &pt, &pt2);
|
2019-06-16 09:10:23 +01:00
|
|
|
}
|
|
|
|
{ // test vector (790) from NIST KAT submission.
|
|
|
|
var ad: [60 / 2]u8 = undefined;
|
2021-02-18 19:28:59 +00:00
|
|
|
_ = try std.fmt.hexToBytes(&ad, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D");
|
2019-06-16 09:10:23 +01:00
|
|
|
var pt: [46 / 2]u8 = undefined;
|
2021-02-18 19:28:59 +00:00
|
|
|
_ = try std.fmt.hexToBytes(&pt, "000102030405060708090A0B0C0D0E0F10111213141516");
|
2019-06-16 09:10:23 +01:00
|
|
|
|
|
|
|
var ct: [pt.len]u8 = undefined;
|
2020-08-25 19:17:56 +01:00
|
|
|
var tag: [16]u8 = undefined;
|
|
|
|
Aead.encrypt(&ct, &tag, &pt, &ad, nonce, key);
|
2021-05-04 18:47:26 +01:00
|
|
|
try htest.assertEqual("6815B4A0ECDAD01596EAD87D9E690697475D234C6A13D1", &ct);
|
|
|
|
try htest.assertEqual("DFE23F1642508290D68245279558B2FB", &tag);
|
2019-06-16 09:10:23 +01:00
|
|
|
|
|
|
|
var pt2: [pt.len]u8 = undefined;
|
2020-08-25 19:17:56 +01:00
|
|
|
try Aead.decrypt(&pt2, &ct, tag, &ad, nonce, key);
|
2021-05-04 18:47:26 +01:00
|
|
|
try testing.expectEqualSlices(u8, &pt, &pt2);
|
2019-06-16 09:10:23 +01:00
|
|
|
}
|
|
|
|
{ // test vector (1057) from NIST KAT submission.
|
|
|
|
const ad: [0]u8 = undefined;
|
|
|
|
var pt: [64 / 2]u8 = undefined;
|
2021-02-18 19:28:59 +00:00
|
|
|
_ = try std.fmt.hexToBytes(&pt, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
|
2019-06-16 09:10:23 +01:00
|
|
|
|
|
|
|
var ct: [pt.len]u8 = undefined;
|
2020-08-25 19:17:56 +01:00
|
|
|
var tag: [16]u8 = undefined;
|
|
|
|
Aead.encrypt(&ct, &tag, &pt, &ad, nonce, key);
|
2021-05-04 18:47:26 +01:00
|
|
|
try htest.assertEqual("7F8A2CF4F52AA4D6B2E74105C30A2777B9D0C8AEFDD555DE35861BD3011F652F", &ct);
|
|
|
|
try htest.assertEqual("7256456FA935AC34BBF55AE135F33257", &tag);
|
2019-06-16 09:10:23 +01:00
|
|
|
|
|
|
|
var pt2: [pt.len]u8 = undefined;
|
2020-08-25 19:17:56 +01:00
|
|
|
try Aead.decrypt(&pt2, &ct, tag, &ad, nonce, key);
|
2021-05-04 18:47:26 +01:00
|
|
|
try testing.expectEqualSlices(u8, &pt, &pt2);
|
2019-06-16 09:10:23 +01:00
|
|
|
}
|
|
|
|
}
|