Hash,就是把任意长度的输入,通过散列算法,变换成固定长度的输出,该输出就是散列值。
这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能
会散列成相同的输出,而不可能从散列值来唯一的确定输入值。数学表述为:h = H(M) ,
其中H( )--单向散列函数,M--任意长度明文,h--固定长度散列值。
在信息安全领域中应用的Hash算法,还需要满足其他关键特性:
第一当然是单向性(one-way),从预映射,能够简单迅速的得到散列值,而在计算上不可能构造一个预映
射,使其散列结果等于某个特定的散列值,即构造相应的M=H-1(h)不可行。这样,散列值就能在统计上唯一的
表征输入值,因此,密码学上的 Hash 又被称为"消息摘要(messagedigest)",就是要求能方便的将"消息"进
行"摘要",但在"摘要"中无法得到比"摘要"本身更多的关于"消息"的信息。
第二是抗冲突性(collision-resistant),即在统计上无法产生2个散列值相同的预映射。给定M,计算上
无法找到M',满足H(M)=H(M') ,此谓弱抗冲突性;计算上也难以寻找一对任意的M和M',使满足H(M)=H(M')
,此谓强抗冲突性。要求"强抗冲突性"主要是为了防范 所谓"生日攻击(birthdayattack)",在一个10人的团
体中,你能找到和你生日相同的人的概率是2.4%,而在同一团体中,有2人生日相同的概率是11.7%。类似的,
当预映射的空间很大的情况下,算法必须有足够的强度来保证不能轻易找到"相同生日"的人。
第三是映射分布均匀性和差分分布均匀性,散列结果中,为 0 的 bit 和为 1 的 bit ,其总数应该大致
相等;输入中一个 bit的变化,散列结果中将有一半以上的 bit 改变,这又叫做"雪崩效应(avalanche effect)";
要实现使散列结果中出现 1bit的变化,则输入中至少有一半以上的 bit 必须发生变化。其实质是必须使输入
中每一个 bit 的信息, 尽量均匀的反映到输出的每一个 bit上去;输出中的每一个 bit,都是输入中尽可能
多 bit 的信息一起作用的结果。Damgard 和 Merkle定义了所谓“压缩函数(compression function)”,就是
将一个固定长度输入,变换成较短的固定长度的输出,这对密码学实践上Hash函数的设计产生了很大的影响。
Hash函数就是被设计为基于通过特定压缩函数的不断重复“压缩”输入的分组和前一次压缩处理的结果的过程,
直到整个消息都被压缩完毕,最后的输出作为整个消息的散列值。尽管还缺乏严格的证明,但绝大多数业界的
研究者都同意,如果压缩函数是安全的,那么以上述形式散列任意长度的消息也将是安全的。任意长度的消息
被分拆成符合压缩函数输入要求的分组,最后一个分组可能需要在末尾添上特定的填充字节,这些分组将被顺
序处理,除了第一个消息分组将与散列初始化值一起作为压缩函数的输入外,当前分组将和前一个分组的压缩
函数输出一起被作为这一次压缩的输入,而其输出又将被作为下一个分组压缩函数输入的一部分,直到最后一
个压缩函数的输出,将被作为整个消息散列的结果。MD5 和 SHA1 可以说是目前应用最广泛的Hash算法,而它
们都是以MD4 为基础设计的。
设计高效算法往往需要使用Hash链表,常数级的查找速度是任何别的算法无法比拟的,Hash链表的构造和冲突
的不同实现方法对效率当然有一定的影响,然而Hash函数是Hash链表最核心的部分,下面是几款经典软件中使
用到的字符串Hash函数实现,通过阅读这些代码,我们可以在Hash算法的执行效率、离散性、空间利用率等方
面有比较深刻的了解。
下面分别介绍几个经典软件中出现的字符串Hash函数。
●PHP中出现的字符串Hash函数
static unsigned long hashpjw(char *arKey, unsigned int nKeyLength) { unsigned long h = 0, g; char *arEnd=arKey+nKeyLength;
while (arKey < arEnd) { h = (h << 4) + *arKey++; if ((g = (h & 0xF0000000))) { h = h ^ (g >> 24); h = h ^ g; } }
return h; }
|
●OpenSSL中出现的字符串Hash函数
unsigned long lh_strhash(char *str) { int i,l; unsigned long ret=0; unsigned short *s;
if (str == NULL) return(0); l=(strlen(str)+1)/2; s=(unsigned short *)str;
for (i=0; i<l; i++) ret^=(s[i]<<(i&0×0f));
return(ret); }
/* The following hash seems to work very well on normal text strings * no collisions on /usr/dict/words and it distributes on %2^n quite * well, not as good as MD5, but still good. */ unsigned long lh_strhash(const char *c) { unsigned long ret=0; long n; unsigned long v; int r;
if ((c == NULL) || (*c == ‘\0′)) return(ret);
/* unsigned char b[16]; MD5(c,strlen(c),b); return(b[0]|(b[1]<<8)|(b[2]<<16)|(b[3]<<24)); */
n=0×100; while (*c) { v=n|(*c); n+=0×100; r= (int)((v>>2)^v)&0×0f; ret=(ret(32-r)); ret&=0xFFFFFFFFL; ret^=v*v; c++; }
return((ret>>16)^ret); }
|
●MySql中出现的字符串Hash函数
#ifndef NEW_HASH_FUNCTION
/* Calc hashvalue for a key (区分大小写)*/ static uint calc_hashnr(const byte *key,uint length) { register uint nr=1, nr2=4;
while (length––) { nr^= (((nr & 63)+nr2)*((uint) (uchar) *key++))+ (nr << 8); nr2+=3; }
return((uint) nr); }
/* Calc hashvalue for a key, case indepenently (不区分大小写) */ static uint calc_hashnr_caseup(const byte *key,uint length) { register uint nr=1, nr2=4;
while (length–) { nr^= (((nr & 63)+nr2)*((uint) (uchar) toupper(*key++)))+ (nr << 8); nr2+=3; }
return((uint) nr); }
#else /* * Fowler/Noll/Vo hash * * The basis of the hash algorithm was taken from an idea sent by email to the * IEEE Posix P1003.2 mailing list from Phong Vo ([email protected]) and * Glenn Fowler ([email protected]). Landon Curt Noll ([email protected]) * later improved on their algorithm. * * The magic is in the interesting relationship between the special prime * 16777619 (2^24 + 403) and 2^32 and 2^8. * * This hash produces the fewest collisions of any function that we’ve seen so * far, and works well on both numbers and strings. */
//(区分大小写) uint calc_hashnr(const byte *key, uint len) { const byte *end=key+len; uint hash;
for (hash = 0; key < end; key++) { hash *= 16777619; hash ^= (uint) *(uchar*) key; }
return (hash); }
//(不区分大小写) uint calc_hashnr_caseup(const byte *key, uint len) { const byte *end=key+len; uint hash;
for (hash = 0; key < end; key++) { hash *= 16777619; hash ^= (uint) (uchar) toupper(*key); }
return (hash); } #endif
|
●另一个经典字符串Hash函数
unsigned int hash(char *str) { register unsigned int h; register unsigned char *p;
for(h=0, p = (unsigned char *)str; *p ; p++) h = 31 * h + *p;
return h; }
|