1-hash哈希介绍
hash函数
y
=
h
(
k
)
y=h(k)
y=h(k),把任意长度的输入
k
k
k通过散列算法
h
h
h变换成固定长度的输出
y
y
y,该输出就是散列值1。一种常见的hash函数是
y
=
H
(
k
)
=
(
a
⋅
k
+
b
)
m
o
d
m
y=H(k)=(a\cdot k+b) \mod m
y=H(k)=(a⋅k+b)modm,
m
m
m一般取素数。
设hash函数的定义域为
K
K
K,值域为
Y
Y
Y,一般来说,
∣
K
∣
>
∣
Y
∣
|K|>|Y|
∣K∣>∣Y∣,这样hash函数容易出现碰撞,如下图,
h
(
k
5
)
=
h
(
k
2
)
=
h
(
k
7
)
h(k_5)=h(k_2)=h(k_7)
h(k5)=h(k2)=h(k7),
k
5
,
k
2
,
k
7
k_5,k_2,k_7
k5,k2,k7在一条链上(碰撞):
对于hash函数,基本上都能找到一组输入,使得它们的hash值都相同,导致它们在一条链上,有时甚至会比线性查找的复杂度还要高,因为比线性查找多了hash的时间。
2-Universal hashing全域哈希法
思路:解决上述问题的一种方法就是随机。随机从一组hash函数(a family of hash functions)中选择一个。这样选的话,攻击者就没办法针对特定的hash函数构造一组输入,使得hash函数效率很低。
定义1: U \mathcal{U} U是定义域, H \mathcal{H} H是hash函数的集合,能够将 U \mathcal{U} U映射到 { 0 , 1 , . . . , m − 1 } \{0, 1, ..., m-1\} {0,1,...,m−1},即 h : U → { 0 , 1 , . . . , m − 1 } , h ∈ H h:\mathcal{U}\rightarrow\{0, 1, ..., m-1\}, h\in \mathcal{H} h:U→{0,1,...,m−1},h∈H.
定义2:如果 ∀ x , y \forall x, y ∀x,y满足 x ≠ y x\neq y x=y并且 ∣ { h ∈ H : h ( x ) = h ( y ) } ∣ = ∣ H ∣ m |\{h\in \mathcal{H}:h(x)=h(y)\}|=\frac{|\mathcal{H}|}{m} ∣{h∈H:h(x)=h(y)}∣=m∣H∣,则称 H \mathcal{H} H是全域(universal)的。
根据定义2,如果h是随机均匀地从
H
\mathcal{H}
H中选择(注意每个输入要重新选择一个hash函数), 那么
x
x
x和
y
y
y碰撞的概率是:
h
(
x
)
=
h
(
y
)
的
函
数
数
量
所
有
的
函
数
=
∣
H
∣
m
∣
H
∣
=
1
m
.
\frac{h(x)=h(y)的函数数量}{所有的函数} =\frac{\frac{|\mathcal{H}|}{m}}{|\mathcal{H}|}=\frac{1}{m}.
所有的函数h(x)=h(y)的函数数量=∣H∣m∣H∣=m1.
定理1:随机均匀地从
H
\mathcal{H}
H(
H
\mathcal{H}
H是全域的)选择
h
h
h,如果我们现在已经把
n
n
n个输入放入了hash表
T
T
T中了,则再给一个输入
x
x
x,有
E
[
h
a
s
h
表
T
中
元
素
和
x
碰
撞
的
数
量
]
<
n
m
,
E[hash表T中元素和x碰撞的数量]<\frac{n}{m},
E[hash表T中元素和x碰撞的数量]<mn,
其中
E
[
⋅
]
E[\cdot]
E[⋅]表示期望。
[定理1的重要性] 通过证明上述定理,我们就可以说,如果存在 H \mathcal{H} H是全域的,那么最终在hash表 T T T中元素的分布(在平均意义上)是均匀的。
定理1的证明. 设
C
x
C_{x}
Cx表示在hash表
T
T
T中的随机元素和
x
x
x碰撞的数量,设
C
x
y
=
{
1
i
f
h
(
x
)
=
h
(
y
)
0
i
f
h
(
x
)
≠
h
(
y
)
C_{xy}=\left\{\begin{array}{cr} 1 & if\ h(x)=h(y) \\ 0 & if\ h(x)\neq h(y) \end{array}\right.
Cxy={10if h(x)=h(y)if h(x)=h(y)
那么,
E
[
C
x
]
=
E
[
∑
y
∈
T
−
x
C
x
y
]
=
∑
y
∈
T
−
x
E
[
C
x
y
]
因
为
期
望
的
线
性
性
质
=
∑
y
∈
T
−
x
1
m
=
(
n
−
1
)
1
m
<
n
m
.
\begin{array}{lll} E[C_x]&=E[\sum_{y\in T-x}C_{xy}] \\ &=\sum_{y\in T-x}E[C_{xy}] & 因为期望的线性性质\\ &=\sum_{y\in T-x}\frac{1}{m} \\ &=(n-1)\frac{1}{m} \\ &<\frac{n}{m}. \end{array}
E[Cx]=E[∑y∈T−xCxy]=∑y∈T−xE[Cxy]=∑y∈T−xm1=(n−1)m1<mn.因为期望的线性性质
例子 :如果 n = 1 , m = 2 n=1,m=2 n=1,m=2,则 E [ C x ] < 1 2 . E[C_x]<\frac{1}{2}. E[Cx]<21.
3-构造一个全域哈希 H \mathcal{H} H
定理2: 按照如下四个步骤构造的 H \mathcal{H} H是全域的:
- (条件)令 m m m等于一个素数;
- (初始准备)将输入 k k k写成 r + 1 r+1 r+1个数字: k = < k 0 , k 1 , . . . , k r > k=<k_0,k_1,...,k_r> k=<k0,k1,...,kr>,其中 k i ∈ { 0 , 1 , . . . , m − 1 } k_i\in\{0, 1, ..., m-1\} ki∈{0,1,...,m−1}(等价于将 k k k用 m m m进制表示);
- (随机)随机选择一个 a = < a 0 , a 1 , . . . , a r > a=<a_0, a_1,...,a_r> a=<a0,a1,...,ar>,其中 a i ∈ 0 , 1 , . . . , m − 1 a_i\in{0, 1,..., m-1} ai∈0,1,...,m−1;
- (hash函数) h a ( k ) = ( ∑ i = 0 i = r a i × k i ) m o d m h_a(k)=(\sum_{i=0}^{i=r}a_i\times k_i) \mod m ha(k)=(∑i=0i=rai×ki)modm.
证明见2。
4-python实现
自己写的代码,如有错误望指正。代码链接:https://github.com/VFVrPQ/LDP/blob/master/Components/UniversalHashing.py,另有完整代码如下:
import math
import random
class UniversalHashing:
'''
g: a prime
d: domain, [0, 1, ..., d-1]
len: The maximum number of digits in g Base
v: an input value in [0, 1, ..., d-1]
hash function: H_a(k) = (a(0)*k(0)+a(1)*k(1)+...+a(len-1)*k(len-1)) % g
'''
def __init__(self, g, d):
self.__g = g
assert g>=2, 'g is less than 2'
assert self.__isPrime(g), 'g is not a prime'
self.__d = d
self.__len = math.ceil( math.log(d) / math.log(g)) # g进制下,最大的位数
self.__a = self.__len*[0] # initial length
# v is an input value in [0, 1, ..., d-1]
def hash(self, v):
self.__randomness() # regenerate a, select H
out = self.calc(self.__a, v)
return self.__a, out
# calc H_a(k) = (a(0)*k(0)+a(1)*k(1)+...+a(len-1)*k(len-1)) % g
def calc(self, a, v):
assert len(a)==self.__len, 'len(a)!=self.__len'
k = self.__toBitList(v)
out = 0
for i in range(self.__len):
out = (out + a[i]*k[i]) % self.__g
return out
def __randomness(self):
# generate a
for i in range(self.__len):
self.__a[i] = random.randint(0, self.__g-1)
def __toBitList(self, v):
assert v>=0, 'v<0'
if v == 0:
return self.__len * [0]
bitList = self.__len * [0]
for i in range(self.__len):
bitList[i] = v%self.__g
v = int(v/self.__g)
return bitList
def __isPrime(self, v):
if v<=1:
return False
for i in range(2, int(math.sqrt(v))+1, 1):
if v%i==0:
return False
return True
# for test
if __name__ == "__main__":
TIMES = 10
g = 29 # prime
d = 16 # domain
uhash = UniversalHashing(g, d)
H = g * [0]
for i in range(TIMES): # random TIMES to verify
x = random.randint(0, d-1)
_, out = uhash.hash(x)
H[out] += 1
for i in range(g):
print(i, H[i])