diff options
| author | Marc Tiehuis <marctiehuis@gmail.com> | 2018-08-31 18:40:09 +1200 |
|---|---|---|
| committer | Marc Tiehuis <marctiehuis@gmail.com> | 2018-08-31 18:40:09 +1200 |
| commit | a7527389ccdf4e3ab6ce3f83ff9a55003b2c5692 (patch) | |
| tree | badc8a288b9121227e0be06955587a832eee5efb | |
| parent | 65b89f598c63719ab80553163424feb2d8e6f9e4 (diff) | |
| download | zig-a7527389ccdf4e3ab6ce3f83ff9a55003b2c5692.tar.gz zig-a7527389ccdf4e3ab6ce3f83ff9a55003b2c5692.zip | |
Make poly1305 and x25519 more idiomatic zig
This also adjusts the current hash/hmac functions to have a consistent
interface allowing easier switching/testing.
| -rw-r--r-- | std/crypto/blake2.zig | 16 | ||||
| -rw-r--r-- | std/crypto/hmac.zig | 91 | ||||
| -rw-r--r-- | std/crypto/index.zig | 8 | ||||
| -rw-r--r-- | std/crypto/md5.zig | 8 | ||||
| -rw-r--r-- | std/crypto/poly1305.zig | 345 | ||||
| -rw-r--r-- | std/crypto/sha1.zig | 8 | ||||
| -rw-r--r-- | std/crypto/sha2.zig | 16 | ||||
| -rw-r--r-- | std/crypto/sha3.zig | 12 | ||||
| -rw-r--r-- | std/crypto/x25519.zig | 233 |
9 files changed, 387 insertions, 350 deletions
diff --git a/std/crypto/blake2.zig b/std/crypto/blake2.zig index 947133e4cf..467ddde5db 100644 --- a/std/crypto/blake2.zig +++ b/std/crypto/blake2.zig @@ -34,8 +34,8 @@ pub const Blake2s256 = Blake2s(256); fn Blake2s(comptime out_len: usize) type { return struct { const Self = this; - const block_size = 64; - const digest_size = out_len / 8; + const block_length = 64; + const digest_length = out_len / 8; const iv = [8]u32{ 0x6A09E667, @@ -250,8 +250,8 @@ test "blake2s256 streaming" { } test "blake2s256 aligned final" { - var block = []u8{0} ** Blake2s256.block_size; - var out: [Blake2s256.digest_size]u8 = undefined; + var block = []u8{0} ** Blake2s256.block_length; + var out: [Blake2s256.digest_length]u8 = undefined; var h = Blake2s256.init(); h.update(block); @@ -267,8 +267,8 @@ pub const Blake2b512 = Blake2b(512); fn Blake2b(comptime out_len: usize) type { return struct { const Self = this; - const block_size = 128; - const digest_size = out_len / 8; + const block_length = 128; + const digest_length = out_len / 8; const iv = [8]u64{ 0x6a09e667f3bcc908, @@ -483,8 +483,8 @@ test "blake2b512 streaming" { } test "blake2b512 aligned final" { - var block = []u8{0} ** Blake2b512.block_size; - var out: [Blake2b512.digest_size]u8 = undefined; + var block = []u8{0} ** Blake2b512.block_length; + var out: [Blake2b512.digest_length]u8 = undefined; var h = Blake2b512.init(); h.update(block); diff --git a/std/crypto/hmac.zig b/std/crypto/hmac.zig index 1415e88cf4..23eeff2a00 100644 --- a/std/crypto/hmac.zig +++ b/std/crypto/hmac.zig @@ -7,46 +7,63 @@ pub const HmacMd5 = Hmac(crypto.Md5); pub const HmacSha1 = Hmac(crypto.Sha1); pub const HmacSha256 = Hmac(crypto.Sha256); -pub fn Hmac(comptime H: type) type { +pub fn Hmac(comptime Hash: type) type { return struct { - const digest_size = H.digest_size; + const Self = this; + pub const mac_length = Hash.digest_length; + pub const minimum_key_length = 0; - pub fn hash(output: []u8, key: []const u8, message: []const u8) void { - debug.assert(output.len >= H.digest_size); - debug.assert(H.digest_size <= H.block_size); // HMAC makes this assumption - var scratch: [H.block_size]u8 = undefined; + o_key_pad: [Hash.block_length]u8, + i_key_pad: [Hash.block_length]u8, + scratch: [Hash.block_length]u8, + hash: Hash, + + // HMAC(k, m) = H(o_key_pad | H(i_key_pad | msg)) where | is concatenation + pub fn create(out: []u8, msg: []const u8, key: []const u8) void { + var ctx = Self.init(key); + ctx.update(msg); + ctx.final(out[0..]); + } + + pub fn init(key: []const u8) Self { + var ctx: Self = undefined; // Normalize key length to block size of hash - if (key.len > H.block_size) { - H.hash(key, scratch[0..H.digest_size]); - mem.set(u8, scratch[H.digest_size..H.block_size], 0); - } else if (key.len < H.block_size) { - mem.copy(u8, scratch[0..key.len], key); - mem.set(u8, scratch[key.len..H.block_size], 0); + if (key.len > Hash.block_length) { + Hash.hash(key, ctx.scratch[0..mac_length]); + mem.set(u8, ctx.scratch[mac_length..Hash.block_length], 0); + } else if (key.len < Hash.block_length) { + mem.copy(u8, ctx.scratch[0..key.len], key); + mem.set(u8, ctx.scratch[key.len..Hash.block_length], 0); } else { - mem.copy(u8, scratch[0..], key); + mem.copy(u8, ctx.scratch[0..], key); } - var o_key_pad: [H.block_size]u8 = undefined; - for (o_key_pad) |*b, i| { - b.* = scratch[i] ^ 0x5c; + for (ctx.o_key_pad) |*b, i| { + b.* = ctx.scratch[i] ^ 0x5c; } - var i_key_pad: [H.block_size]u8 = undefined; - for (i_key_pad) |*b, i| { - b.* = scratch[i] ^ 0x36; + for (ctx.i_key_pad) |*b, i| { + b.* = ctx.scratch[i] ^ 0x36; } - // HMAC(k, m) = H(o_key_pad | H(i_key_pad | message)) where | is concatenation - var hmac = H.init(); - hmac.update(i_key_pad[0..]); - hmac.update(message); - hmac.final(scratch[0..H.digest_size]); + ctx.hash = Hash.init(); + ctx.hash.update(ctx.i_key_pad[0..]); + return ctx; + } + + pub fn update(ctx: *Self, msg: []const u8) void { + ctx.hash.update(msg); + } + + pub fn final(ctx: *Self, out: []u8) void { + debug.assert(Hash.block_length >= out.len and out.len >= mac_length); - hmac.reset(); - hmac.update(o_key_pad[0..]); - hmac.update(scratch[0..H.digest_size]); - hmac.final(output[0..H.digest_size]); + ctx.hash.final(ctx.scratch[0..mac_length]); + ctx.hash.reset(); + ctx.hash.update(ctx.o_key_pad[0..]); + ctx.hash.update(ctx.scratch[0..mac_length]); + ctx.hash.final(out[0..mac_length]); } }; } @@ -54,28 +71,28 @@ pub fn Hmac(comptime H: type) type { const htest = @import("test.zig"); test "hmac md5" { - var out: [crypto.Md5.digest_size]u8 = undefined; - HmacMd5.hash(out[0..], "", ""); + var out: [HmacMd5.mac_length]u8 = undefined; + HmacMd5.create(out[0..], "", ""); htest.assertEqual("74e6f7298a9c2d168935f58c001bad88", out[0..]); - HmacMd5.hash(out[0..], "key", "The quick brown fox jumps over the lazy dog"); + HmacMd5.create(out[0..], "The quick brown fox jumps over the lazy dog", "key"); htest.assertEqual("80070713463e7749b90c2dc24911e275", out[0..]); } test "hmac sha1" { - var out: [crypto.Sha1.digest_size]u8 = undefined; - HmacSha1.hash(out[0..], "", ""); + var out: [HmacSha1.mac_length]u8 = undefined; + HmacSha1.create(out[0..], "", ""); htest.assertEqual("fbdb1d1b18aa6c08324b7d64b71fb76370690e1d", out[0..]); - HmacSha1.hash(out[0..], "key", "The quick brown fox jumps over the lazy dog"); + HmacSha1.create(out[0..], "The quick brown fox jumps over the lazy dog", "key"); htest.assertEqual("de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9", out[0..]); } test "hmac sha256" { - var out: [crypto.Sha256.digest_size]u8 = undefined; - HmacSha256.hash(out[0..], "", ""); + var out: [HmacSha256.mac_length]u8 = undefined; + HmacSha256.create(out[0..], "", ""); htest.assertEqual("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad", out[0..]); - HmacSha256.hash(out[0..], "key", "The quick brown fox jumps over the lazy dog"); + HmacSha256.create(out[0..], "The quick brown fox jumps over the lazy dog", "key"); htest.assertEqual("f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8", out[0..]); } diff --git a/std/crypto/index.zig b/std/crypto/index.zig index f861b61a20..9f20ce514b 100644 --- a/std/crypto/index.zig +++ b/std/crypto/index.zig @@ -21,15 +21,15 @@ pub const Blake2b512 = blake2.Blake2b512; const hmac = @import("hmac.zig"); pub const HmacMd5 = hmac.HmacMd5; -pub const HmacSha1 = hmac.Sha1; -pub const HmacSha256 = hmac.Sha256; +pub const HmacSha1 = hmac.HmacSha1; +pub const HmacSha256 = hmac.HmacSha256; const import_chaCha20 = @import("chacha20.zig"); pub const chaCha20IETF = import_chaCha20.chaCha20IETF; pub const chaCha20With64BitNonce = import_chaCha20.chaCha20With64BitNonce; -const poly1305 = @import("poly1305.zig"); -const x25519 = @import("x25519.zig"); +pub const Poly1305 = @import("poly1305.zig").Poly1305; +pub const X25519 = @import("x25519.zig").X25519; test "crypto" { _ = @import("md5.zig"); diff --git a/std/crypto/md5.zig b/std/crypto/md5.zig index 23fe2313a0..20334ec7d8 100644 --- a/std/crypto/md5.zig +++ b/std/crypto/md5.zig @@ -29,8 +29,8 @@ fn Rp(a: usize, b: usize, c: usize, d: usize, k: usize, s: u32, t: u32) RoundPar pub const Md5 = struct { const Self = this; - const block_size = 64; - const digest_size = 16; + const block_length = 64; + const digest_length = 16; s: [4]u32, // Streaming Cache @@ -271,8 +271,8 @@ test "md5 streaming" { } test "md5 aligned final" { - var block = []u8{0} ** Md5.block_size; - var out: [Md5.digest_size]u8 = undefined; + var block = []u8{0} ** Md5.block_length; + var out: [Md5.digest_length]u8 = undefined; var h = Md5.init(); h.update(block); diff --git a/std/crypto/poly1305.zig b/std/crypto/poly1305.zig index bce90db3d5..1cec72890f 100644 --- a/std/crypto/poly1305.zig +++ b/std/crypto/poly1305.zig @@ -9,7 +9,12 @@ const Endian = builtin.Endian; const readInt = std.mem.readInt; const writeInt = std.mem.writeInt; -const crypto_poly1305_ctx = struct { +pub const Poly1305 = struct { + const Self = this; + + pub const mac_length = 16; + pub const minimum_key_length = 32; + // constant multiplier (from the secret key) r: [4]u32, // accumulated hash @@ -21,190 +26,198 @@ const crypto_poly1305_ctx = struct { // How many bytes are there in the chunk. c_idx: usize, - fn secure_zero(self: *crypto_poly1305_ctx) void { - std.mem.secureZero(u8, @ptrCast([*]u8, self)[0..@sizeOf(crypto_poly1305_ctx)]); + fn secure_zero(self: *Poly1305) void { + std.mem.secureZero(u8, @ptrCast([*]u8, self)[0..@sizeOf(Poly1305)]); } -}; -// h = (h + c) * r -// preconditions: -// ctx->h <= 4_ffffffff_ffffffff_ffffffff_ffffffff -// ctx->c <= 1_ffffffff_ffffffff_ffffffff_ffffffff -// ctx->r <= 0ffffffc_0ffffffc_0ffffffc_0fffffff -// Postcondition: -// ctx->h <= 4_ffffffff_ffffffff_ffffffff_ffffffff -fn poly_block(ctx: *crypto_poly1305_ctx) void { - // s = h + c, without carry propagation - const s0 = u64(ctx.h[0]) + ctx.c[0]; // s0 <= 1_fffffffe - const s1 = u64(ctx.h[1]) + ctx.c[1]; // s1 <= 1_fffffffe - const s2 = u64(ctx.h[2]) + ctx.c[2]; // s2 <= 1_fffffffe - const s3 = u64(ctx.h[3]) + ctx.c[3]; // s3 <= 1_fffffffe - const s4 = u64(ctx.h[4]) + ctx.c[4]; // s4 <= 5 - - // Local all the things! - const r0 = ctx.r[0]; // r0 <= 0fffffff - const r1 = ctx.r[1]; // r1 <= 0ffffffc - const r2 = ctx.r[2]; // r2 <= 0ffffffc - const r3 = ctx.r[3]; // r3 <= 0ffffffc - const rr0 = (r0 >> 2) * 5; // rr0 <= 13fffffb // lose 2 bits... - const rr1 = (r1 >> 2) + r1; // rr1 <= 13fffffb // rr1 == (r1 >> 2) * 5 - const rr2 = (r2 >> 2) + r2; // rr2 <= 13fffffb // rr1 == (r2 >> 2) * 5 - const rr3 = (r3 >> 2) + r3; // rr3 <= 13fffffb // rr1 == (r3 >> 2) * 5 - - // (h + c) * r, without carry propagation - const x0 = s0 * r0 + s1 * rr3 + s2 * rr2 + s3 * rr1 + s4 * rr0; //<=97ffffe007fffff8 - const x1 = s0 * r1 + s1 * r0 + s2 * rr3 + s3 * rr2 + s4 * rr1; //<=8fffffe20ffffff6 - const x2 = s0 * r2 + s1 * r1 + s2 * r0 + s3 * rr3 + s4 * rr2; //<=87ffffe417fffff4 - const x3 = s0 * r3 + s1 * r2 + s2 * r1 + s3 * r0 + s4 * rr3; //<=7fffffe61ffffff2 - const x4 = s4 * (r0 & 3); // ...recover 2 bits //<= f - - // partial reduction modulo 2^130 - 5 - const _u5 = @truncate(u32, x4 + (x3 >> 32)); // u5 <= 7ffffff5 - const _u0 = (_u5 >> 2) * 5 + (x0 & 0xffffffff); - const _u1 = (_u0 >> 32) + (x1 & 0xffffffff) + (x0 >> 32); - const _u2 = (_u1 >> 32) + (x2 & 0xffffffff) + (x1 >> 32); - const _u3 = (_u2 >> 32) + (x3 & 0xffffffff) + (x2 >> 32); - const _u4 = (_u3 >> 32) + (_u5 & 3); - - // Update the hash - ctx.h[0] = @truncate(u32, _u0); // u0 <= 1_9ffffff0 - ctx.h[1] = @truncate(u32, _u1); // u1 <= 1_97ffffe0 - ctx.h[2] = @truncate(u32, _u2); // u2 <= 1_8fffffe2 - ctx.h[3] = @truncate(u32, _u3); // u3 <= 1_87ffffe4 - ctx.h[4] = @truncate(u32, _u4); // u4 <= 4 -} - -// (re-)initializes the input counter and input buffer -fn poly_clear_c(ctx: *crypto_poly1305_ctx) void { - ctx.c[0] = 0; - ctx.c[1] = 0; - ctx.c[2] = 0; - ctx.c[3] = 0; - ctx.c_idx = 0; -} + pub fn create(out: []u8, msg: []const u8, key: []const u8) void { + std.debug.assert(out.len >= mac_length); + std.debug.assert(key.len >= minimum_key_length); -fn poly_take_input(ctx: *crypto_poly1305_ctx, input: u8) void { - const word = ctx.c_idx >> 2; - const byte = ctx.c_idx & 3; - ctx.c[word] |= std.math.shl(u32, input, byte * 8); - ctx.c_idx += 1; -} - -fn poly_update(ctx: *crypto_poly1305_ctx, message: []const u8) void { - for (message) |b| { - poly_take_input(ctx, b); - if (ctx.c_idx == 16) { - poly_block(ctx); - poly_clear_c(ctx); - } + var ctx = Poly1305.init(key); + ctx.update(msg); + ctx.final(out); } -} -pub fn crypto_poly1305_init(ctx: *crypto_poly1305_ctx, key: [32]u8) void { - // Initial hash is zero - { - var i: usize = 0; - while (i < 5) : (i += 1) { - ctx.h[i] = 0; + // Initialize the MAC context. + // - key.len is sufficient size. + pub fn init(key: []const u8) Self { + var ctx: Poly1305 = undefined; + + // Initial hash is zero + { + var i: usize = 0; + while (i < 5) : (i += 1) { + ctx.h[i] = 0; + } } - } - // add 2^130 to every input block - ctx.c[4] = 1; - poly_clear_c(ctx); - - // load r and pad (r has some of its bits cleared) - { - var i: usize = 0; - while (i < 1) : (i += 1) { - ctx.r[0] = readInt(key[0..4], u32, Endian.Little) & 0x0fffffff; + // add 2^130 to every input block + ctx.c[4] = 1; + poly_clear_c(&ctx); + + // load r and pad (r has some of its bits cleared) + { + var i: usize = 0; + while (i < 1) : (i += 1) { + ctx.r[0] = readInt(key[0..4], u32, Endian.Little) & 0x0fffffff; + } } - } - { - var i: usize = 1; - while (i < 4) : (i += 1) { - ctx.r[i] = readInt(key[i * 4 .. i * 4 + 4], u32, Endian.Little) & 0x0ffffffc; + { + var i: usize = 1; + while (i < 4) : (i += 1) { + ctx.r[i] = readInt(key[i * 4 .. i * 4 + 4], u32, Endian.Little) & 0x0ffffffc; + } } - } - { - var i: usize = 0; - while (i < 4) : (i += 1) { - ctx.pad[i] = readInt(key[i * 4 + 16 .. i * 4 + 16 + 4], u32, Endian.Little); + { + var i: usize = 0; + while (i < 4) : (i += 1) { + ctx.pad[i] = readInt(key[i * 4 + 16 .. i * 4 + 16 + 4], u32, Endian.Little); + } } + + return ctx; } -} -inline fn alignto(x: usize, block_size: usize) usize { - return ((~x) +% 1) & (block_size - 1); -} + // h = (h + c) * r + // preconditions: + // ctx->h <= 4_ffffffff_ffffffff_ffffffff_ffffffff + // ctx->c <= 1_ffffffff_ffffffff_ffffffff_ffffffff + // ctx->r <= 0ffffffc_0ffffffc_0ffffffc_0fffffff + // Postcondition: + // ctx->h <= 4_ffffffff_ffffffff_ffffffff_ffffffff + fn poly_block(ctx: *Poly1305) void { + // s = h + c, without carry propagation + const s0 = u64(ctx.h[0]) + ctx.c[0]; // s0 <= 1_fffffffe + const s1 = u64(ctx.h[1]) + ctx.c[1]; // s1 <= 1_fffffffe + const s2 = u64(ctx.h[2]) + ctx.c[2]; // s2 <= 1_fffffffe + const s3 = u64(ctx.h[3]) + ctx.c[3]; // s3 <= 1_fffffffe + const s4 = u64(ctx.h[4]) + ctx.c[4]; // s4 <= 5 + + // Local all the things! + const r0 = ctx.r[0]; // r0 <= 0fffffff + const r1 = ctx.r[1]; // r1 <= 0ffffffc + const r2 = ctx.r[2]; // r2 <= 0ffffffc + const r3 = ctx.r[3]; // r3 <= 0ffffffc + const rr0 = (r0 >> 2) * 5; // rr0 <= 13fffffb // lose 2 bits... + const rr1 = (r1 >> 2) + r1; // rr1 <= 13fffffb // rr1 == (r1 >> 2) * 5 + const rr2 = (r2 >> 2) + r2; // rr2 <= 13fffffb // rr1 == (r2 >> 2) * 5 + const rr3 = (r3 >> 2) + r3; // rr3 <= 13fffffb // rr1 == (r3 >> 2) * 5 + + // (h + c) * r, without carry propagation + const x0 = s0 * r0 + s1 * rr3 + s2 * rr2 + s3 * rr1 + s4 * rr0; //<=97ffffe007fffff8 + const x1 = s0 * r1 + s1 * r0 + s2 * rr3 + s3 * rr2 + s4 * rr1; //<=8fffffe20ffffff6 + const x2 = s0 * r2 + s1 * r1 + s2 * r0 + s3 * rr3 + s4 * rr2; //<=87ffffe417fffff4 + const x3 = s0 * r3 + s1 * r2 + s2 * r1 + s3 * r0 + s4 * rr3; //<=7fffffe61ffffff2 + const x4 = s4 * (r0 & 3); // ...recover 2 bits //<= f + + // partial reduction modulo 2^130 - 5 + const _u5 = @truncate(u32, x4 + (x3 >> 32)); // u5 <= 7ffffff5 + const _u0 = (_u5 >> 2) * 5 + (x0 & 0xffffffff); + const _u1 = (_u0 >> 32) + (x1 & 0xffffffff) + (x0 >> 32); + const _u2 = (_u1 >> 32) + (x2 & 0xffffffff) + (x1 >> 32); + const _u3 = (_u2 >> 32) + (x3 & 0xffffffff) + (x2 >> 32); + const _u4 = (_u3 >> 32) + (_u5 & 3); + + // Update the hash + ctx.h[0] = @truncate(u32, _u0); // u0 <= 1_9ffffff0 + ctx.h[1] = @truncate(u32, _u1); // u1 <= 1_97ffffe0 + ctx.h[2] = @truncate(u32, _u2); // u2 <= 1_8fffffe2 + ctx.h[3] = @truncate(u32, _u3); // u3 <= 1_87ffffe4 + ctx.h[4] = @truncate(u32, _u4); // u4 <= 4 + } -pub fn crypto_poly1305_update(ctx: *crypto_poly1305_ctx, message: []const u8) void { - // Align ourselves with block boundaries - const alignm = std.math.min(alignto(ctx.c_idx, 16), message.len); - poly_update(ctx, message[0..alignm]); - - var nmessage = message[alignm..]; - - // Process the message block by block - const nb_blocks = nmessage.len >> 4; - var i: usize = 0; - while (i < nb_blocks) : (i += 1) { - ctx.c[0] = readInt(nmessage[0..4], u32, Endian.Little); - ctx.c[1] = readInt(nmessage[4..8], u32, Endian.Little); - ctx.c[2] = readInt(nmessage[8..12], u32, Endian.Little); - ctx.c[3] = readInt(nmessage[12..16], u32, Endian.Little); - poly_block(ctx); - nmessage = nmessage[16..]; + // (re-)initializes the input counter and input buffer + fn poly_clear_c(ctx: *Poly1305) void { + ctx.c[0] = 0; + ctx.c[1] = 0; + ctx.c[2] = 0; + ctx.c[3] = 0; + ctx.c_idx = 0; } - if (nb_blocks > 0) { - poly_clear_c(ctx); + + fn poly_take_input(ctx: *Poly1305, input: u8) void { + const word = ctx.c_idx >> 2; + const byte = ctx.c_idx & 3; + ctx.c[word] |= std.math.shl(u32, input, byte * 8); + ctx.c_idx += 1; } - // remaining bytes - poly_update(ctx, nmessage[0..]); -} + fn poly_update(ctx: *Poly1305, msg: []const u8) void { + for (msg) |b| { + poly_take_input(ctx, b); + if (ctx.c_idx == 16) { + poly_block(ctx); + poly_clear_c(ctx); + } + } + } -pub fn crypto_poly1305_final(ctx: *crypto_poly1305_ctx, mac: []u8) void { - // Process the last block (if any) - if (ctx.c_idx != 0) { - // move the final 1 according to remaining input length - // (We may add less than 2^130 to the last input block) - ctx.c[4] = 0; - poly_take_input(ctx, 1); - // one last hash update - poly_block(ctx); + inline fn alignto(x: usize, block_size: usize) usize { + return ((~x) +% 1) & (block_size - 1); } - // check if we should subtract 2^130-5 by performing the - // corresponding carry propagation. - const _u0 = u64(5) + ctx.h[0]; // <= 1_00000004 - const _u1 = (_u0 >> 32) + ctx.h[1]; // <= 1_00000000 - const _u2 = (_u1 >> 32) + ctx.h[2]; // <= 1_00000000 - const _u3 = (_u2 >> 32) + ctx.h[3]; // <= 1_00000000 - const _u4 = (_u3 >> 32) + ctx.h[4]; // <= 5 - // u4 indicates how many times we should subtract 2^130-5 (0 or 1) - - // h + pad, minus 2^130-5 if u4 exceeds 3 - const uu0 = (_u4 >> 2) * 5 + ctx.h[0] + ctx.pad[0]; // <= 2_00000003 - const uu1 = (uu0 >> 32) + ctx.h[1] + ctx.pad[1]; // <= 2_00000000 - const uu2 = (uu1 >> 32) + ctx.h[2] + ctx.pad[2]; // <= 2_00000000 - const uu3 = (uu2 >> 32) + ctx.h[3] + ctx.pad[3]; // <= 2_00000000 - - writeInt(mac[0..], uu0, Endian.Little); - writeInt(mac[4..], uu1, Endian.Little); - writeInt(mac[8..], uu2, Endian.Little); - writeInt(mac[12..], uu3, Endian.Little); - - ctx.secure_zero(); -} + // Feed data into the MAC context. + pub fn update(ctx: *Self, msg: []const u8) void { + // Align ourselves with block boundaries + const alignm = std.math.min(alignto(ctx.c_idx, 16), msg.len); + poly_update(ctx, msg[0..alignm]); -pub fn crypto_poly1305(mac: []u8, message: []const u8, key: [32]u8) void { - std.debug.assert(mac.len >= 16); + var nmsg = msg[alignm..]; - var ctx: crypto_poly1305_ctx = undefined; - crypto_poly1305_init(&ctx, key); - crypto_poly1305_update(&ctx, message); - crypto_poly1305_final(&ctx, mac); -} + // Process the msg block by block + const nb_blocks = nmsg.len >> 4; + var i: usize = 0; + while (i < nb_blocks) : (i += 1) { + ctx.c[0] = readInt(nmsg[0..4], u32, Endian.Little); + ctx.c[1] = readInt(nmsg[4..8], u32, Endian.Little); + ctx.c[2] = readInt(nmsg[8..12], u32, Endian.Little); + ctx.c[3] = readInt(nmsg[12..16], u32, Endian.Little); + poly_block(ctx); + nmsg = nmsg[16..]; + } + if (nb_blocks > 0) { + poly_clear_c(ctx); + } + + // remaining bytes + poly_update(ctx, nmsg[0..]); + } + + // Finalize the MAC and output into buffer provided by caller. + pub fn final(ctx: *Self, out: []u8) void { + // Process the last block (if any) + if (ctx.c_idx != 0) { + // move the final 1 according to remaining input length + // (We may add less than 2^130 to the last input block) + ctx.c[4] = 0; + poly_take_input(ctx, 1); + // one last hash update + poly_block(ctx); + } + + // check if we should subtract 2^130-5 by performing the + // corresponding carry propagation. + const _u0 = u64(5) + ctx.h[0]; // <= 1_00000004 + const _u1 = (_u0 >> 32) + ctx.h[1]; // <= 1_00000000 + const _u2 = (_u1 >> 32) + ctx.h[2]; // <= 1_00000000 + const _u3 = (_u2 >> 32) + ctx.h[3]; // <= 1_00000000 + const _u4 = (_u3 >> 32) + ctx.h[4]; // <= 5 + // u4 indicates how many times we should subtract 2^130-5 (0 or 1) + + // h + pad, minus 2^130-5 if u4 exceeds 3 + const uu0 = (_u4 >> 2) * 5 + ctx.h[0] + ctx.pad[0]; // <= 2_00000003 + const uu1 = (uu0 >> 32) + ctx.h[1] + ctx.pad[1]; // <= 2_00000000 + const uu2 = (uu1 >> 32) + ctx.h[2] + ctx.pad[2]; // <= 2_00000000 + const uu3 = (uu2 >> 32) + ctx.h[3] + ctx.pad[3]; // <= 2_00000000 + + writeInt(out[0..], @truncate(u32, uu0), Endian.Little); + writeInt(out[4..], @truncate(u32, uu1), Endian.Little); + writeInt(out[8..], @truncate(u32, uu2), Endian.Little); + writeInt(out[12..], @truncate(u32, uu3), Endian.Little); + + ctx.secure_zero(); + } +}; test "poly1305 rfc7439 vector1" { const expected_mac = "\xa8\x06\x1d\xc1\x30\x51\x36\xc6\xc2\x2b\x8b\xaf\x0c\x01\x27\xa9"; @@ -214,7 +227,7 @@ test "poly1305 rfc7439 vector1" { "\x01\x03\x80\x8a\xfb\x0d\xb2\xfd\x4a\xbf\xf6\xaf\x41\x49\xf5\x1b"; var mac: [16]u8 = undefined; - crypto_poly1305(mac[0..], msg, key); + Poly1305.create(mac[0..], msg, key); std.debug.assert(std.mem.eql(u8, mac, expected_mac)); } diff --git a/std/crypto/sha1.zig b/std/crypto/sha1.zig index 451cfb3122..6d6b4dbd3f 100644 --- a/std/crypto/sha1.zig +++ b/std/crypto/sha1.zig @@ -26,8 +26,8 @@ fn Rp(a: usize, b: usize, c: usize, d: usize, e: usize, i: u32) RoundParam { pub const Sha1 = struct { const Self = this; - const block_size = 64; - const digest_size = 20; + const block_length = 64; + const digest_length = 20; s: [5]u32, // Streaming Cache @@ -292,8 +292,8 @@ test "sha1 streaming" { } test "sha1 aligned final" { - var block = []u8{0} ** Sha1.block_size; - var out: [Sha1.digest_size]u8 = undefined; + var block = []u8{0} ** Sha1.block_length; + var out: [Sha1.digest_length]u8 = undefined; var h = Sha1.init(); h.update(block); diff --git a/std/crypto/sha2.zig b/std/crypto/sha2.zig index d1b915835c..8a25fecc43 100644 --- a/std/crypto/sha2.zig +++ b/std/crypto/sha2.zig @@ -78,8 +78,8 @@ pub const Sha256 = Sha2_32(Sha256Params); fn Sha2_32(comptime params: Sha2Params32) type { return struct { const Self = this; - const block_size = 64; - const digest_size = params.out_len / 8; + const block_length = 64; + const digest_length = params.out_len / 8; s: [8]u32, // Streaming Cache @@ -338,8 +338,8 @@ test "sha256 streaming" { } test "sha256 aligned final" { - var block = []u8{0} ** Sha256.block_size; - var out: [Sha256.digest_size]u8 = undefined; + var block = []u8{0} ** Sha256.block_length; + var out: [Sha256.digest_length]u8 = undefined; var h = Sha256.init(); h.update(block); @@ -419,8 +419,8 @@ pub const Sha512 = Sha2_64(Sha512Params); fn Sha2_64(comptime params: Sha2Params64) type { return struct { const Self = this; - const block_size = 128; - const digest_size = params.out_len / 8; + const block_length = 128; + const digest_length = params.out_len / 8; s: [8]u64, // Streaming Cache @@ -715,8 +715,8 @@ test "sha512 streaming" { } test "sha512 aligned final" { - var block = []u8{0} ** Sha512.block_size; - var out: [Sha512.digest_size]u8 = undefined; + var block = []u8{0} ** Sha512.block_length; + var out: [Sha512.digest_length]u8 = undefined; var h = Sha512.init(); h.update(block); diff --git a/std/crypto/sha3.zig b/std/crypto/sha3.zig index ae02d7a482..ade615f492 100644 --- a/std/crypto/sha3.zig +++ b/std/crypto/sha3.zig @@ -13,8 +13,8 @@ pub const Sha3_512 = Keccak(512, 0x06); fn Keccak(comptime bits: usize, comptime delim: u8) type { return struct { const Self = this; - const block_size = 200; - const digest_size = bits / 8; + const block_length = 200; + const digest_length = bits / 8; s: [200]u8, offset: usize, @@ -297,8 +297,8 @@ test "sha3-256 streaming" { } test "sha3-256 aligned final" { - var block = []u8{0} ** Sha3_256.block_size; - var out: [Sha3_256.digest_size]u8 = undefined; + var block = []u8{0} ** Sha3_256.block_length; + var out: [Sha3_256.digest_length]u8 = undefined; var h = Sha3_256.init(); h.update(block); @@ -368,8 +368,8 @@ test "sha3-512 streaming" { } test "sha3-512 aligned final" { - var block = []u8{0} ** Sha3_512.block_size; - var out: [Sha3_512.digest_size]u8 = undefined; + var block = []u8{0} ** Sha3_512.block_length; + var out: [Sha3_512.digest_length]u8 = undefined; var h = Sha3_512.init(); h.update(block); diff --git a/std/crypto/x25519.zig b/std/crypto/x25519.zig index b5b85e7104..cf88d6e1c0 100644 --- a/std/crypto/x25519.zig +++ b/std/crypto/x25519.zig @@ -9,6 +9,118 @@ const Endian = builtin.Endian; const readInt = std.mem.readInt; const writeInt = std.mem.writeInt; +// Based on Supercop's ref10 implementation. +pub const X25519 = struct { + pub const secret_length = 32; + pub const minimum_key_length = 32; + + fn trim_scalar(s: []u8) void { + s[0] &= 248; + s[31] &= 127; + s[31] |= 64; + } + + fn scalar_bit(s: []const u8, i: usize) i32 { + return (s[i >> 3] >> @intCast(u3, i & 7)) & 1; + } + + pub fn create(out: []u8, private_key: []const u8, public_key: []const u8) bool { + std.debug.assert(out.len >= secret_length); + std.debug.assert(private_key.len >= minimum_key_length); + std.debug.assert(public_key.len >= minimum_key_length); + + var storage: [7]Fe = undefined; + + var x1 = &storage[0]; + var x2 = &storage[1]; + var z2 = &storage[2]; + var x3 = &storage[3]; + var z3 = &storage[4]; + var t0 = &storage[5]; + var t1 = &storage[6]; + + // computes the scalar product + fe_frombytes(x1, public_key); + + // restrict the possible scalar values + var e: [32]u8 = undefined; + for (e[0..]) |_, i| { + e[i] = private_key[i]; + } + trim_scalar(e[0..]); + + // computes the actual scalar product (the result is in x2 and z2) + + // Montgomery ladder + // In projective coordinates, to avoid divisons: x = X / Z + // We don't care about the y coordinate, it's only 1 bit of information + fe_1(x2); + fe_0(z2); // "zero" point + fe_copy(x3, x1); + fe_1(z3); + + var swap: i32 = 0; + var pos: isize = 254; + while (pos >= 0) : (pos -= 1) { + // constant time conditional swap before ladder step + const b = scalar_bit(e, @intCast(usize, pos)); + swap ^= b; // xor trick avoids swapping at the end of the loop + fe_cswap(x2, x3, swap); + fe_cswap(z2, z3, swap); + swap = b; // anticipates one last swap after the loop + + // Montgomery ladder step: replaces (P2, P3) by (P2*2, P2+P3) + // with differential addition + fe_sub(t0, x3, z3); + fe_sub(t1, x2, z2); + fe_add(x2, x2, z2); + fe_add(z2, x3, z3); + fe_mul(z3, t0, x2); + fe_mul(z2, z2, t1); + fe_sq(t0, t1); + fe_sq(t1, x2); + fe_add(x3, z3, z2); + fe_sub(z2, z3, z2); + fe_mul(x2, t1, t0); + fe_sub(t1, t1, t0); + fe_sq(z2, z2); + fe_mul121666(z3, t1); + fe_sq(x3, x3); + fe_add(t0, t0, z3); + fe_mul(z3, x1, z2); + fe_mul(z2, t1, t0); + } + + // last swap is necessary to compensate for the xor trick + // Note: after this swap, P3 == P2 + P1. + fe_cswap(x2, x3, swap); + fe_cswap(z2, z3, swap); + + // normalises the coordinates: x == X / Z + fe_invert(z2, z2); + fe_mul(x2, x2, z2); + fe_tobytes(out, x2); + + x1.secure_zero(); + x2.secure_zero(); + x3.secure_zero(); + t0.secure_zero(); + t1.secure_zero(); + z2.secure_zero(); + z3.secure_zero(); + std.mem.secureZero(u8, e[0..]); + + // Returns false if the output is all zero + // (happens with some malicious public keys) + return !zerocmp(u8, out); + } + + pub fn createPublicKey(public_key: []const u8, private_key: []const u8) bool { + var base_point = []u8{9} ++ []u8{0} ** 31; + return create(public_key, private_key, base_point); + } +}; + // Constant time compare to zero. fn zerocmp(comptime T: type, a: []const T) bool { var s: T = 0; @@ -144,7 +256,9 @@ fn load24_le(s: []const u8) u32 { return s[0] | (u32(s[1]) << 8) | (u32(s[2]) << 16); } -fn fe_frombytes(h: *Fe, s: [32]u8) void { +fn fe_frombytes(h: *Fe, s: []const u8) void { + std.debug.assert(s.len >= 32); + var t: [10]i64 = undefined; t[0] = readInt(s[0..4], u32, Endian.Little); @@ -469,113 +583,6 @@ fn fe_isnonzero(f: *const Fe) bool { return isneg; } -/////////////// -/// X-25519 /// Taken from Supercop's ref10 implementation. -/////////////// -fn trim_scalar(s: []u8) void { - s[0] &= 248; - s[31] &= 127; - s[31] |= 64; -} - -fn scalar_bit(s: []const u8, i: usize) i32 { - return (s[i >> 3] >> @intCast(u3, i & 7)) & 1; -} - -pub fn crypto_x25519(raw_shared_secret: []u8, your_secret_key: [32]u8, their_public_key: [32]u8) bool { - std.debug.assert(raw_shared_secret.len >= 32); - - var storage: [7]Fe = undefined; - - var x1 = &storage[0]; - var x2 = &storage[1]; - var z2 = &storage[2]; - var x3 = &storage[3]; - var z3 = &storage[4]; - var t0 = &storage[5]; - var t1 = &storage[6]; - - // computes the scalar product - fe_frombytes(x1, their_public_key); - - // restrict the possible scalar values - var e: [32]u8 = undefined; - for (e[0..]) |_, i| { - e[i] = your_secret_key[i]; - } - trim_scalar(e[0..]); - - // computes the actual scalar product (the result is in x2 and z2) - - // Montgomery ladder - // In projective coordinates, to avoid divisons: x = X / Z - // We don't care about the y coordinate, it's only 1 bit of information - fe_1(x2); - fe_0(z2); // "zero" point - fe_copy(x3, x1); - fe_1(z3); - - var swap: i32 = 0; - var pos: isize = 254; - while (pos >= 0) : (pos -= 1) { - // constant time conditional swap before ladder step - const b = scalar_bit(e, @intCast(usize, pos)); - swap ^= b; // xor trick avoids swapping at the end of the loop - fe_cswap(x2, x3, swap); - fe_cswap(z2, z3, swap); - swap = b; // anticipates one last swap after the loop - - // Montgomery ladder step: replaces (P2, P3) by (P2*2, P2+P3) - // with differential addition - fe_sub(t0, x3, z3); - fe_sub(t1, x2, z2); - fe_add(x2, x2, z2); - fe_add(z2, x3, z3); - fe_mul(z3, t0, x2); - fe_mul(z2, z2, t1); - fe_sq(t0, t1); - fe_sq(t1, x2); - fe_add(x3, z3, z2); - fe_sub(z2, z3, z2); - fe_mul(x2, t1, t0); - fe_sub(t1, t1, t0); - fe_sq(z2, z2); - fe_mul121666(z3, t1); - fe_sq(x3, x3); - fe_add(t0, t0, z3); - fe_mul(z3, x1, z2); - fe_mul(z2, t1, t0); - } - - // last swap is necessary to compensate for the xor trick - // Note: after this swap, P3 == P2 + P1. - fe_cswap(x2, x3, swap); - fe_cswap(z2, z3, swap); - - // normalises the coordinates: x == X / Z - fe_invert(z2, z2); - fe_mul(x2, x2, z2); - fe_tobytes(raw_shared_secret, x2); - - x1.secure_zero(); - x2.secure_zero(); - x3.secure_zero(); - t0.secure_zero(); - t1.secure_zero(); - z2.secure_zero(); - z3.secure_zero(); - std.mem.secureZero(u8, e[0..]); - - // Returns false if the output is all zero - // (happens with some malicious public keys) - return !zerocmp(u8, raw_shared_secret); -} - -pub fn crypto_x25519_public_key(public_key: []u8, secret_key: [32]u8) void { - var base_point = []u8{9} ++ []u8{0} ** 31; - crypto_x25519(public_key, secret_key, base_point); -} - test "x25519 rfc7748 vector1" { const secret_key = "\xa5\x46\xe3\x6b\xf0\x52\x7c\x9d\x3b\x16\x15\x4b\x82\x46\x5e\xdd\x62\x14\x4c\x0a\xc1\xfc\x5a\x18\x50\x6a\x22\x44\xba\x44\x9a\xc4"; const public_key = "\xe6\xdb\x68\x67\x58\x30\x30\xdb\x35\x94\xc1\xa4\x24\xb1\x5f\x7c\x72\x66\x24\xec\x26\xb3\x35\x3b\x10\xa9\x03\xa6\xd0\xab\x1c\x4c"; @@ -584,7 +591,7 @@ test "x25519 rfc7748 vector1" { var output: [32]u8 = undefined; - std.debug.assert(crypto_x25519(output[0..], secret_key, public_key)); + std.debug.assert(X25519.create(output[0..], secret_key, public_key)); std.debug.assert(std.mem.eql(u8, output, expected_output)); } @@ -596,7 +603,7 @@ test "x25519 rfc7748 vector2" { var output: [32]u8 = undefined; - std.debug.assert(crypto_x25519(output[0..], secret_key, public_key)); + std.debug.assert(X25519.create(output[0..], secret_key, public_key)); std.debug.assert(std.mem.eql(u8, output, expected_output)); } @@ -610,7 +617,7 @@ test "x25519 rfc7748 one iteration" { var i: usize = 0; while (i < 1) : (i += 1) { var output: [32]u8 = undefined; - std.debug.assert(crypto_x25519(output[0..], k, u)); + std.debug.assert(X25519.create(output[0..], k, u)); std.mem.copy(u8, u[0..], k[0..]); std.mem.copy(u8, k[0..], output[0..]); @@ -634,7 +641,7 @@ test "x25519 rfc7748 1,000 iterations" { var i: usize = 0; while (i < 1000) : (i += 1) { var output: [32]u8 = undefined; - std.debug.assert(crypto_x25519(output[0..], k, u)); + std.debug.assert(X25519.create(output[0..], k, u)); std.mem.copy(u8, u[0..], k[0..]); std.mem.copy(u8, k[0..], output[0..]); @@ -657,7 +664,7 @@ test "x25519 rfc7748 1,000,000 iterations" { var i: usize = 0; while (i < 1000000) : (i += 1) { var output: [32]u8 = undefined; - std.debug.assert(crypto_x25519(output[0..], k, u)); + std.debug.assert(X25519.create(output[0..], k, u)); std.mem.copy(u8, u[0..], k[0..]); std.mem.copy(u8, k[0..], output[0..]); |
