Skip to content

std.crypto.gimli enhancements #4369

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/std/crypto/benchmark.zig
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const hashes = [_]Crypto{
Crypto{ .ty = crypto.Sha512, .name = "sha512" },
Crypto{ .ty = crypto.Sha3_256, .name = "sha3-256" },
Crypto{ .ty = crypto.Sha3_512, .name = "sha3-512" },
Crypto{ .ty = crypto.gimli.Hash, .name = "gimli-hash" },
Crypto{ .ty = crypto.Blake2s256, .name = "blake2s" },
Crypto{ .ty = crypto.Blake2b512, .name = "blake2b" },
Crypto{ .ty = crypto.Blake3, .name = "blake3" },
Expand Down
222 changes: 221 additions & 1 deletion lib/std/crypto/gimli.zig
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ pub const State = struct {
pub const BLOCKBYTES = 48;
pub const RATE = 16;

// TODO: https://github.com/ziglang/zig/issues/2673#issuecomment-501763017
data: [BLOCKBYTES / 4]u32,

const Self = @This();
Expand Down Expand Up @@ -134,6 +133,8 @@ pub const Hash = struct {
}
}

pub const digest_length = 32;

/// Finish the current hashing operation, writing the hash to `out`
///
/// From 4.9 "Application to hashing"
Expand Down Expand Up @@ -166,3 +167,222 @@ test "hash" {
hash(&md, &msg);
htest.assertEqual("1C9A03DC6A5DDC5444CFC6F4B154CFF5CF081633B2CEA4D7D0AE7CCFED5AAA44", &md);
}

pub const Aead = struct {
/// ad: Associated Data
/// npub: public nonce
/// k: private key
fn init(ad: []const u8, npub: [16]u8, k: [32]u8) State {
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
/// at: authentication tag: output MAC
/// m: message
/// ad: Associated Data
/// npub: public nonce
/// k: private key
pub fn encrypt(c: []u8, at: *[State.RATE]u8, m: []const u8, ad: []const u8, npub: [16]u8, k: [32]u8) void {
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..];
}) {
for (buf[0..State.RATE]) |*p, i| {
p.* ^= in[i];
out[i] = p.*;
}
state.permute();
}
for (buf[0..in.len]) |*p, i| {
p.* ^= in[i];
out[i] = p.*;
}

// 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.
std.mem.copy(u8, at, buf[0..State.RATE]);
}

/// m: message: output buffer should be of size c.len
/// c: ciphertext
/// at: authentication tag
/// ad: Associated Data
/// npub: public nonce
/// k: private key
/// NOTE: the check of the authentication tag is currently not done in constant time
pub fn decrypt(m: []u8, c: []const u8, at: [State.RATE]u8, ad: []u8, npub: [16]u8, k: [32]u8) !void {
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..];
}) {
for (buf[0..State.RATE]) |*p, i| {
out[i] = p.* ^ in[i];
p.* = in[i];
}
state.permute();
}
for (buf[0..in.len]) |*p, i| {
out[i] = p.* ^ in[i];
p.* = in[i];
}

// 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
if (!mem.eql(u8, buf[0..State.RATE], &at)) {
@memset(m.ptr, undefined, m.len);
return error.InvalidMessage;
}
}
};

