diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index 32ab5071c..5de2f1389 100644 --- a/lib/std/crypto.zig +++ b/lib/std/crypto.zig @@ -18,6 +18,7 @@ pub const hash = struct { /// Authentication (MAC) functions. pub const auth = struct { pub const hmac = @import("crypto/hmac.zig"); + pub const siphash = @import("crypto/siphash.zig"); }; /// Authenticated Encryption with Associated Data @@ -80,6 +81,7 @@ test "crypto" { _ = @import("crypto/sha1.zig"); _ = @import("crypto/sha2.zig"); _ = @import("crypto/sha3.zig"); + _ = @import("crypto/siphash.zig"); _ = @import("crypto/25519/curve25519.zig"); _ = @import("crypto/25519/ed25519.zig"); _ = @import("crypto/25519/edwards25519.zig"); diff --git a/lib/std/crypto/benchmark.zig b/lib/std/crypto/benchmark.zig index 70bd30d6b..d9c83992c 100644 --- a/lib/std/crypto/benchmark.zig +++ b/lib/std/crypto/benchmark.zig @@ -60,6 +60,10 @@ const macs = [_]Crypto{ Crypto{ .ty = crypto.auth.hmac.HmacSha1, .name = "hmac-sha1" }, Crypto{ .ty = crypto.auth.hmac.sha2.HmacSha256, .name = "hmac-sha256" }, Crypto{ .ty = crypto.auth.hmac.sha2.HmacSha512, .name = "hmac-sha512" }, + Crypto{ .ty = crypto.auth.siphash.SipHash64(2, 4), .name = "siphash-2-4" }, + Crypto{ .ty = crypto.auth.siphash.SipHash64(1, 3), .name = "siphash-1-3" }, + Crypto{ .ty = crypto.auth.siphash.SipHash128(2, 4), .name = "siphash128-2-4" }, + Crypto{ .ty = crypto.auth.siphash.SipHash128(1, 3), .name = "siphash128-1-3" }, }; pub fn benchmarkMac(comptime Mac: anytype, comptime bytes: comptime_int) !u64 { diff --git a/lib/std/hash/siphash.zig b/lib/std/crypto/siphash.zig similarity index 82% rename from lib/std/hash/siphash.zig rename to lib/std/crypto/siphash.zig index 107ea1972..c4d8735b1 100644 --- a/lib/std/hash/siphash.zig +++ b/lib/std/crypto/siphash.zig @@ -3,25 +3,39 @@ // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. -// Siphash // -// SipHash is a moderately fast, non-cryptographic keyed hash function designed for resistance -// against hash flooding DoS attacks. +// SipHash is a moderately fast pseudorandom function, returning a 64-bit or 128-bit tag for an arbitrary long input. +// +// Typical use cases include: +// - protection against against DoS attacks for hash tables and bloom filters +// - authentication of short-lived messages in online protocols // // https://131002.net/siphash/ - const std = @import("../std.zig"); const assert = std.debug.assert; const testing = std.testing; const math = std.math; const mem = std.mem; -const Endian = std.builtin.Endian; - +/// SipHash function with 64-bit output. +/// +/// Recommended parameters are: +/// - (c_rounds=2, d_rounds=4) standard parameters. +/// - (c_rounds=1, d_rounds=2) reduced-round function. Faster, no known implications on its practical security level. +/// +/// SipHash is not a traditional hash function. If the input includes untrusted content, a secret key is absolutely necessary. +/// And due to its small output size, collisions in SipHash64 can be found with an exhaustive search. pub fn SipHash64(comptime c_rounds: usize, comptime d_rounds: usize) type { return SipHash(u64, c_rounds, d_rounds); } +/// SipHash function with 128-bit output. +/// +/// Recommended parameters are: +/// - (c_rounds=2, d_rounds=4) standard parameters. +/// - (c_rounds=1, d_rounds=2) reduced-round function. Faster, no known implications on its practical security level. +/// +/// SipHash is not a traditional hash function. If the input includes untrusted content, a secret key is absolutely necessary. pub fn SipHash128(comptime c_rounds: usize, comptime d_rounds: usize) type { return SipHash(u128, c_rounds, d_rounds); } @@ -146,30 +160,31 @@ fn SipHashStateless(comptime T: type, comptime c_rounds: usize, comptime d_round d.v2 = math.rotl(u64, d.v2, @as(u64, 32)); } - pub fn hash(key: []const u8, input: []const u8) T { - const aligned_len = input.len - (input.len % 8); - + pub fn hash(msg: []const u8, key: []const u8) T { + const aligned_len = msg.len - (msg.len % 8); var c = Self.init(key); - @call(.{ .modifier = .always_inline }, c.update, .{input[0..aligned_len]}); - return @call(.{ .modifier = .always_inline }, c.final, .{input[aligned_len..]}); + @call(.{ .modifier = .always_inline }, c.update, .{msg[0..aligned_len]}); + return @call(.{ .modifier = .always_inline }, c.final, .{msg[aligned_len..]}); } }; } -pub fn SipHash(comptime T: type, comptime c_rounds: usize, comptime d_rounds: usize) type { +fn SipHash(comptime T: type, comptime c_rounds: usize, comptime d_rounds: usize) type { assert(T == u64 or T == u128); assert(c_rounds > 0 and d_rounds > 0); return struct { const State = SipHashStateless(T, c_rounds, d_rounds); const Self = @This(); - const digest_size = 64; - const block_size = 64; + pub const minimum_key_length = 16; + pub const mac_length = @sizeOf(T); + pub const block_length = 8; state: State, buf: [8]u8, buf_len: usize, + /// Initialize a state for a SipHash function pub fn init(key: []const u8) Self { return Self{ .state = State.init(key), @@ -178,6 +193,7 @@ pub fn SipHash(comptime T: type, comptime c_rounds: usize, comptime d_rounds: us }; } + /// Add data to the state pub fn update(self: *Self, b: []const u8) void { var off: usize = 0; @@ -196,12 +212,27 @@ pub fn SipHash(comptime T: type, comptime c_rounds: usize, comptime d_rounds: us self.buf_len += @intCast(u8, b[off + aligned_len ..].len); } - pub fn final(self: *Self) T { + /// Return an authentication tag for the current state + pub fn final(self: *Self, out: []u8) void { + std.debug.assert(out.len >= mac_length); + mem.writeIntLittle(T, out[0..mac_length], self.state.final(self.buf[0..self.buf_len])); + } + + /// Return an authentication tag for a message and a key + pub fn create(out: []u8, msg: []const u8, key: []const u8) void { + var ctx = Self.init(key); + ctx.update(msg); + ctx.final(out[0..]); + } + + /// Return an authentication tag for the current state, as an integer + pub fn finalInt(self: *Self) T { return self.state.final(self.buf[0..self.buf_len]); } - pub fn hash(key: []const u8, input: []const u8) T { - return State.hash(key, input); + /// Return an authentication tag for a message and a key, as an integer + pub fn toInt(msg: []const u8, key: []const u8) T { + return State.hash(msg, key); } }; } @@ -284,8 +315,9 @@ test "siphash64-2-4 sanity" { for (vectors) |vector, i| { buffer[i] = @intCast(u8, i); - const expected = mem.readIntLittle(u64, &vector); - testing.expectEqual(siphash.hash(test_key, buffer[0..i]), expected); + var out: [siphash.mac_length]u8 = undefined; + siphash.create(&out, buffer[0..i], test_key); + testing.expectEqual(out, vector); } } @@ -363,8 +395,9 @@ test "siphash128-2-4 sanity" { for (vectors) |vector, i| { buffer[i] = @intCast(u8, i); - const expected = mem.readIntLittle(u128, &vector); - testing.expectEqual(siphash.hash(test_key, buffer[0..i]), expected); + var out: [siphash.mac_length]u8 = undefined; + siphash.create(&out, buffer[0..i], test_key[0..]); + testing.expectEqual(out, vector); } } @@ -379,14 +412,14 @@ test "iterative non-divisible update" { var end: usize = 9; while (end < buf.len) : (end += 9) { - const non_iterative_hash = Siphash.hash(key, buf[0..end]); + const non_iterative_hash = Siphash.toInt(buf[0..end], key[0..]); - var wy = Siphash.init(key); + var siphash = Siphash.init(key); var i: usize = 0; while (i < end) : (i += 7) { - wy.update(buf[i..std.math.min(i + 7, end)]); + siphash.update(buf[i..std.math.min(i + 7, end)]); } - const iterative_hash = wy.final(); + const iterative_hash = siphash.finalInt(); std.testing.expectEqual(iterative_hash, non_iterative_hash); } diff --git a/lib/std/hash.zig b/lib/std/hash.zig index 1cc078959..7bac37831 100644 --- a/lib/std/hash.zig +++ b/lib/std/hash.zig @@ -20,7 +20,7 @@ pub const Fnv1a_32 = fnv.Fnv1a_32; pub const Fnv1a_64 = fnv.Fnv1a_64; pub const Fnv1a_128 = fnv.Fnv1a_128; -const siphash = @import("hash/siphash.zig"); +const siphash = @import("crypto/siphash.zig"); pub const SipHash64 = siphash.SipHash64; pub const SipHash128 = siphash.SipHash128; @@ -42,7 +42,6 @@ test "hash" { _ = @import("hash/auto_hash.zig"); _ = @import("hash/crc.zig"); _ = @import("hash/fnv.zig"); - _ = @import("hash/siphash.zig"); _ = @import("hash/murmur.zig"); _ = @import("hash/cityhash.zig"); _ = @import("hash/wyhash.zig"); diff --git a/lib/std/hash/benchmark.zig b/lib/std/hash/benchmark.zig index c23743160..f0cafa997 100644 --- a/lib/std/hash/benchmark.zig +++ b/lib/std/hash/benchmark.zig @@ -25,24 +25,12 @@ const Hash = struct { init_u64: ?u64 = null, }; -const siphash_key = "0123456789abcdef"; - const hashes = [_]Hash{ Hash{ .ty = hash.Wyhash, .name = "wyhash", .init_u64 = 0, }, - Hash{ - .ty = hash.SipHash64(1, 3), - .name = "siphash(1,3)", - .init_u8s = siphash_key, - }, - Hash{ - .ty = hash.SipHash64(2, 4), - .name = "siphash(2,4)", - .init_u8s = siphash_key, - }, Hash{ .ty = hash.Fnv1a_64, .name = "fnv1a",