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

哈希加密详解和md5、sha1、sha256、Java 工具类

程序员文章站 2022-03-09 18:07:56
...

前言

在所有的加密算法中使用最多的就是哈希加密了,很多人第一次接触的加密算法如MD5、SHA1都是典型的哈希加密算法,而哈希加密除了用在密码加密上,它还有很多的用途,如提取内容摘要、生成签名、文件对比、区块链等等。这篇文章就是想详细的讲解一下哈希加密,并分享一个哈希加密的工具类。

概述

哈希函数(Hash Function),也称为散列函数或杂凑函数。哈希函数是一个公开函数,可以将任意长度的消息M映射成为一个长度较短且长度固定的值H(M),称H(M)为哈希值、散列值(Hash Value)、杂凑值或者消息摘要(Message Digest)。它是一种单向密码*,即一个从明文到密文的不可逆映射,只有加密过程,没有解密过程。

它的函数表达式为:h=H(m)

无论输入是什么数字格式、文件有多大,输出都是固定长度的比特串。以比特币使用的Sh256算法为例,无论输入是什么数据文件,输出就是256bit。

哈希算法

把网址A,转换成数字1。网址B,转换成数字2。

一个网址X,转换成数字N,根据数字N作为下标,就可以快速地查找出网址X的信息。这个转换的过程就是哈希算法。

比如这里有一万首歌,给你一首新的歌X,要求你确认这首歌是否在那一万首歌之内。

无疑,将一万首歌一个一个比对非常慢。但如果存在一种方式,能将一万首歌的每首数据浓缩到一个数字(称为哈希码)中,于是得到一万个数字,那么用同样的算法计算新的歌X的编码,看看歌X的编码是否在之前那一万个数字中,就能知道歌X是否在那一万首歌中。

作为例子,如果要你组织那一万首歌,一个简单的哈希算法就是让歌曲所占硬盘的字节数作为哈希码。这样的话,你可以让一万首歌“按照大小排序”,然后遇到一首新的歌,只要看看新的歌的字节数是否和已有的一万首歌中的某一首的字节数相同,就知道新的歌是否在那一万首歌之内了。

一个可靠的哈希算法,应该满足:

对于给定的数据M,很容易算出哈希值X=F(M);

根据X很难反算出M;

很难找到M和N使得F(N)=F(M)

总结哈希加密的特点

  1. 易压缩:对于任意大小的输入x,Hash值的长度很小,在实际应用中,函数H产生的Hash值其长度是固定的。

  2. 易计算:对于任意给定的消息,计算其Hash值比较容易。

  3. 不可逆:对于给定的Hash值,要找到使得在计算上是不可行的,即求Hash的逆很困难。在给定某个哈希函数H和哈希值H(M)的情况下,得出M在计算上是不可行的。即从哈希输出无法倒推输入的原始数值。这是哈希函数安全性的基础。

  4. 抗碰撞性:理想的Hash函数是无碰撞的,但在实际算法的设计中很难做到这一点。
    有两种抗碰撞性:一种是弱抗碰撞性,即对于给定的消息,要发现另一个消息,满足在计算上是不可行的;另一种是强抗碰撞性,即对于任意一对不同的消息,使得在计算上也是不可行的。

  5. 高灵敏性:这是从比特位角度出发的,指的是1比特位的输入变化会造成1/2的比特位发生变化。消息M的任何改变都会导致哈希值H(M)发生改变。即如果输入有微小不同,哈希运算后的输出一定不同。

哈希加密并非不可**,2004年,王小云教授在国际密码学大会上公布了**Hash函数的关键技术。

哈希加密和对称/非对称加密对比

哈希加密详解和md5、sha1、sha256、Java 工具类
主要有这些区别:

  1. 哈希密码是不可逆的,因此无法从密文中获取到原文,而对称/非对称加密可以;
  2. 哈希密码加密大部分不需要**(除了HMAC),而对称/非对称加密需要;
  3. 哈希加密不管是短数据还是长数据,加密后得到的密文长度是固定的,而对称/非对称通常和原文的长度成正比;
  4. 哈希加密有可能碰撞,虽然理论的哈希加密是不可能碰撞的,但是只是理论,王小云教授之前就提出碰撞的方法。而对于对称/非对称,一个密文用**解密后的结果一定是唯一的;

