SipHash - 短輸入 PRF

作者:

作者:Jason A. Donenfeld <jason@zx2c4.com>

SipHash 是一種密碼學安全的 PRF(偽隨機函式)—— 也就是一個帶金鑰的雜湊函式 —— 它對短輸入表現非常好,因此得名。它由密碼學家 Daniel J. Bernstein 和 Jean-Philippe Aumasson 設計。它旨在替代 jhashmd5_transformsha1_transform 等函式的某些用途。

SipHash 接受一個填充了隨機生成數字的金鑰,以及一個輸入緩衝區或幾個輸入整數。它會輸出一個與隨機數無法區分的整數。然後,您可以將該整數用作安全序列號、安全 cookie 的一部分,或將其掩碼化用於雜湊表。

生成金鑰

金鑰應始終從密碼學安全的隨機數源生成,可使用 get_random_bytes 或 get_random_once。

siphash_key_t key;
get_random_bytes(&key, sizeof(key));

如果您不從這裡派生金鑰,那麼您的做法是錯誤的。

使用函式

該函式有兩個變體,一個接受整數列表,另一個接受緩衝區

u64 siphash(const void *data, size_t len, const siphash_key_t *key);

以及

u64 siphash_1u64(u64, const siphash_key_t *key);
u64 siphash_2u64(u64, u64, const siphash_key_t *key);
u64 siphash_3u64(u64, u64, u64, const siphash_key_t *key);
u64 siphash_4u64(u64, u64, u64, u64, const siphash_key_t *key);
u64 siphash_1u32(u32, const siphash_key_t *key);
u64 siphash_2u32(u32, u32, const siphash_key_t *key);
u64 siphash_3u32(u32, u32, u32, const siphash_key_t *key);
u64 siphash_4u32(u32, u32, u32, u32, const siphash_key_t *key);

如果您向通用 SipHash 函式傳遞固定長度的引數,它將在編譯時進行常量摺疊,並自動選擇其中一個最佳化函式。

雜湊表金鑰函式用法

struct some_hashtable {
        DECLARE_HASHTABLE(hashtable, 8);
        siphash_key_t key;
};

void init_hashtable(struct some_hashtable *table)
{
        get_random_bytes(&table->key, sizeof(table->key));
}

static inline hlist_head *some_hashtable_bucket(struct some_hashtable *table, struct interesting_input *input)
{
        return &table->hashtable[siphash(input, sizeof(*input), &table->key) & (HASH_SIZE(table->hashtable) - 1)];
}

然後,您可以像往常一樣遍歷返回的雜湊桶。

安全性

SipHash 擁有非常高的安全裕度,因為它使用 128 位金鑰。只要金鑰保密,攻擊者就不可能猜測函式的輸出,即使能夠觀察到許多輸出,因為 2^128 個輸出是一個巨大的數量。

Linux 實現了 SipHash 的“2-4”變體。

結構體傳遞陷阱

通常,XuY 函式的容量不夠大,此時您會希望將一個預填充的結構體傳遞給 SipHash。在這樣做時,務必確保結構體沒有填充空洞(padding holes)。最簡單的方法是按大小降序排列結構體的成員,並使用 offsetofend() 而不是 sizeof() 來獲取大小。出於效能考慮,如果可能的話,最好將結構體對齊到正確的邊界。以下是一個示例

const struct {
        struct in6_addr saddr;
        u32 counter;
        u16 dport;
} __aligned(SIPHASH_ALIGNMENT) combined = {
        .saddr = *(struct in6_addr *)saddr,
        .counter = counter,
        .dport = dport
};
u64 h = siphash(&combined, offsetofend(typeof(combined), dport), &secret);

資源

如果您有興趣瞭解更多,請閱讀 SipHash 論文:https://131002.net/siphash/siphash.pdf


HalfSipHash - SipHash 不安全的表親

作者:

作者:Jason A. Donenfeld <jason@zx2c4.com>

萬一 SipHash 的速度不足以滿足您的需求,您或許可以考慮使用 HalfSipHash,這是一個可怕但可能很有用的選擇。HalfSipHash 將 SipHash 的輪數從“2-4”減少到“1-3”,更可怕的是,它使用一個易於暴力破解的 64 位金鑰(具有 32 位輸出),而不是 SipHash 的 128 位金鑰。然而,這可能會吸引一些追求高效能的 jhash 使用者。

HalfSipHash 支援透過“hsiphash”系列函式提供。

警告

切勿使用 hsiphash 函式,除非將其用作雜湊表金鑰函式,並且只有在您絕對確定輸出永遠不會從核心中傳出時才可使用。它相較於 jhash 僅在減輕雜湊表泛洪拒絕服務攻擊方面有微不足道的用處。

在 64 位核心上,hsiphash 函式實際上實現的是 SipHash-1-3(SipHash 的一個精簡輪變體),而不是 HalfSipHash-1-3。這是因為在 64 位程式碼中,SipHash-1-3 的速度不比 HalfSipHash-1-3 慢,甚至可能更快。請注意,這並意味著在 64 位核心中 hsiphash 函式與 siphash 函式相同,或者它們是安全的;hsiphash 函式仍然使用安全性較低的精簡輪演算法,並將其輸出截斷為 32 位。

生成 hsiphash 金鑰

金鑰應始終從密碼學安全的隨機數源生成,可使用 get_random_bytes 或 get_random_once。

hsiphash_key_t key;
get_random_bytes(&key, sizeof(key));

如果您不從這裡派生金鑰,那麼您的做法是錯誤的。

使用 hsiphash 函式

該函式有兩個變體,一個接受整數列表,另一個接受緩衝區

u32 hsiphash(const void *data, size_t len, const hsiphash_key_t *key);

以及

u32 hsiphash_1u32(u32, const hsiphash_key_t *key);
u32 hsiphash_2u32(u32, u32, const hsiphash_key_t *key);
u32 hsiphash_3u32(u32, u32, u32, const hsiphash_key_t *key);
u32 hsiphash_4u32(u32, u32, u32, u32, const hsiphash_key_t *key);

如果您向通用 hsiphash 函式傳遞固定長度的引數,它將在編譯時進行常量摺疊,並自動選擇其中一個最佳化函式。

雜湊表金鑰函式用法

struct some_hashtable {
        DECLARE_HASHTABLE(hashtable, 8);
        hsiphash_key_t key;
};

void init_hashtable(struct some_hashtable *table)
{
        get_random_bytes(&table->key, sizeof(table->key));
}

static inline hlist_head *some_hashtable_bucket(struct some_hashtable *table, struct interesting_input *input)
{
        return &table->hashtable[hsiphash(input, sizeof(*input), &table->key) & (HASH_SIZE(table->hashtable) - 1)];
}

然後,您可以像往常一樣遍歷返回的雜湊桶。

效能

hsiphash() 的速度大約比 jhash() 慢 3 倍。對於許多替換場景來說,這不是問題,因為雜湊表查詢並非瓶頸。總的來說,為了 hsiphash() 的安全性和抗 DoS 能力,這可能是一個值得的犧牲。