常见的hash算法及其原理
Hash,⼀般翻译做“散列”,也有直接⾳译为“哈希”的,就是把任意长度的输⼊(⼜叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是⼀种压缩映射,也就是,散列值的空间通常远⼩于输⼊的空间,不同的输⼊可能会散列成相同的输出,⽽不可能从散列值来唯⼀的确定输⼊值。简单的说就是⼀种将任意长度的消息压缩到某⼀固定长度的消息摘要的函数。
哈希表是根据设定的哈希函数H(key)和处理冲突⽅法将⼀组关键字映射到⼀个有限的地址区间上,并以关键字在地址区间中的象作为记录在表中的存储位置,这种表称为哈希表或散列,所得存储位置称为哈希地址或散列地址。作为线性数据结构与表格和队列等相⽐,哈希表⽆疑是查速度⽐较快的⼀种。
通过将单向数学函数(有时称为“哈希算法”)应⽤到任意数量的数据所得到的固定⼤⼩的结果。如果输⼊数据中有变化,则哈希也会发⽣变化。哈希可⽤于许多操作,包括⾝份验证和数字签名。也称为“消息摘要”。
简单解释:哈希(Hash)算法,即散列函数。它是⼀种单向密码体制,即它是⼀个从明⽂到密⽂的不可逆的映射,只有加密过程,没有解密过程。同时,哈希函数可以将任意长度的输⼊经过变化以后得到固定长度的输出。哈希函数的这种单向特征和输出数据长度固定的特征使得它可以⽣成消息或者数据。
常⽤hash算法的介绍:
(1)MD4
MD4(RFC 1320)是 MIT 的Ronald L. Rivest在 1990 年设计的,MD 是 Message Digest(消息摘要) 的缩写。它适⽤在32位字长的处理器上⽤⾼速软件实现——它是基于 32位操作数的位操作来实现的。
(2)MD5
MD5(RFC 1321)是 Rivest 于1991年对MD4的改进版本。它对输⼊仍以512位分组,其输出是4个32位字的级联,与 MD4 相同。MD5⽐MD4来得复杂,并且速度较之要慢⼀点,但更安全,在抗分析和抗差分⽅⾯表现更好。
(3)SHA-1及其他
SHA1是由NIST NSA设计为同DSA⼀起使⽤的,它对长度⼩于264的输⼊,产⽣长度为160bit的散列值,因此抗穷举(brute-force)性更好。SHA-1 设计时基于和MD4相同原理,并且模仿了该算法。
常见hash算法的原理
散列表,它是基于快速存取的⾓度设计的,也是⼀种典型的“空间换时间”的做法。顾名思义,该数据结构可以理解为⼀个线性表,但是其中的元素不是紧密排列的,⽽是可能存在空隙。
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)⽽直接进⾏访问的数据结构。也就是说,它通过把关键码值映射到表中⼀个位置来访问记录,以加快查的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
⽐如我们存储70个元素,但我们可能为这70个元素申请了100个元素的空间。70/100=0.7,这个数字称为负载因⼦。我们之所以这样做,也是为了“快速存取”的⽬的。我们基于⼀种结果尽可能随机平均分
布的固定函数H为每个元素安排存储位置,这样就可以避免遍历性质的线性搜索,以达到快速存取。但是由于此随机性,也必然导致⼀个问题就是冲突。所谓冲突,即两个元素通过散列函数H得到的地址相同,那么这两个元素称为“同义词”。这类似于70个⼈去⼀个有100个椅⼦的饭店吃饭。散列函数的计算结果是⼀个存储单位地址,每个存储单位称为“桶”。设⼀个散列表有m个桶,则散列函数的值域应为[0,m-1]。
解决冲突是⼀个复杂问题。
冲突主要取决于:
(1)散列函数,⼀个好的散列函数的值应尽可能平均分布。
(2)处理冲突⽅法。
(3)负载因⼦的⼤⼩。太⼤不⼀定就好,⽽且浪费空间严重,负载因⼦和散列函数是联动的。
解决冲突的办法:
(1)线性探查法:冲突后,线性向前试探,到最近的⼀个空位置。缺点是会出现堆积现象。存取时,可能不是同义词的词也位于探查序列,影响效率。
(2)双散列函数法:在位置d冲突后,再次使⽤另⼀个散列函数产⽣⼀个与散列表桶容量m互质的数c,依次试探(d+n*c)%m,使探查序列跳跃式分布。
常⽤的构造散列函数的⽅法
散列函数能使对⼀个数据序列的访问过程更加迅速有效,通过散列函数,数据元素将被更快地定位:
1. 直接寻址法:取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a?key + b,其中a和b为常数(这种散列函数叫做⾃⾝函数)
2. 数字分析法:分析⼀组数据,⽐如⼀组员⼯的出⽣年⽉⽇,这时我们发现出⽣年⽉⽇的前⼏位数字⼤体相同,这样的话,出现冲突的⼏率就会很⼤,但是我们发现年⽉⽇的后⼏位表⽰⽉份和具体⽇期的数字差别很⼤,如果⽤后⾯的数字来构成散列地址,则冲突的⼏率会明显降低。因此数字分析法就是出数字的规律,尽可能利⽤这些数据来构造冲突⼏率较低的散列地址。
3. 平⽅取中法:取关键字平⽅后的中间⼏位作为散列地址。
4. 折叠法:将关键字分割成位数相同的⼏部分,最后⼀部分位数可以不同,然后取这⼏部分的叠加和(去除进位)作为散列地址。
5. 随机数法:选择⼀随机函数,取关键字的随机值作为散列地址,通常⽤于关键字长度不同的场合。
6. 除留余数法:取关键字被某个不⼤于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p, p《=m。不仅可以对关键字直接取模,也可在折叠、平⽅取中等运算之后取模。对p的选择很重要,⼀般取素数或m,若p选的不好,容易产⽣同义词。
查的性能分析
散列表的查过程基本上和造表过程相同。⼀些关键码可通过散列函数转换的地址直接到,另⼀些关键码在散列函数得到的地址上产⽣了冲突,需要按处理冲突的⽅法进⾏查。在介绍的三种处理冲突的⽅法中,产⽣冲突后的查仍然是给定值与关键码进⾏⽐较的过程。所以,对散列表查效率的量度,依然⽤平均查长度来衡量。
查过程中,关键码的⽐较次数,取决于产⽣冲突的多少,产⽣的冲突少,查效率就⾼,产⽣的冲突多,查效率就低。因此,影响产⽣冲突多少的因素,也就是影响查效率的因素。影响产⽣冲突多少有以下三个因素:
1. 散列函数是否均匀;
2. 处理冲突的⽅法;
3. 散列表的装填因⼦。
散列表的装填因⼦定义为:α= 填⼊表中的元素个数 / 散列表的长度
α是散列表装满程度的标志因⼦。由于表长是定值,α与“填⼊表中的元素个数”成正⽐,所以,α越⼤,填⼊表中的元素较多,产⽣冲突的可能性就越⼤;α越⼩,填⼊表中的元素较少,产⽣冲突的可能性就越⼩。
实际上,散列表的平均查长度是装填因⼦α的函数,只是不同处理冲突的⽅法有不同的函数。
了解了hash基本定义,就不能不提到⼀些著名的hash算法,MD5 和 SHA-1 可以说是⽬前应⽤最⼴泛的Hash算法,⽽它们都是以MD4 为基础设计的。那么他们都是什么意思呢?
这⾥简单说⼀下:
(1) MD4
MD4(RFC 1320)是 MIT 的 Ronald L. Rivest 在 1990 年设计的,MD 是 Message Digest 的缩写。它适⽤在32位字长的处理器上⽤⾼速软件实现--它是基于 32 位操作数的位操作来实现的。
(2) MD5
MD5(RFC 1321)是 Rivest 于1991年对MD4的改进版本。它对输⼊仍以512位分组,其输出是4个32位字的级联,与 MD4 相同。MD5⽐MD4来得复杂,并且速度较之要慢⼀点,但更安全,在抗分析和抗差分⽅⾯表现更好
(3) SHA-1 及其他
SHA1是由NIST NSA设计为同DSA⼀起使⽤的,它对长度⼩于264的输⼊,产⽣长度为160bit的散列值,因此抗穷举(brute-force)性更好。SHA-1 设计时基于和MD4相同原理,并且模仿了该算法。
哈希表不可避免冲突(collision)现象:对不同的关键字可能得到同⼀哈希地址 即key1≠key2,⽽hash(key1)=hash(key2)。因此,在建造哈希表时不仅要设定⼀个好的哈希函数,⽽且要设定⼀种处理冲突的⽅法。可如下描述哈希表:根据设定的哈希函数
H(key)和所选中的处理冲突的⽅法,将⼀组关键字映象到⼀个有限的、地址连续的地址集(区间)上并以关键字在地址集中的“象”作为相应记录在表中的存储位置,这种表被称为哈希表。
对于动态查表⽽⾔,1) 表长不确定;2)在设计查表时,只知道关键字所属范围,⽽不知道确切的关键字。因此,⼀般情况需建⽴⼀个函数关系,以f(key)作为关键字为key的录在表中的位置,通常称这个函数f(key)为哈希函数。(注意:这个函数并不⼀定是数学函数)
哈希函数是⼀个映象,即:将关键字的集合映射到某个地址集合上,它的设置很灵活,只要这个地址集合的⼤⼩不超出允许范围即可。
现实中哈希函数是需要构造的,并且构造的好才能使⽤的好。
那么这些Hash算法到底有什么⽤呢?
Hash算法在信息安全⽅⾯的应⽤主要体现在以下的3个⽅⾯:
(1) ⽂件校验
我们⽐较熟悉的校验算法有奇偶校验和CRC校验,这2种校验并没有抗数据篡改的能⼒,它们⼀定程度上能检测并纠正数据传输中的信道误码,但却不能防⽌对数据的恶意破坏。
MD5 Hash算法的“数字指纹”特性,使它成为⽬前应⽤最⼴泛的⼀种⽂件完整性校验和(Checksum)算法,不少Unix系统有提供计算md5 checksum的命令。
(2) 数字签名
Hash 算法也是现代密码体系中的⼀个重要组成部分。由于⾮对称算法的运算速度较慢,所以在数字签名协议中,单向散列函数扮演了⼀个重要的⾓⾊。 对 Hash 值,⼜称“数字摘要”进⾏数字签名,在统计上可以认为与对⽂件本⾝进⾏数字签名是等效的。⽽且这样的协议还有其他的优点。
(3) 鉴权协议
如下的鉴权协议⼜被称作挑战--认证模式:在传输信道是可被侦听,但不可被篡改的情况下,这是⼀种简单⽽安全的⽅法。
⽂件hash值
MD5-Hash-⽂件的数字⽂摘通过Hash函数计算得到。不管⽂件长度如何,它的Hash函数计算结果是⼀
个固定长度的数字。与加密算法不同,这⼀个Hash算法是⼀个不可逆的单向函数。采⽤安全性⾼的Hash算法,如MD5、SHA时,两个不同的⽂件⼏乎不可能得到相同的Hash结果。因此,⼀旦⽂件被修改,就可检测出来。
Hash函数还有另外的含义。实际中的Hash函数是指把⼀个⼤范围映射到⼀个⼩范围。把⼤范围映射到⼀个⼩范围的⽬的往往是为了节省空间,使得数据容易保存。除此以外,Hash函数往往应⽤于查上。所以,在考虑使⽤Hash函数之前,需要明⽩它的⼏个限制:
1. Hash的主要原理就是把⼤范围映射到⼩范围;所以,你输⼊的实际值的个数必须和⼩范围相当或者⽐它更⼩。不然冲突就会很多。
2. 由于Hash逼近单向函数;所以,你可以⽤它来对数据进⾏加密。
3. 不同的应⽤对Hash函数有着不同的要求;⽐如,⽤于加密的Hash函数主要考虑它和单项函数的差距,⽽⽤于查的Hash函数主要考虑它映射到⼩范围的冲突率。
应⽤于加密的Hash函数已经探讨过太多了,在作者的博客⾥⾯有更详细的介绍。所以,本⽂只探讨⽤于查的Hash函数。
Hash函数应⽤的主要对象是数组(⽐如,字符串),⽽其⽬标⼀般是⼀个int类型。以下我们都按照这
种⽅式来说明。
⼀般的说,Hash函数可以简单的划分为如下⼏类:
1. 加法Hash;
2. 位运算Hash;
3. 乘法Hash;
4. 除法Hash;
5. 查表Hash;
6. 混合Hash;
下⾯详细的介绍以上各种⽅式在实际中的运⽤。
⼀ 加法Hash
所谓的加法Hash就是把输⼊元素⼀个⼀个的加起来构成最后的结果。标准的加法Hash的构造如下:
static int additiveHash(String key, int prime)
{
int hash, i;
for (hash = key.length(), i = 0; i 《 key.length(); i++)
hash += key.charAt(i);
return (hash % prime);
}
这⾥的prime是任意的质数,看得出,结果的值域为[0,prime-1]。
⼆ 位运算Hash
这类型Hash函数通过利⽤各种位运算(常见的是移位和异或)来充分的混合输⼊元素。⽐如,标准的旋转Hash的构造如下:
static int rotatingHash(String key, int prime)
{
int hash, i;
for (hash=key.length(), i=0; i
hash = (hash《《4》》28)^key.charAt(i);
return (hash % prime);
}
先移位,然后再进⾏各种位运算是这种类型Hash函数的主要特点。⽐如,以上的那段计算hash的代码还可以有如下⼏种变形:
hash = (hash《《5》》27)^key.charAt(i);
hash += key.charAt(i);
hash += (hash 《《 10);
hash ^= (hash 》》 6);
if((i&1) == 0)
{
hash ^= (hash《《7》》3);
}
else
{
hash ^= ~((hash《《11》》5));
}
hash += (hash《《5》
hash = key.charAt(i) + (hash《《6》》16) ? hash;
hash ^= ((hash《《5》》2));
三 乘法Hash
这种类型的Hash函数利⽤了乘法的不相关性(乘法的这种性质,最有名的莫过于平⽅取头尾的随机数⽣成算法,虽然这种算法效果并不好)。⽐如,
static int bernstein(String key)
{
int hash = 0;
int i;
for (i=0; i
return hash;
}
jdk5.0⾥⾯的String类的hashCode()⽅法也使⽤乘法Hash。不过,它使⽤的乘数是31。推荐的乘数还有:131, 1313,13131, 131313等等。
value函数什么意思 使⽤这种⽅式的著名Hash函数还有:
// 32位FNV算法
int M_SHIFT = 0;
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论