常见的加密算法

MD5信息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。MD5由美国密码学家罗纳德·李维斯特(Ronald Linn Rivest)设计,于1992年公开,用以取代MD4算法。这套算法的程序在 RFC 1321 标准中被加以规范。1996年后该算法被证实存在弱点,可以被加以**,对于需要高度安全性的数据,专家一般建议改用其他算法,如SHA-2。2004年,证实MD5算法无法防止碰撞(collision),因此不适用于安全性认证,如SSL公开**认证或是数字签名等用途。

SHA-1(英语:Secure Hash Algorithm 1,中文名:安全散列算法1)是一种密码散列函数,美国国家安全局设计,并由美国国家标准技术研究所(NIST)发布为联邦数据处理标准(FIPS)。SHA-1可以生成一个被称为消息摘要的160位(20字节)散列值,散列值通常的呈现形式为40个十六进制数。
SHA-1已经不再视为可抵御有充足资金、充足计算资源的攻击者。2005年,密码分析人员发现了对SHA-1的有效攻击方法,这表明该算法可能不够安全,不能继续使用,自2010年以来,许多组织建议用SHA-2或SHA-3来替换SHA-1。Microsoft、Google以及Mozilla都宣布,它们旗下的浏览器将在2017年前停止接受使用SHA-1算法签名的SSL证书。
2017年2月23日,CWI Amsterdam与Google宣布了一个成功的SHA-1碰撞攻击,发布了两份内容不同但SHA-1散列值相同的PDF文件作为概念证明。

SHA-2/SHA-256
SHA-2有多种不同的位数,导致这个名词有一些混乱。但是无论是“SHA-2”,“SHA-256”或“SHA-256位”,其实都是指同一种加密算法。但是SHA-224”,“SHA-384”或“SHA-512”,表示SHA-2的二进制长度。还要另一种就是会把算法和二进制长度都写上,如“SHA-2 384”。
SSL行业选择SHA作为数字签名的散列算法,从2011到2015,一直以SHA-1位主导算法。但随着互联网技术的提升,SHA-1的缺点越来越突显。从去年起,SHA-2成为了新的标准,所以现在签发的SSL证书,必须使用该算法签名。
也许有人偶尔会看到SHA-2 384位的证书,很少会看到224位,因为224位不允许用于公共信任的证书,512位,不被软件支持。
初步预计,SHA-2的使用年限为五年,但也许会被提前淘汰。这需要时间来验证。
哈希加密详解和md5、sha1、sha256、Java 工具类

HMAC是**相关的哈希运算消息认证码(Hash-based Message Authentication Code)的缩写,由H.Krawezyk,M.Bellare,R.Canetti于1996年提出的一种基于Hash函数和**进行消息认证的方法,并于1997年作为RFC2104被公布,并在IPSec和其他网络协议(如SSL)中得以广泛应用,现在已经成为事实上的Internet安全标准。它可以与任何迭代散列函数捆绑使用。
HMAC运算利用hash算法,以一个消息M和一个**K作为输入,生成一个定长的消息摘要作为输出。HMAC算法利用已有的Hash函数,关键问题是如何使用**。
哈希加密详解和md5、sha1、sha256、Java 工具类
HMAC的**长度可以是任意大小,如果小于n(hash输出值的大小),那么将会消弱算法安全的强度。建议使用长度大于n的**,但是采用长度大的**并不意味着增强了函数的安全性。**应该是随机选取的,可以采用一种强伪随机发生器,并且**需要周期性更新,这样可以减少散列函数弱**的危险性以及已经暴露**所带来的破坏
使用SHA-1、SHA-224、SHA-256、SHA-384、SHA-512所构造的HMAC,分别称为HMAC-SHA1、HMAC-SHA-224、HMAC-SHA-384、HMAC-SHA-512。
HMAC算法更象是一种加密算法,它引入了**,其安全性已经不完全依赖于所使用的Hash算法,安全性主要有以下几点保证。
(1)使用的**是双方事先约定的,第三方不可能知道。作为非法截获信息的第三方,能够得到的信息只有作为“挑战”的随机数和作为“响应”的HMAC 结果,无法根据这两个数据推算出**。由于不知道**,所以无法仿造出一致的响应。
(2)HMAC与一般的加密重要的区别在于它具有“瞬时"性,即认证只在当时有效,而加密算法被**后,以前的加密结果就可能被解密。
HMAC的安全性依赖于散列函数H的密码学属性:①抗碰撞属性;②当应用于一个单独的消息分组时H的压缩函数的消息认证属性 。