test "cipher" {
var key: [32]u8 = undefined;
try std.fmt.hexToBytes(&key, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
var nonce: [16]u8 = undefined;
try std.fmt.hexToBytes(&nonce, "000102030405060708090A0B0C0D0E0F");
{ // test vector (1) from NIST KAT submission.
const ad: [0]u8 = undefined;
const pt: [0]u8 = undefined;

var ct: [pt.len]u8 = undefined;
var at: [16]u8 = undefined;
Aead.encrypt(&ct, &at, &pt, &ad, nonce, key);
htest.assertEqual("", &ct);
htest.assertEqual("14DA9BB7120BF58B985A8E00FDEBA15B", &at);

var pt2: [pt.len]u8 = undefined;
try Aead.decrypt(&pt2, &ct, at, &ad, nonce, key);
testing.expectEqualSlices(u8, &pt, &pt2);
}
{ // test vector (34) from NIST KAT submission.
const ad: [0]u8 = undefined;
var pt: [2 / 2]u8 = undefined;
try std.fmt.hexToBytes(&pt, "00");

var ct: [pt.len]u8 = undefined;
var at: [16]u8 = undefined;
Aead.encrypt(&ct, &at, &pt, &ad, nonce, key);
htest.assertEqual("7F", &ct);
htest.assertEqual("80492C317B1CD58A1EDC3A0D3E9876FC", &at);

var pt2: [pt.len]u8 = undefined;
try Aead.decrypt(&pt2, &ct, at, &ad, nonce, key);
testing.expectEqualSlices(u8, &pt, &pt2);
}
{ // test vector (106) from NIST KAT submission.
var ad: [12 / 2]u8 = undefined;
try std.fmt.hexToBytes(&ad, "000102030405");
var pt: [6 / 2]u8 = undefined;
try std.fmt.hexToBytes(&pt, "000102");

var ct: [pt.len]u8 = undefined;
var at: [16]u8 = undefined;
Aead.encrypt(&ct, &at, &pt, &ad, nonce, key);
htest.assertEqual("484D35", &ct);
htest.assertEqual("030BBEA23B61C00CED60A923BDCF9147", &at);

var pt2: [pt.len]u8 = undefined;
try Aead.decrypt(&pt2, &ct, at, &ad, nonce, key);
testing.expectEqualSlices(u8, &pt, &pt2);
}
{ // test vector (790) from NIST KAT submission.
var ad: [60 / 2]u8 = undefined;
try std.fmt.hexToBytes(&ad, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D");
var pt: [46 / 2]u8 = undefined;
try std.fmt.hexToBytes(&pt, "000102030405060708090A0B0C0D0E0F10111213141516");

var ct: [pt.len]u8 = undefined;
var at: [16]u8 = undefined;
Aead.encrypt(&ct, &at, &pt, &ad, nonce, key);
htest.assertEqual("6815B4A0ECDAD01596EAD87D9E690697475D234C6A13D1", &ct);
htest.assertEqual("DFE23F1642508290D68245279558B2FB", &at);

var pt2: [pt.len]u8 = undefined;
try Aead.decrypt(&pt2, &ct, at, &ad, nonce, key);
testing.expectEqualSlices(u8, &pt, &pt2);
}
{ // test vector (1057) from NIST KAT submission.
const ad: [0]u8 = undefined;
var pt: [64 / 2]u8 = undefined;
try std.fmt.hexToBytes(&pt, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");

var ct: [pt.len]u8 = undefined;
var at: [16]u8 = undefined;
Aead.encrypt(&ct, &at, &pt, &ad, nonce, key);
htest.assertEqual("7F8A2CF4F52AA4D6B2E74105C30A2777B9D0C8AEFDD555DE35861BD3011F652F", &ct);
htest.assertEqual("7256456FA935AC34BBF55AE135F33257", &at);

var pt2: [pt.len]u8 = undefined;
try Aead.decrypt(&pt2, &ct, at, &ad, nonce, key);
testing.expectEqualSlices(u8, &pt, &pt2);
}
}
26 changes: 26 additions & 0 deletions lib/std/rand.zig
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,32 @@ test "xoroshiro sequence" {
}
}

// Gimli
//
// CSPRNG
pub const Gimli = struct {
random: Random,
state: std.crypto.gimli.State,

pub fn init(init_s: u64) Gimli {
var self = Gimli{
.random = Random{ .fillFn = fill },
.state = std.crypto.gimli.State{
.data = [_]u32{0} ** (std.crypto.gimli.State.BLOCKBYTES / 4),
},
};
self.state.data[0] = @truncate(u32, init_s >> 32);
self.state.data[1] = @truncate(u32, init_s);
return self;
}

fn fill(r: *Random, buf: []u8) void {
const self = @fieldParentPtr(Gimli, "random", r);

self.state.squeeze(buf);
}
};

// ISAAC64 - http://www.burtleburtle.net/bob/rand/isaacafa.html
//
// CSPRNG
Expand Down