Commit e919744c7a

Frank Denis <github@pureftpd.org>
2020-08-22 01:11:24
Promote hash/siphash to crypto/siphash
SipHash *is* a cryptographic function, with a 128-bit security level. However, it is not a regular hash function: a secret key is required, and knowledge of that key allows collisions to be quickly computed offline. SipHash is therefore more suitable to be used as a MAC. The same API as other MACs was implemented in addition to functions directly returning an integer. The benchmarks have been updated accordingly. No changes to the SipHash implementation itself.
1 parent 9ab4281
Changed files (5)
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 {
lib/std/hash/siphash.zig → 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);
     }
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",
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");
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");