四种算法的用途:MD5、SHA-1可以在一些数据量较小的情况下用来生成信息摘要,他们生成的摘要长度较短,加密速度快,但是摘要长度较短,有碰撞的可能;SHA-256可以用来对一些大数据量的数据来进行加密、或者生成信息摘要,基于它的长摘要长度,碰撞的可能性较低;HMAC引入了**,因此其安全性最高,可以很好的用在对密码的加密上;

JAVA工具类

package cn.hengyumo.humor.utils;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * HeaUtil
 * Hash encryption algorithm
 * 哈希加密算法:MD5、SHA-1、SHA-256、HMAC-SHA-1、HMAC-SHA-256
 * 需要导入 org.apache.commons.codec 包
 *
 * @author hengyumo
 * @version 1.0
 * @since 2019/11/12
 */
@SuppressWarnings("WeakerAccess")
public class HeaUtil {

    /**
     * md5加密
     *
     * @param text 内容
     * @return digest 摘要
     * @throws NoSuchAlgorithmException e
     */
    public static String md5(String text) throws NoSuchAlgorithmException {
        MessageDigest messageDigest = MessageDigest.getInstance("MD5");
        byte[] bytes = messageDigest.digest(text.getBytes());
        return Hex.encodeHexString(bytes);
    }

    /**
     * sha1加密
     *
     * @param text 内容
     * @return digest 摘要
     * @throws NoSuchAlgorithmException e
     */
    public static String sha1(String text) throws NoSuchAlgorithmException {
        MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
        byte[] bytes = messageDigest.digest(text.getBytes());
        return Hex.encodeHexString(bytes);
    }

    /**
     * sha256加密
     *
     * @param text 内容
     * @return digest 摘要
     * @throws NoSuchAlgorithmException e
     */
    public static String sha256(String text) throws NoSuchAlgorithmException {
        MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
        byte[] bytes = messageDigest.digest(text.getBytes());
        return Hex.encodeHexString(bytes);
    }

    /**
     * hmac-sha1加密
     *
     * @param text 内容
     * @param key **
     *
     * @return 密文
     * @throws Exception e
     */
    public static String hmacSha1(String text, String key) throws Exception {
        SecretKeySpec sk = new SecretKeySpec(key.getBytes(), "HmacSHA1");
        return hmacSha1(text, sk);
    }

