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.
master
Frank Denis 2020-08-22 01:11:24 +02:00 committed by Andrew Kelley
parent 9ab4281856
commit e919744c7a
5 changed files with 65 additions and 39 deletions

View File

@ -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");

View File

@ -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 {

View File

@ -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);
}

View File

@ -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");

View File

@ -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",