]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Use SipHash-1-3 for hash tables, keep SipHash-2-4 for cookies 11787/head
authorOndřej Surý <ondrej@sury.org>
Thu, 2 Apr 2026 06:52:14 +0000 (08:52 +0200)
committerOndřej Surý <ondrej@isc.org>
Fri, 15 May 2026 06:15:59 +0000 (08:15 +0200)
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.

lib/isc/hash.c
lib/isc/include/isc/hash.h
lib/isc/include/isc/siphash.h

index 49b942ef5bc6db7e9841d64f78f1e35c504f0c0a..8d60173649051d022c5f6048d1f75053feb70c51 100644 (file)
@@ -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;
 }
index 676df77de8474be1bbc7a3b3831a7b333fc07bba..75584e25bd0bb059862666d2bde5b0a3c234d66d 100644 (file)
@@ -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
index e4df6aaf8c7c74c31a0f7701a2d331d9c1dd97b1..e9f9b8c14ed89b3a0544fbb43bd772dc00adbdb7 100644 (file)
 #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);
+}