欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Hash算法小结

程序员文章站 2022-03-08 16:42:46
...

Hash算法小结

https://www.jianshu.com/p/bf1d7eee28d0
http://blog.jobbole.com/106733/
https://blog.csdn.net/v_JULY_v/article/details/6256463
http://www.alloyteam.com/2017/05/hash-functions-introduction/
http://blog.csdn.net/l1028386804/article/details/54573106

Hash算法,我们经常碰到,最近抽空整理一下哈希相关的东西

  • Hash简介
  • Hash在数据结构中的应用
  • Hash碰撞的一些解决方法
  • Hash在安全领域的应用

Hash 简介

什么是Hash

Hash算法又称为杂凑算法、摘要(digest)算法、散列算法、指纹(fingerprint)算法,可以将任意长度的二进制明文映射成较短(固定长度)二进制串(hash值)。与指纹一样,这种标志与明文中的每一个字节都相关,当原文件发生变化时,其标志值也会发生变化。

Hash算法的特点

一个优秀的Hash算法,具有一下几个特点:

  • 正向快速:给定原文和Hash算法,在有限时间和资源内能计算得到Hash值
  • 逆向困难:给定若干Hash值,在有限时间内无法(基本不可能)逆推出原文
  • 输入敏感:原始输入信息发生任何变化,新产生的Hash值应该发生很大变化
  • 避免碰撞:很难找到两段内容不同的明文,使得他们Hash值一致(即发生碰撞)

对于任意两个不同的数据块,其Hash值相同的可能性绩效;对于一个给定的数据块,找到和其Hash值相同的数据块极为困难。

在不同的使用场景,如数据将结构或者安全领域,会对其中某一些特点有一些侧重点。

Hash 在数据结构中的应用

在数据结构中,对速度比较重视,对抗碰撞不太看重,只要保证Hash均匀分布就可以了。例如hashmap,hash(key)的目的加快键值对的查找,key的作用是将元素适当地放在各个桶里,只要保证元素尽量均匀。

  • JDK中String.hashCode()

      public int hashCode() {
          int h = hash;
          if (h == 0 && value.length > 0) {
              char val[] = value;
              for (int i = 0; i < value.length; i++) {
                  h = 31 * h + val[i];
              }
              hash = h;
          }
          return h;
      }
    

    hash值的计算公式:s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]

这里为什么选用31作为hashcode乘子

  • 31是一个不大不小的奇质数。选择偶数在乘法运算中会溢出,乘2相当与移位。

  • 可以被JVM优化,31*i=(2<<5)-i,乘法运算可以被移位和减法代替;

  • HashMap中的hash(Object obj)方法

      static final int hash(Object key) {
          int h;
          return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
      }
    

这里进行了一次抑或运算,可以降低冲突。

Hash碰撞的一些解决方法

由于哈希算法被计算的数据是无限的,而计算后的结果范围有限,因此总会存在不同的数据经过计算后得到的值相同,这就是哈希冲突。
通过构造性能良好的哈希函数,可以减少冲突,但一般不可避免完全冲突。
常用的解决冲突的方法有一下几种:

开放定址法

从发生冲突的那个单元起,按照一定的次序,从哈希表中找到一个空闲的单元。然后把发生冲突的元素存入到该单元的一种方法。开放定址法需要的表长度要大于等于所需要存放的元素。
在开放定址法中解决冲突的方法有:线行探查法、平方探查法、双散列函数探查法
开放定址法的缺点在于删除元素的时候不能真的删除,否则会引起查找错误,只能做一个特殊标记。只到有下个元素插入才能真正删除该元素。
简而言之:发生冲突,计算出来的hash值上加一个增量,增量可以有以下几种取法

  • 线性探测再散列

    增量为1,依次向后查找。

  • 二次探测再散列

    增量为平方,依次查找

  • 伪随机探测再散列

    生成一个随机增量,进行查找

再哈希法

就是同时构造多个不同的哈希函数:
Hi = RHi(key) i= 1,2,3 … k;
当H1 = RH1(key) 发生冲突时,再用H2 = RH2(key) 进行计算,直到冲突不再产生,这种方法不易产生聚集,但是增加了计算时间。

链地址法

链接地址法的思路是将哈希值相同的元素构成一个同义词的单链表,并将单链表的头指针存放在哈希表的第i个单元中,查找、插入和删除主要在同义词链表中进行。链表法适用于经常进行插入和删除的情况。

建立公共溢出区

将哈希表分为公共表和溢出表,当溢出发生时,将所有溢出数据统一放到溢出区

Hash在安全领域的应用

在加解密技术中,hash算法常用于消息摘要和签名,对消息进行完整性校验。因此,对于抗碰撞和抗篡改的要求比较高。

Hash算法的实现:求模算法,异或,附加运算

目前比较常见的摘要算法有:MD5、SHA-1、SHA256、SM3等。

MD5算法

/**
 * MD5摘要算法
 * @param plain 明文
 * @return 密文
 * @throws Exception 异常
 */
public static byte[] MD5Digest(byte[] plain)throws Exception{
    MessageDigest md = null;
    md = MessageDigest.getInstance(MD5);
    md.reset();
    md.update(plain);
    byte[] result = md.digest();
    return result;

}

SHA-1算法

/**
 * SHA-1
 * @param plain
 * @return
 */
public static byte[] SHA1Digest(byte[] plain) throws Exception{
    MessageDigest md = null;
    md = MessageDigest.getInstance(SHA1);
    md.reset();
    md.update(plain);
    byte[] result = md.digest();
    return result;
}

SHA256

/**
 * SHA-256
 * @param plain
 * @return
 */
public static byte[] SHA256Digest(byte[] plain) throws Exception {
    MessageDigest md = null;
    md = MessageDigest.getInstance(SHA256);
    md.reset();
    md.update(plain);
    byte[] result = md.digest();
    return result;
}

SM3

/**
 * SM3 摘要算法
 * @param plain 明文
 * @return
 */
public static byte[] SM3Digest(byte[] plain) throws Exception {
    SM3Digest sm3Digest = new SM3Digest();
    byte[] result = null;
    try {
        sm3Digest.update(plain,0,plain.length);
        result = new byte[sm3Digest.getDigestSize()];
        sm3Digest.doFinal(result,0);
    }catch (Exception ex){
        throw new Exception(ex);
    }
    return result;
}
相关标签: hash