mirror of
https://github.com/Ed94/Odin.git
synced 2026-06-14 18:02:22 -07:00
e86bb3a795
Assertions can be disabled, but at the point where cryptographic anything is involved, a single branch has an infinitesimally small performance impact. The correct thing to do is to punch the caller in the face if they do something that is blatantly incorrect, especially in a security critical setting.
364 lines
9.4 KiB
Odin
364 lines
9.4 KiB
Odin
package siphash
|
|
|
|
/*
|
|
Copyright 2022 zhibog
|
|
Made available under the BSD-3 license.
|
|
|
|
List of contributors:
|
|
zhibog: Initial implementation.
|
|
|
|
Implementation of the SipHash hashing algorithm, as defined at <https://github.com/veorq/SipHash> and <https://www.aumasson.jp/siphash/siphash.pdf>
|
|
|
|
Use the specific procedures for a certain setup. The generic procdedures will default to Siphash 2-4
|
|
*/
|
|
|
|
import "core:crypto"
|
|
import "core:encoding/endian"
|
|
import "core:math/bits"
|
|
|
|
/*
|
|
High level API
|
|
*/
|
|
|
|
KEY_SIZE :: 16
|
|
DIGEST_SIZE :: 8
|
|
|
|
// sum_string_1_3 will hash the given message with the key and return
|
|
// the computed hash as a u64
|
|
sum_string_1_3 :: proc(msg, key: string) -> u64 {
|
|
return sum_bytes_1_3(transmute([]byte)(msg), transmute([]byte)(key))
|
|
}
|
|
|
|
// sum_bytes_1_3 will hash the given message with the key and return
|
|
// the computed hash as a u64
|
|
sum_bytes_1_3 :: proc(msg, key: []byte) -> u64 {
|
|
ctx: Context
|
|
hash: u64
|
|
init(&ctx, key, 1, 3)
|
|
update(&ctx, msg)
|
|
final(&ctx, &hash)
|
|
return hash
|
|
}
|
|
|
|
// sum_string_to_buffer_1_3 will hash the given message with the key and write
|
|
// the computed hash into the provided destination buffer
|
|
sum_string_to_buffer_1_3 :: proc(msg, key: string, dst: []byte) {
|
|
sum_bytes_to_buffer_1_3(transmute([]byte)(msg), transmute([]byte)(key), dst)
|
|
}
|
|
|
|
// sum_bytes_to_buffer_1_3 will hash the given message with the key and write
|
|
// the computed hash into the provided destination buffer
|
|
sum_bytes_to_buffer_1_3 :: proc(msg, key, dst: []byte) {
|
|
hash := sum_bytes_1_3(msg, key)
|
|
_collect_output(dst[:], hash)
|
|
}
|
|
|
|
sum_1_3 :: proc {
|
|
sum_string_1_3,
|
|
sum_bytes_1_3,
|
|
sum_string_to_buffer_1_3,
|
|
sum_bytes_to_buffer_1_3,
|
|
}
|
|
|
|
// verify_u64_1_3 will check if the supplied tag matches with the output you
|
|
// will get from the provided message and key
|
|
verify_u64_1_3 :: proc(tag: u64, msg, key: []byte) -> bool {
|
|
return sum_bytes_1_3(msg, key) == tag
|
|
}
|
|
|
|
// verify_bytes will check if the supplied tag matches with the output you
|
|
// will get from the provided message and key
|
|
verify_bytes_1_3 :: proc(tag, msg, key: []byte) -> bool {
|
|
derived_tag: [8]byte
|
|
sum_bytes_to_buffer_1_3(msg, key, derived_tag[:])
|
|
return crypto.compare_constant_time(derived_tag[:], tag) == 1
|
|
}
|
|
|
|
verify_1_3 :: proc {
|
|
verify_bytes_1_3,
|
|
verify_u64_1_3,
|
|
}
|
|
|
|
// sum_string_2_4 will hash the given message with the key and return
|
|
// the computed hash as a u64
|
|
sum_string_2_4 :: proc(msg, key: string) -> u64 {
|
|
return sum_bytes_2_4(transmute([]byte)(msg), transmute([]byte)(key))
|
|
}
|
|
|
|
// sum_bytes_2_4 will hash the given message with the key and return
|
|
// the computed hash as a u64
|
|
sum_bytes_2_4 :: proc(msg, key: []byte) -> u64 {
|
|
ctx: Context
|
|
hash: u64
|
|
init(&ctx, key, 2, 4)
|
|
update(&ctx, msg)
|
|
final(&ctx, &hash)
|
|
return hash
|
|
}
|
|
|
|
// sum_string_to_buffer_2_4 will hash the given message with the key and write
|
|
// the computed hash into the provided destination buffer
|
|
sum_string_to_buffer_2_4 :: proc(msg, key: string, dst: []byte) {
|
|
sum_bytes_to_buffer_2_4(transmute([]byte)(msg), transmute([]byte)(key), dst)
|
|
}
|
|
|
|
// sum_bytes_to_buffer_2_4 will hash the given message with the key and write
|
|
// the computed hash into the provided destination buffer
|
|
sum_bytes_to_buffer_2_4 :: proc(msg, key, dst: []byte) {
|
|
hash := sum_bytes_2_4(msg, key)
|
|
_collect_output(dst[:], hash)
|
|
}
|
|
|
|
sum_2_4 :: proc {
|
|
sum_string_2_4,
|
|
sum_bytes_2_4,
|
|
sum_string_to_buffer_2_4,
|
|
sum_bytes_to_buffer_2_4,
|
|
}
|
|
|
|
sum_string :: sum_string_2_4
|
|
sum_bytes :: sum_bytes_2_4
|
|
sum_string_to_buffer :: sum_string_to_buffer_2_4
|
|
sum_bytes_to_buffer :: sum_bytes_to_buffer_2_4
|
|
sum :: proc {
|
|
sum_string,
|
|
sum_bytes,
|
|
sum_string_to_buffer,
|
|
sum_bytes_to_buffer,
|
|
}
|
|
|
|
// verify_u64_2_4 will check if the supplied tag matches with the output you
|
|
// will get from the provided message and key
|
|
verify_u64_2_4 :: proc(tag: u64, msg, key: []byte) -> bool {
|
|
return sum_bytes_2_4(msg, key) == tag
|
|
}
|
|
|
|
// verify_bytes will check if the supplied tag matches with the output you
|
|
// will get from the provided message and key
|
|
verify_bytes_2_4 :: proc(tag, msg, key: []byte) -> bool {
|
|
derived_tag: [8]byte
|
|
sum_bytes_to_buffer_2_4(msg, key, derived_tag[:])
|
|
return crypto.compare_constant_time(derived_tag[:], tag) == 1
|
|
}
|
|
|
|
verify_2_4 :: proc {
|
|
verify_bytes_2_4,
|
|
verify_u64_2_4,
|
|
}
|
|
|
|
verify_bytes :: verify_bytes_2_4
|
|
verify_u64 :: verify_u64_2_4
|
|
verify :: proc {
|
|
verify_bytes,
|
|
verify_u64,
|
|
}
|
|
|
|
// sum_string_4_8 will hash the given message with the key and return
|
|
// the computed hash as a u64
|
|
sum_string_4_8 :: proc(msg, key: string) -> u64 {
|
|
return sum_bytes_4_8(transmute([]byte)(msg), transmute([]byte)(key))
|
|
}
|
|
|
|
// sum_bytes_4_8 will hash the given message with the key and return
|
|
// the computed hash as a u64
|
|
sum_bytes_4_8 :: proc(msg, key: []byte) -> u64 {
|
|
ctx: Context
|
|
hash: u64
|
|
init(&ctx, key, 4, 8)
|
|
update(&ctx, msg)
|
|
final(&ctx, &hash)
|
|
return hash
|
|
}
|
|
|
|
// sum_string_to_buffer_4_8 will hash the given message with the key and write
|
|
// the computed hash into the provided destination buffer
|
|
sum_string_to_buffer_4_8 :: proc(msg, key: string, dst: []byte) {
|
|
sum_bytes_to_buffer_4_8(transmute([]byte)(msg), transmute([]byte)(key), dst)
|
|
}
|
|
|
|
// sum_bytes_to_buffer_4_8 will hash the given message with the key and write
|
|
// the computed hash into the provided destination buffer
|
|
sum_bytes_to_buffer_4_8 :: proc(msg, key, dst: []byte) {
|
|
hash := sum_bytes_4_8(msg, key)
|
|
_collect_output(dst[:], hash)
|
|
}
|
|
|
|
sum_4_8 :: proc {
|
|
sum_string_4_8,
|
|
sum_bytes_4_8,
|
|
sum_string_to_buffer_4_8,
|
|
sum_bytes_to_buffer_4_8,
|
|
}
|
|
|
|
// verify_u64_4_8 will check if the supplied tag matches with the output you
|
|
// will get from the provided message and key
|
|
verify_u64_4_8 :: proc(tag: u64, msg, key: []byte) -> bool {
|
|
return sum_bytes_4_8(msg, key) == tag
|
|
}
|
|
|
|
// verify_bytes will check if the supplied tag matches with the output you
|
|
// will get from the provided message and key
|
|
verify_bytes_4_8 :: proc(tag, msg, key: []byte) -> bool {
|
|
derived_tag: [8]byte
|
|
sum_bytes_to_buffer_4_8(msg, key, derived_tag[:])
|
|
return crypto.compare_constant_time(derived_tag[:], tag) == 1
|
|
}
|
|
|
|
verify_4_8 :: proc {
|
|
verify_bytes_4_8,
|
|
verify_u64_4_8,
|
|
}
|
|
|
|
/*
|
|
Low level API
|
|
*/
|
|
|
|
init :: proc(ctx: ^Context, key: []byte, c_rounds, d_rounds: int) {
|
|
if len(key) != KEY_SIZE {
|
|
panic("crypto/siphash; invalid key size")
|
|
}
|
|
ctx.c_rounds = c_rounds
|
|
ctx.d_rounds = d_rounds
|
|
is_valid_setting :=
|
|
(ctx.c_rounds == 1 && ctx.d_rounds == 3) ||
|
|
(ctx.c_rounds == 2 && ctx.d_rounds == 4) ||
|
|
(ctx.c_rounds == 4 && ctx.d_rounds == 8)
|
|
if !is_valid_setting {
|
|
panic("crypto/siphash: incorrect rounds set up")
|
|
}
|
|
ctx.k0 = endian.unchecked_get_u64le(key[:8])
|
|
ctx.k1 = endian.unchecked_get_u64le(key[8:])
|
|
ctx.v0 = 0x736f6d6570736575 ~ ctx.k0
|
|
ctx.v1 = 0x646f72616e646f6d ~ ctx.k1
|
|
ctx.v2 = 0x6c7967656e657261 ~ ctx.k0
|
|
ctx.v3 = 0x7465646279746573 ~ ctx.k1
|
|
|
|
ctx.last_block = 0
|
|
ctx.total_length = 0
|
|
|
|
ctx.is_initialized = true
|
|
}
|
|
|
|
update :: proc(ctx: ^Context, data: []byte) {
|
|
assert(ctx.is_initialized, "crypto/siphash: context is not initialized")
|
|
|
|
data := data
|
|
ctx.total_length += len(data)
|
|
if ctx.last_block > 0 {
|
|
n := copy(ctx.buf[ctx.last_block:], data)
|
|
ctx.last_block += n
|
|
if ctx.last_block == BLOCK_SIZE {
|
|
block(ctx, ctx.buf[:])
|
|
ctx.last_block = 0
|
|
}
|
|
data = data[n:]
|
|
}
|
|
if len(data) >= BLOCK_SIZE {
|
|
n := len(data) &~ (BLOCK_SIZE - 1)
|
|
block(ctx, data[:n])
|
|
data = data[n:]
|
|
}
|
|
if len(data) > 0 {
|
|
ctx.last_block = copy(ctx.buf[:], data)
|
|
}
|
|
}
|
|
|
|
final :: proc(ctx: ^Context, dst: ^u64) {
|
|
assert(ctx.is_initialized, "crypto/siphash: context is not initialized")
|
|
|
|
tmp: [BLOCK_SIZE]byte
|
|
copy(tmp[:], ctx.buf[:ctx.last_block])
|
|
tmp[7] = byte(ctx.total_length & 0xff)
|
|
block(ctx, tmp[:])
|
|
|
|
ctx.v2 ~= 0xff
|
|
|
|
for _ in 0 ..< ctx.d_rounds {
|
|
_compress(ctx)
|
|
}
|
|
|
|
dst^ = ctx.v0 ~ ctx.v1 ~ ctx.v2 ~ ctx.v3
|
|
|
|
reset(ctx)
|
|
}
|
|
|
|
reset :: proc(ctx: ^Context) {
|
|
ctx.k0, ctx.k1 = 0, 0
|
|
ctx.v0, ctx.v1 = 0, 0
|
|
ctx.v2, ctx.v3 = 0, 0
|
|
ctx.last_block = 0
|
|
ctx.total_length = 0
|
|
ctx.c_rounds = 0
|
|
ctx.d_rounds = 0
|
|
ctx.is_initialized = false
|
|
}
|
|
|
|
BLOCK_SIZE :: 8
|
|
|
|
Context :: struct {
|
|
v0, v1, v2, v3: u64, // State values
|
|
k0, k1: u64, // Split key
|
|
c_rounds: int, // Number of message rounds
|
|
d_rounds: int, // Number of finalization rounds
|
|
buf: [BLOCK_SIZE]byte, // Provided data
|
|
last_block: int, // Offset from the last block
|
|
total_length: int,
|
|
is_initialized: bool,
|
|
}
|
|
|
|
@(private)
|
|
block :: proc "contextless" (ctx: ^Context, buf: []byte) {
|
|
buf := buf
|
|
|
|
for len(buf) >= BLOCK_SIZE {
|
|
m := endian.unchecked_get_u64le(buf)
|
|
|
|
ctx.v3 ~= m
|
|
for _ in 0 ..< ctx.c_rounds {
|
|
_compress(ctx)
|
|
}
|
|
|
|
ctx.v0 ~= m
|
|
|
|
buf = buf[BLOCK_SIZE:]
|
|
}
|
|
}
|
|
|
|
@(private)
|
|
_get_byte :: #force_inline proc "contextless" (byte_num: byte, into: u64) -> byte {
|
|
return byte(into >> (((~byte_num) & (size_of(u64) - 1)) << 3))
|
|
}
|
|
|
|
@(private)
|
|
_collect_output :: #force_inline proc(dst: []byte, hash: u64) {
|
|
if len(dst) < DIGEST_SIZE {
|
|
panic("crypto/siphash: invalid tag size")
|
|
}
|
|
dst[0] = _get_byte(7, hash)
|
|
dst[1] = _get_byte(6, hash)
|
|
dst[2] = _get_byte(5, hash)
|
|
dst[3] = _get_byte(4, hash)
|
|
dst[4] = _get_byte(3, hash)
|
|
dst[5] = _get_byte(2, hash)
|
|
dst[6] = _get_byte(1, hash)
|
|
dst[7] = _get_byte(0, hash)
|
|
}
|
|
|
|
@(private)
|
|
_compress :: #force_inline proc "contextless" (ctx: ^Context) {
|
|
ctx.v0 += ctx.v1
|
|
ctx.v1 = bits.rotate_left64(ctx.v1, 13)
|
|
ctx.v1 ~= ctx.v0
|
|
ctx.v0 = bits.rotate_left64(ctx.v0, 32)
|
|
ctx.v2 += ctx.v3
|
|
ctx.v3 = bits.rotate_left64(ctx.v3, 16)
|
|
ctx.v3 ~= ctx.v2
|
|
ctx.v0 += ctx.v3
|
|
ctx.v3 = bits.rotate_left64(ctx.v3, 21)
|
|
ctx.v3 ~= ctx.v0
|
|
ctx.v2 += ctx.v1
|
|
ctx.v1 = bits.rotate_left64(ctx.v1, 17)
|
|
ctx.v1 ~= ctx.v2
|
|
ctx.v2 = bits.rotate_left64(ctx.v2, 32)
|
|
}
|