Documentation / translations / zh_CN / security / siphash.rst


Based on kernel version 6.14. Page generated on 2025-04-02 08:20 EST.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/security/siphash.rst

:翻译:

 张巍 zhangwei <zhangwei@cqsoftware.com.cn>

=====================================
SipHash - 一种短输入伪随机函数(PRF)
=====================================

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

SipHash是一种加密安全的伪随机函数,即一种用于生成伪随机密钥的哈
希函数,因为其在处理短输入时表现出色,因此得名。其由密码学家
Daniel J. Bernstein和Jean-Philippe Aumasson设计。目的主要是替
代其他哈希函数,例如:jhash,md5_transform,sha1_transform等。

SipHash采用一个完全由随机数生成的密钥,以及一个输入缓冲区或者
多个输入整数,它输出一个与随机数难以区分的整数,你可以将它作
为安全序列、安全cookies的一部分,或者对其进行掩码处理,以便在
哈希表中使用。

生成密钥
========

密钥应来源于加密安全的随机数生成,要么使用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);

如果向一个通用的hsiphash函数传递一个恒定长度的常量,他将
在编译的时候将常量折叠,并自动选择一个优化后的函数。

哈希表键函数的用法::

        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”变体

Struct-passing陷阱
==================

通常情况下,XuY函数的输出长度不够大,因此你可能需要传递一个预填充
的结构体给SipHash,在这样做时,务必确保结构体没有填充空隙,最简单
的方法就是将结构体的成员按照大小降序的方式排序,并且使用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" 系列函数提供的。

.. warning::
   绝对不要在作为哈希表键函数之外使用hsiphash函数,只有在你
   能完全能确定输出永远不会从内核传输出去的情况下才能使用,
   作为缓解哈希表泛洪拒绝服务攻击的一种手段,它仅在某些情况
   下比jhash好用。

在64位的内核中,hsiphash函数实际上实现的是SipHash-1-3,这是一
种减少轮数的SipHash变形,而不是HalfSipHash-1-3。这是因为在64位
代码中SipHash-1-3的性能与HalfSipHash-1-3相当,甚至可能更快,请
注意,这并不意味这在64位的内核中,hsihpash函数与siphash函数相
同,也不意味着他们是安全的;hsihash函数仍然使用一种不太安全的
减少轮数的算法,并将输出截断为32位。

生成哈希密钥
============

密钥应始终来源于加密安全的随机数生成,要么使用get random bytes
要么使用get random once::

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

如果你的钥匙来源不是这两个,那么你的做法是错的。

使用哈希函数
============

这个函数有两种变体,一个是接受整数列表,另一种是接受缓冲区::

        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()慢三倍,这是因为有许多替换,不过这些都不是问题,
因为哈希表查找不是瓶颈。而且,这些牺牲是为了hsiphash()的安全性和DoS抗
性,这是值得的。