aboutsummaryrefslogtreecommitdiff
path: root/lib/std
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2020-02-05 18:13:28 -0500
committerGitHub <noreply@github.com>2020-02-05 18:13:28 -0500
commit5cf30b6791243f1df55c7730399d1a772c85305f (patch)
tree6b51cfe12e8c9e1828f91a5f142273e169a8e4bd /lib/std
parente1f4f44dff238918d0e4f1fa35d56de50d1c22bb (diff)
parentef4ecaa46212c34a124eca2da5dc41859a7de40a (diff)
downloadzig-5cf30b6791243f1df55c7730399d1a772c85305f.tar.gz
zig-5cf30b6791243f1df55c7730399d1a772c85305f.zip
Merge pull request #4369 from daurnimator/gimli
std.crypto.gimli enhancements
Diffstat (limited to 'lib/std')
-rw-r--r--lib/std/crypto/benchmark.zig1
-rw-r--r--lib/std/crypto/gimli.zig222
-rw-r--r--lib/std/rand.zig26
3 files changed, 248 insertions, 1 deletions
diff --git a/lib/std/crypto/benchmark.zig b/lib/std/crypto/benchmark.zig
index d9b84a6ed7..44326bfd97 100644
--- a/lib/std/crypto/benchmark.zig
+++ b/lib/std/crypto/benchmark.zig
@@ -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" },
diff --git a/lib/std/crypto/gimli.zig b/lib/std/crypto/gimli.zig
index 1d835b231b..ed2c5e764f 100644
--- a/lib/std/crypto/gimli.zig
+++ b/lib/std/crypto/gimli.zig
@@ -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();
@@ -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"
@@ -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);
+ }
+}
diff --git a/lib/std/rand.zig b/lib/std/rand.zig
index f9fa4a2d66..0bdc593545 100644
--- a/lib/std/rand.zig
+++ b/lib/std/rand.zig
@@ -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