    /**
     * hmac-sha1加密
     *
     * @param text 内容
     * @param sk **
     *
     * @return 密文
     * @throws Exception e
     */
    public static String hmacSha1(String text, SecretKeySpec sk) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA1");
        mac.init(sk);
        byte[] rawHmac = mac.doFinal(text.getBytes());
        return new String(Base64.encodeBase64(rawHmac));
    }

    /**
     * 生成 HmacSha1 **
     *
     * @param key **字符串
     * @return SecretKeySpec
     */
    public static SecretKeySpec createHmacSha1Key(String key) {
        return new SecretKeySpec(key.getBytes(), "HmacSHA1");
    }

    /**
     * hmac-sha256加密
     *
     * @param text 内容
     * @param key **
     *
     * @return 密文
     * @throws Exception e
     */
    public static String hmacSha256(String text, String key) throws Exception {
        SecretKeySpec sk = new SecretKeySpec(key.getBytes(), "HmacSHA256");
        return hmacSha1(text, sk);
    }

    /**
     * hmac-sha256加密
     *
     * @param text 内容
     * @param sk **
     *
     * @return 密文
     * @throws Exception e
     */
    public static String hmacSha256(String text, SecretKeySpec sk) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(sk);
        byte[] rawHmac = mac.doFinal(text.getBytes());
        return new String(Base64.encodeBase64(rawHmac));
    }

    /**
     * 生成 HmacSha256 **
     *
     * @param key **字符串
     * @return SecretKeySpec
     */
    public static SecretKeySpec createHmacSha256Key(String key) {
        return new SecretKeySpec(key.getBytes(), "HmacSHA256");
    }

    /**
     * 测试
     *
     * @param args args
     */
    public static void main(String[] args) throws Exception {
        String s = "123456789了踩踩踩";
        System.out.println(md5(s));
        System.out.println(sha1(s));
        System.out.println(sha256(s));
        String k = "aaa@qq.com";
        System.out.println(hmacSha1(s, k));
        s = "aeqnfoavneornqoenr1啊可是到了南方情况无法弄清了我呢010jownfasdfqijqor";
        System.out.println(hmacSha1(s, k));
        SecretKeySpec sk1 = createHmacSha1Key(k);
        System.out.println(hmacSha1(s, sk1));
        SecretKeySpec sk256 = createHmacSha256Key(k);
        System.out.println(hmacSha256(s, sk256));
    }
}


输出

d415ffe55bf2e18b9e059be4ab371fe7
15b03a4189d24ca3459e199bbf8de7fc3e4d68f3
e32b84b738416e91198112a57d295ba685a40e55bb114725e8a444efd53d5a01
ck66ZAkPEh+19UIgnX775N3nLyc=
WVJAs/+6OlwkzM1ZEK+t8iMgtyc=
WVJAs/+6OlwkzM1ZEK+t8iMgtyc=
E1sXoiPtnjzMiDRagp08Lt3gBKXE/EU+nLJZpy8hURc=

备注

使用需要导入maven或jar包


        <!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.12</version>
        </dependency>

用途探讨

  1. 文件、图片等数据的标识码
    对文件进行md5加密,得到一个唯一的文件摘要,把摘要存储,之后再次上传文件时前端先计算摘要,如果文件的摘要在后端发现重复的,那么就不进行上传。这样可以为服务器节省大量的硬盘资源。这主要利用了哈希加密的压缩性、唯一性;
  2. 密码加密存储
    密码如果明文存储到数据库是不安全的,这时可以在存储之前先用sha1或者sha256等算法进行加密,加密后进行存储,利用哈希加密的不可逆性,保证了密码存储的安全性,防止密码泄露。
  3. 生成数字签名、防止篡改
    所谓的签名,指的是对于一份数据,拥有独一无二的标识=》这标识就是数字签名。生成数字签名的方法就是用md5等哈希算法生成摘要,并将摘要公开,对方获得数据之后,通过再次进行哈希生成摘要后对比公开摘要,就可以确认文件是不是原始的那份,而不是被篡改过的;这主要利用了哈希加密的高灵敏性,消息M的任何改变都会导致哈希值H(M)发生改变。即如果输入有微小不同,哈希运算后的输出一定不同。
  4. 防止查表法
    所谓查表法针对的就是md5、sha1等哈希加密算法,黑客预先收集一些常用的密码存入表中,预先对表中的密码计算md5、sha1值,之后就可以通过对比表和数据库的密文,来获取到密码的明文;防止查表法的方式有加盐、或者使用HMAC这样的**哈希算法,从而使得查表法失效。

参考&引用

通俗易懂的哈希算法讲解
https://blog.csdn.net/zongyue_wang/article/details/81947142
MD5
https://baike.baidu.com/item/MD5/212708?fr=aladdin
SHA-1
https://baike.baidu.com/item/SHA-1/1699692?fromtitle=SHA1&fromid=8812671&fr=aladdin
散列算法:SHA-1,SHA-2和SHA-256之间的区别(下)
https://www.jianshu.com/p/68c664b663f4
hmac
https://baike.baidu.com/item/hmac/7307543?fr=aladdin
HMAC的图解
https://blog.csdn.net/chengqiuming/article/details/82822933