From: Ondřej Surý Date: Thu, 2 Apr 2026 06:52:14 +0000 (+0200) Subject: Use SipHash-1-3 for hash tables, keep SipHash-2-4 for cookies X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6175577210af58f778e65ba430958c614fd17ff4;p=thirdparty%2Fbind9.git Use SipHash-1-3 for hash tables, keep SipHash-2-4 for cookies SipHash-2-4 was designed as a conservative PRF/MAC with extra rounds against future attacks. For hash tables, where outputs are never exposed, SipHash-1-3 provides sufficient collision resistance with fewer rounds. As the SipHash author noted: "I would be very surprised if SipHash-1-3 introduced weaknesses for hash tables." DNS cookies continue to use SipHash-2-4 since cookie values are sent on the wire and must resist online attacks. --- diff --git a/lib/isc/hash.c b/lib/isc/hash.c index 49b942ef5bc..8d601736490 100644 --- a/lib/isc/hash.c +++ b/lib/isc/hash.c @@ -55,7 +55,7 @@ isc_hash_set_initializer(const void *initializer) { void isc_hash32_init(isc_hash32_t *restrict state) { - isc_halfsiphash24_init(state, isc_hash_key); + isc_halfsiphash13_init(state, isc_hash_key); } void @@ -63,21 +63,21 @@ isc_hash32_hash(isc_hash32_t *restrict state, const void *data, const size_t length, const bool case_sensitive) { REQUIRE(length == 0 || data != NULL); - isc_halfsiphash24_hash(state, data, length, case_sensitive); + isc_halfsiphash13_hash(state, data, length, case_sensitive); } uint32_t isc_hash32_finalize(isc_hash32_t *restrict state) { uint32_t hval; - isc_halfsiphash24_finalize(state, (uint8_t *)&hval); + isc_halfsiphash13_finalize(state, (uint8_t *)&hval); return hval; } void isc_hash64_init(isc_hash64_t *restrict state) { - isc_siphash24_init(state, isc_hash_key); + isc_siphash13_init(state, isc_hash_key); } void @@ -85,14 +85,14 @@ isc_hash64_hash(isc_hash64_t *restrict state, const void *data, const size_t length, const bool case_sensitive) { REQUIRE(length == 0 || data != NULL); - isc_siphash24_hash(state, data, length, case_sensitive); + isc_siphash13_hash(state, data, length, case_sensitive); } uint64_t isc_hash64_finalize(isc_hash64_t *restrict state) { uint64_t hval; - isc_siphash24_finalize(state, (uint8_t *)&hval); + isc_siphash13_finalize(state, (uint8_t *)&hval); return hval; } diff --git a/lib/isc/include/isc/hash.h b/lib/isc/include/isc/hash.h index 676df77de84..75584e25bd0 100644 --- a/lib/isc/include/isc/hash.h +++ b/lib/isc/include/isc/hash.h @@ -26,8 +26,8 @@ #define ISC_HASH_MIN_BITS 2U #define ISC_HASH_MAX_BITS 32U -typedef struct isc_halfsiphash24 isc_hash32_t; -typedef struct isc_siphash24 isc_hash64_t; +typedef isc_halfsiphash13_t isc_hash32_t; +typedef isc_siphash13_t isc_hash64_t; /*** *** Functions diff --git a/lib/isc/include/isc/siphash.h b/lib/isc/include/isc/siphash.h index e4df6aaf8c7..e9f9b8c14ed 100644 --- a/lib/isc/include/isc/siphash.h +++ b/lib/isc/include/isc/siphash.h @@ -41,8 +41,11 @@ #define ISC_HALFSIPHASH24_KEY_LENGTH 64 / 8 #define ISC_HALFSIPHASH24_TAG_LENGTH 32 / 8 -#define cROUNDS 2 -#define dROUNDS 4 +#define cROUNDS_24 2 +#define dROUNDS_24 4 + +#define cROUNDS_13 1 +#define dROUNDS_13 3 #define HALF_ROUND64(a, b, c, d, s, t) \ a += b; \ @@ -125,7 +128,7 @@ static inline void isc_siphash24_one(isc_siphash24_t *restrict state, const uint64_t m) { state->v3 ^= m; - for (size_t i = 0; i < cROUNDS; ++i) { + for (size_t i = 0; i < cROUNDS_24; ++i) { SIPROUND(state->v0, state->v1, state->v2, state->v3); } @@ -270,7 +273,7 @@ isc_siphash24_finalize(isc_siphash24_t *restrict state, uint8_t *out) { state->v2 ^= 0xff; - for (size_t i = 0; i < dROUNDS; ++i) { + for (size_t i = 0; i < dROUNDS_24; ++i) { SIPROUND(state->v0, state->v1, state->v2, state->v3); } @@ -288,6 +291,200 @@ isc_siphash24(const uint8_t *key, const uint8_t *in, const size_t inlen, isc_siphash24_finalize(&state, out); } +/* + * SipHash-1-3: fewer rounds, suitable for hash tables where outputs + * are never exposed. Uses the same state and init/hash functions as + * SipHash-2-4; only the round counts in _one and _finalize differ. + */ +typedef isc_siphash24_t isc_siphash13_t; + +#define ISC_SIPHASH13_KEY_LENGTH ISC_SIPHASH24_KEY_LENGTH +#define ISC_SIPHASH13_TAG_LENGTH ISC_SIPHASH24_TAG_LENGTH + +static inline void +isc_siphash13_init(isc_siphash24_t *state, const uint8_t *k) { + REQUIRE(k != NULL); + + uint64_t k0 = ISC_U8TO64_LE(k); + uint64_t k1 = ISC_U8TO64_LE(k + 8); + + *state = (isc_siphash24_t){ + .k0 = k0, + .k1 = k1, + .v0 = UINT64_C(0x736f6d6570736575) ^ k0, + .v1 = UINT64_C(0x646f72616e646f6d) ^ k1, + .v2 = UINT64_C(0x6c7967656e657261) ^ k0, + .v3 = UINT64_C(0x7465646279746573) ^ k1, + }; +} + +static inline void +isc_siphash13_one(isc_siphash13_t *restrict state, const uint64_t m) { + state->v3 ^= m; + + for (size_t i = 0; i < cROUNDS_13; ++i) { + SIPROUND(state->v0, state->v1, state->v2, state->v3); + } + + state->v0 ^= m; +} + +static inline void +isc_siphash13_hash(isc_siphash13_t *restrict state, const uint8_t *in, + const size_t inlen, const bool case_sensitive) { + REQUIRE(inlen == 0 || in != NULL); + + if (in == NULL || inlen == 0) { + return; + } + + size_t len = inlen; + const int right = state->inlen & 7; + + switch (right) { + case 0: + break; + case 1: + state->b |= U8TO64_ONE(case_sensitive, in[0]) << 8; + state->inlen++; + ++in; + + if (--len == 0) { + return; + } + FALLTHROUGH; + case 2: + state->b |= U8TO64_ONE(case_sensitive, in[0]) << 16; + state->inlen++; + ++in; + + if (--len == 0) { + return; + } + FALLTHROUGH; + case 3: + state->b |= U8TO64_ONE(case_sensitive, in[0]) << 24; + state->inlen++; + ++in; + + if (--len == 0) { + return; + } + FALLTHROUGH; + case 4: + state->b |= U8TO64_ONE(case_sensitive, in[0]) << 32; + state->inlen++; + ++in; + + if (--len == 0) { + return; + } + FALLTHROUGH; + case 5: + state->b |= U8TO64_ONE(case_sensitive, in[0]) << 40; + state->inlen++; + ++in; + + if (--len == 0) { + return; + } + FALLTHROUGH; + case 6: + state->b |= U8TO64_ONE(case_sensitive, in[0]) << 48; + state->inlen++; + ++in; + + if (--len == 0) { + return; + } + FALLTHROUGH; + case 7: + state->b |= U8TO64_ONE(case_sensitive, in[0]) << 56; + state->inlen++; + ++in; + + isc_siphash13_one(state, state->b); + state->b = 0; /* consumed */ + + if (--len == 0) { + return; + } + break; + default: + UNREACHABLE(); + } + + const uint8_t *end = in + len - (len % sizeof(uint64_t)); + const size_t left = len & 7; + + for (; in != end; in += 8) { + uint64_t m = case_sensitive + ? ISC_U8TO64_LE(in) + : isc_ascii_tolower8(ISC_U8TO64_LE(in)); + + isc_siphash13_one(state, m); + } + + INSIST(state->b == 0); + switch (left) { + case 7: + state->b |= U8TO64_ONE(case_sensitive, in[6]) << 48; + FALLTHROUGH; + case 6: + state->b |= U8TO64_ONE(case_sensitive, in[5]) << 40; + FALLTHROUGH; + case 5: + state->b |= U8TO64_ONE(case_sensitive, in[4]) << 32; + FALLTHROUGH; + case 4: + state->b |= U8TO64_ONE(case_sensitive, in[3]) << 24; + FALLTHROUGH; + case 3: + state->b |= U8TO64_ONE(case_sensitive, in[2]) << 16; + FALLTHROUGH; + case 2: + state->b |= U8TO64_ONE(case_sensitive, in[1]) << 8; + FALLTHROUGH; + case 1: + state->b |= U8TO64_ONE(case_sensitive, in[0]); + FALLTHROUGH; + case 0: + break; + default: + UNREACHABLE(); + } + + state->inlen += len; +} + +static inline void +isc_siphash13_finalize(isc_siphash13_t *restrict state, uint8_t *out) { + REQUIRE(out != NULL); + + uint64_t b = ((uint64_t)state->inlen) << 56 | state->b; + + isc_siphash13_one(state, b); + + state->v2 ^= 0xff; + + for (size_t i = 0; i < dROUNDS_13; ++i) { + SIPROUND(state->v0, state->v1, state->v2, state->v3); + } + + b = state->v0 ^ state->v1 ^ state->v2 ^ state->v3; + + ISC_U64TO8_LE(out, b); +} + +static inline void +isc_siphash13(const uint8_t *key, const uint8_t *in, const size_t inlen, + bool case_sensitive, uint8_t *out) { + isc_siphash13_t state; + isc_siphash13_init(&state, key); + isc_siphash13_hash(&state, in, inlen, case_sensitive); + isc_siphash13_finalize(&state, out); +} + static inline void isc_halfsiphash24_init(isc_halfsiphash24_t *restrict state, const uint8_t *k) { REQUIRE(k != NULL); @@ -309,7 +506,7 @@ static inline void isc_halfsiphash24_one(isc_halfsiphash24_t *restrict state, const uint32_t m) { state->v3 ^= m; - for (size_t i = 0; i < cROUNDS; ++i) { + for (size_t i = 0; i < cROUNDS_24; ++i) { HALFSIPROUND(state->v0, state->v1, state->v2, state->v3); } @@ -406,7 +603,7 @@ isc_halfsiphash24_finalize(isc_halfsiphash24_t *restrict state, uint8_t *out) { state->v2 ^= 0xff; - for (size_t i = 0; i < dROUNDS; ++i) { + for (size_t i = 0; i < dROUNDS_24; ++i) { HALFSIPROUND(state->v0, state->v1, state->v2, state->v3); } @@ -423,3 +620,147 @@ isc_halfsiphash24(const uint8_t *k, const uint8_t *in, const size_t inlen, isc_halfsiphash24_hash(&state, in, inlen, case_sensitive); isc_halfsiphash24_finalize(&state, out); } + +/* + * HalfSipHash-1-3: fewer rounds for hash tables. + */ +typedef isc_halfsiphash24_t isc_halfsiphash13_t; + +#define ISC_HALFSIPHASH13_KEY_LENGTH ISC_HALFSIPHASH24_KEY_LENGTH +#define ISC_HALFSIPHASH13_TAG_LENGTH ISC_HALFSIPHASH24_TAG_LENGTH + +static inline void +isc_halfsiphash13_init(isc_halfsiphash24_t *restrict state, const uint8_t *k) { + REQUIRE(k != NULL); + + uint32_t k0 = ISC_U8TO32_LE(k); + uint32_t k1 = ISC_U8TO32_LE(k + 4); + + *state = (isc_halfsiphash24_t){ + .k0 = k0, + .k1 = k1, + .v0 = UINT32_C(0x00000000) ^ k0, + .v1 = UINT32_C(0x00000000) ^ k1, + .v2 = UINT32_C(0x6c796765) ^ k0, + .v3 = UINT32_C(0x74656462) ^ k1, + }; +} + +static inline void +isc_halfsiphash13_one(isc_halfsiphash13_t *restrict state, const uint32_t m) { + state->v3 ^= m; + + for (size_t i = 0; i < cROUNDS_13; ++i) { + HALFSIPROUND(state->v0, state->v1, state->v2, state->v3); + } + + state->v0 ^= m; +} + +static inline void +isc_halfsiphash13_hash(isc_halfsiphash13_t *restrict state, const uint8_t *in, + const size_t inlen, const bool case_sensitive) { + REQUIRE(inlen == 0 || in != NULL); + + if (in == NULL || inlen == 0) { + return; + } + + size_t len = inlen; + const int right = state->inlen & 3; + + switch (right) { + case 0: + break; + case 1: + state->b |= U8TO32_ONE(case_sensitive, in[0]) << 8; + state->inlen++; + ++in; + + if (--len == 0) { + return; + } + FALLTHROUGH; + case 2: + state->b |= U8TO32_ONE(case_sensitive, in[0]) << 16; + state->inlen++; + ++in; + + if (--len == 0) { + return; + } + FALLTHROUGH; + case 3: + state->b |= U8TO32_ONE(case_sensitive, in[0]) << 24; + state->inlen++; + ++in; + + isc_halfsiphash13_one(state, state->b); + state->b = 0; /* consumed */ + + if (--len == 0) { + return; + } + break; + default: + UNREACHABLE(); + } + + const uint8_t *end = in + len - (len % sizeof(uint32_t)); + const int left = len & 3; + + for (; in != end; in += 4) { + uint32_t m = case_sensitive + ? ISC_U8TO32_LE(in) + : isc_ascii_tolower4(ISC_U8TO32_LE(in)); + + isc_halfsiphash13_one(state, m); + } + + INSIST(state->b == 0); + switch (left) { + case 3: + state->b |= U8TO32_ONE(case_sensitive, in[2]) << 16; + FALLTHROUGH; + case 2: + state->b |= U8TO32_ONE(case_sensitive, in[1]) << 8; + FALLTHROUGH; + case 1: + state->b |= U8TO32_ONE(case_sensitive, in[0]); + FALLTHROUGH; + case 0: + break; + default: + UNREACHABLE(); + } + + state->inlen += len; +} + +static inline void +isc_halfsiphash13_finalize(isc_halfsiphash13_t *restrict state, uint8_t *out) { + REQUIRE(out != NULL); + + uint32_t b = ((uint32_t)state->inlen) << 24 | state->b; + + isc_halfsiphash13_one(state, b); + + state->v2 ^= 0xff; + + for (size_t i = 0; i < dROUNDS_13; ++i) { + HALFSIPROUND(state->v0, state->v1, state->v2, state->v3); + } + + b = state->v1 ^ state->v3; + ISC_U32TO8_LE(out, b); +} + +static inline void +isc_halfsiphash13(const uint8_t *k, const uint8_t *in, const size_t inlen, + bool case_sensitive, uint8_t *out) { + isc_halfsiphash13_t state; + + isc_halfsiphash13_init(&state, k); + isc_halfsiphash13_hash(&state, in, inlen, case_sensitive); + isc_halfsiphash13_finalize(&state, out); +}