怎么卡飞哈希
哈希
我们定义一个把字符串映射到整数的函数 \(f\),这个 \(f\) 称为是 Hash 函数,我们希望这个函数 \(f\) 可以方便地帮我们判断两个字符串是否相等,这就是哈希。
一般来说,哈希值都是使用 \(hash_i=(base\cdot hash_{i-1} +s_i)\%mod\) 这个转移方程得到的。
在这个转移方程中,\(s\) 为字符串,\(base\) 被称为底数,而 \(mod\) 被称为模数。
生日悖论
如果一个班级有 \(23\) 个人, 那么其中有两个人生日相同的概率超过 \(50\%\)。因为这与自己的直觉相不符,所以被称为生日悖论。
其实出现这样问题的原因是并没有将“两个人的生日相同”和“有人和自己的生日相同”很好的区分开。
定义两人生日重复的情况叫同生缘。
假设一年有 \(N\) 天,那么不发生同生缘的概率可以写作:
当 \(n\le N\) 时,因为 \((1-\frac{1}{N})^n\approx 1-\frac{n}{N}\),所以
当 \(N=365\) 时,函数图像可点击此链接查看。
可以观察到,在 \(n=23\) 时 \(P\) 已经接近了 \(50\%\)。
卡大质数哈希
假设模数为 \(10^9+7\),构造一个长度为 \(10^5\) 的字符串。
将这个数据带入上方函数,得到成功让字符串哈希冲突的概率为 \(P=0.993261715159\)。
所以直接使用随机构造的方式,在一般情况下就可以将其 hack。
#include<bits/stdc++.h>
using namespace std;
int main(){
printf("100000 20\n");
for(int i = 1;i <= 100000;i++){
putchar(rand() % 26 + 'a');
}
return 0;
}
自然溢出
自然溢出,即 hash 数组使用 unsigned long long
,也就是对于 \(2^{64}\) 取模。不光其值域不难出现哈希冲突,而且代码长度与常数都会大大减小,得到了不少的同学的青睐。
将 \(N=2^{64},n=10^5\) 带入上面的函数后,我们发现出现哈希冲突的可能性 \(P\) 无限接近与 \(0\),所以使用生日攻击成功的可能性极小。
底数为偶数
可以构造全部为 \(\texttt{a}\) 的子串和第一个为 \(\texttt{b}\) 其余均为 \(\texttt{a}\) 的两个长度相等且长苏大于 \(64\) 的两个不一样的字符串。
因为底数的 \(64\) 次方以上模 \(2^{64}\) 都是 \(0\),所以即是两个字符串不同,他们的哈希值也都会一样。
底数为奇数
设一些串 \(s\),\(s_i\) 表示第 \(i\) 个串,\(s_i\) 的哈希值为 \(hash(s_i)\)。
定义 \(f(s)\) 为字符串 \(s\) 内全部的 \(\texttt{a}\) 都变为 \(\texttt{b}\),所有的 \(\texttt{b}\) 都变成 \(\texttt{a}\)。
定义 \(s_i+s_j\) 的意思为将 \(s_j\) 添加到 \(s_i\) 的末尾形成的新的字符串。
构造方法为:\(s_1=\texttt{a}\),\(s_i=s_{i-1}+f(s_{i-1})\),所以 \(|s_i|=2^{i-1}\)。
所以:
因为希望产生哈希冲突,即 \(2^{64}\mid hash(s_i)-hash(f(s_i))\)。
设 \(g_i\) 表示 \(hash(s_i)-hash(f(s_i))\),那么 \(g\) 满足一下性质:
因为每一个 \(base^{2^{i-1}}-1\) 都是偶数,所以是的 \(g\) 到达第 \(64\) 项就可以 hack 了。
因为 \(base^{2^{i-1}}-1=(base^{2^{i-2}}-1)\cdot(base^{2^{i-2}}+1)\) 且为一个偶数乘一个偶数, 而左边的可以继续递归下去, 所以到第 \(12\) 位其实就可以 hack 了。
#include <iostream>
#include <cstring>
using namespace std;
char s[10000];
int main(){
cout<<(1<<12)+65<<' '<<(1<<11)<<'\n';
int now=1;
s[1]='a';
for (int i=1;i<=12;i++){
for (int j=1;j<=now;j++) s[now+j]=s[j]=='a'?'b':'a';
now<<=1;
}
for (int i=1;i<=now;i++) printf("%c",s[i]);
for (int i=1;i<=65;i++) putchar('a');
return 0;
}
参考文章 博客园