一文搞懂单向散列函数(代码片段)

码农StayUp 码农StayUp     2023-01-07     442

关键词:

微信搜索:码农StayUp
主页地址:https://gozhuyinglong.github.io
源码分享:https://github.com/gozhuyinglong/blog-demos

1. 定义

单向散列函数(one-way hash function)是指对不同的输入值,通过单向散列函数进行计算,得到固定长度的输出值。这个输入值称为消息(message),输出值称为散列值(hash value)。

单向散列函数也被称为消息摘要函数(message digest function)、哈希函数或者杂凑函数。输入的消息也称为原像(pre-image)。输出的散列值也称为消息摘要(message digest)或者指纹(fingerprint),相当于该消息的身份证。

单向散列函数有多种实现算法,常见的有:MD5SHA-1SHA-2SHA-3

2. 特性

通过上面的定义,我们对单向散列函数的了解还是模糊的。下面介绍单向散列函数的特性,加深一下印象。

2.1 散列值长度固定

无论消息的长度有多少,使用同一算法计算出的散列值长度总是固定的。比如 MD5 算法,无论输入多少,产生的散列值长度总是 128 比特(16字节)。

然而比特是计算机能够识别的单位,而我们人类更习惯于使用十六进制字符串来表示(一个字节占用两位十六进制字符)。

2.2 消息不同其散列值也不同

使用相同的消息,产生的散列值一定相同。

使用不同的消息,产生的散列值也不相同。哪怕只有一个比特的差别,得到的散列值也会有很大区别。

这一特性也叫做抗碰撞性,对于抗碰撞性弱的算法,我们不应该使用。

2.3 具备单向性

只能通过消息计算出散列值,无法通过散列值反算出消息。

2.4 计算速度快

计算散列值的速度快。尽管消息越长,计算散列值的时间也越长,但也会在短时间内完成。

3. 常见算法

MD5 与 SHA-1 算法已被攻破,不应该被用于新的用途;SHA-2 与 SHA-3 还是安全的,可以使用。

SHA-2包括:SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。

SHA-3包括:SHA3-224、SHA3-256、SHA3-384、SHA3-512。

算法名称散列值长度是否安全
MD5128不安全
SHA-1160不安全
SHA-224224安全
SHA-256256安全
SHA-384384安全
SHA-512512安全
SHA-512/224224安全
SHA-512/256256安全
SHA3-224224安全
SHA3-256256安全
SHA3-384384安全
SHA3-512512安全

4. 应用场景

单向散列函数并不能确保信息的机密性,它是一种保证信息完整性的密码技术。下面来看它的应用场景。

4.1 用户密码保护

用户在设置密码时,不记录密码本身,只记录密码的散列值,只有用户自己知道密码的明文。校验密码时,只要输入的密码正确,得到的散列值一定是一样的,表示校验正确。

为了防止彩虹表破解,还可以为密码进行加盐处理,只要验证密码时,使用相同的盐即可完成校验。

使用散列值存储密码的好处是:即使数据库被盗,也无法将密文反推出明文是什么,使密码保存更安全。

4.2 接口验签

为了保证接口的安全,可以采用签名的方式发送。

发送者与接收者要有一个共享秘钥。当发送者向接收者发送请求时,参数中附加上签名(签名由共享秘钥 + 业务参数,进行单向散列函数加密生成)。接收者收到后,使用相同的方式生成签名,再与收到的签名进行比对,如果一致,验签成功。

这样即可以验证业务参数是否被篡改,又能验明发送者的身份。

4.3 文件完整性校验

文件被挂载到网站时,同时也附上其散列值和算法,比如 Tomcat 官网。

用户下载后,计算其散列值,对比结果是否相同,从而校验文件的完整性。

4.4 云盘秒传

当我们将自己喜欢的视频放到网盘上时,发现只用了几秒的时间就上传成功了,而这个文件有几个G大小,是怎么做到的呢?

其实这个“秒传”功能可以利用单向散列函数来实现。

当我们上传一个文件时,云盘客户端会先为该文件生成一个散列值。拿着这个散列值去数据库中匹配,如果匹配到,说明该文件已经在云服务器存在。只需将该散列值与用户进行关联,便可完成本次“上传”。

这样,一个文件在云服务器上只会存一份,大大节约了云服务器的空间。

5. 代码实现

JDK的 java.security.MessageDigest 类为我们提供了消息摘要算法,用于 MD5和SHA的散列值生成。下面代码做了简单的封装,便于直接使用。

public class MDUtil 

    /**
     * MD5 加密
     *
     * @param data 要加密的数据
     * @return 32位十六进制字符串
     */
    public static String MD5(byte[] data) 
        try 
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] bytes = md.digest(data);
            return bytesToHexString(bytes);
         catch (NoSuchAlgorithmException e) 
            e.printStackTrace();
        
        return "";
    

    /**
     * MD5 加密
     *
     * @param data 要加密的数据
     * @return 32位十六进制字符串
     */
    public static String MD5(String data) 
        return MD5(data.getBytes());
    

    /**
     * SHA-1 加密
     *
     * @param data 要加密的数据
     * @return 40位十六进制字符串
     */
    public static String SHA1(byte[] data) 
        try 
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            byte[] bytes = md.digest(data);
            return bytesToHexString(bytes);
         catch (NoSuchAlgorithmException e) 
            e.printStackTrace();
        
        return "";
    

    /**
     * SHA-1 加密
     *
     * @param data 要加密的数据
     * @return 40位十六进制字符串
     */
    public static String SHA1(String data) 
        return SHA1(data.getBytes());
    

    /**
     * SHA-224 加密
     *
     * @param data 要加密的数据
     * @return 56位十六进制字符串
     */
    public static String SHA224(byte[] data) 
        try 
            MessageDigest md = MessageDigest.getInstance("SHA-224");
            byte[] bytes = md.digest(data);
            return bytesToHexString(bytes);
         catch (NoSuchAlgorithmException e) 
            e.printStackTrace();
        
        return "";
    

    /**
     * SHA-224 加密
     *
     * @param data 要加密的数据
     * @return 56位十六进制字符串
     */
    public static String SHA224(String data) 
        return SHA224(data.getBytes());
    

    /**
     * SHA-256 加密
     *
     * @param data 要加密的数据
     * @return 64位十六进制字符串
     */
    public static String SHA256(byte[] data) 
        try 
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            byte[] bytes = md.digest(data);
            return bytesToHexString(bytes);
         catch (NoSuchAlgorithmException e) 
            e.printStackTrace();
        
        return "";
    

    /**
     * SHA-256 加密
     *
     * @param data 要加密的数据
     * @return 64位十六进制字符串
     */
    public static String SHA256(String data) 
        return SHA256(data.getBytes());
    

    /**
     * SHA-384 加密
     *
     * @param data 要加密的数据
     * @return 96位十六进制字符串
     */
    public static String SHA384(byte[] data) 
        try 
            MessageDigest md = MessageDigest.getInstance("SHA-384");
            byte[] bytes = md.digest(data);
            return bytesToHexString(bytes);
         catch (NoSuchAlgorithmException e) 
            e.printStackTrace();
        
        return "";
    

    /**
     * SHA-384 加密
     *
     * @param data 要加密的数据
     * @return 96位十六进制字符串
     */
    public static String SHA384(String data) 
        return SHA384(data.getBytes());
    

    /**
     * SHA-512 加密
     *
     * @param data 要加密的数据
     * @return 128位十六进制字符串
     */
    public static String SHA512(byte[] data) 
        try 
            MessageDigest md = MessageDigest.getInstance("SHA-512");
            byte[] bytes = md.digest(data);
            return bytesToHexString(bytes);
         catch (NoSuchAlgorithmException e) 
            e.printStackTrace();
        
        return "";
    

    /**
     * SHA-512 加密
     *
     * @param data 要加密的数据
     * @return 128位十六进制字符串
     */
    public static String SHA512(String data) 
        return SHA512(data.getBytes());
    

    /**
     * 将字节数组转换为十六进制字符串
     *
     * @param bytes 字节数组
     * @return 十六进制字符串
     */
    private static String bytesToHexString(byte[] bytes) 
        StringBuilder hexValue = new StringBuilder();
        for (byte b : bytes) 
            int val = b & 0xFF;
            if (val < 16) 
                hexValue.append("0");
            
            hexValue.append(Integer.toHexString(val));
        
        return hexValue.toString();
    


下面分别使用这些算法计算“123456”的散列值:

public static void main(String[] args) 
    System.out.println("MD5\\t\\t" + MDUtil.MD5("123456"));
    System.out.println("SHA-1\\t" + MDUtil.SHA1("123456"));
    System.out.println("SHA-224\\t" + MDUtil.SHA224("123456"));
    System.out.println("SHA-256\\t" + MDUtil.SHA256("123456"));
    System.out.println("SHA-384\\t" + MDUtil.SHA384("123456"));
    System.out.println("SHA-512\\t" + MDUtil.SHA512("123456"));

输出结果:

MD5      e10adc3949ba59abbe56e057f20f883e
SHA-1    7c4a8d09ca3762af61e59520943dc26494f8941b
SHA-224  f8cdb04495ded47615258f9dc6a3f4707fd2405434fefc3cbf4ef4e6
SHA-256  8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92
SHA-384  0a989ebc4a77b56a6e2bb7b19d995d185ce44090c13e2984b7ecc6d446d4b61ea9991b76a4c2f04b1b4d244841449454
SHA-512  ba3253876aed6bc22d4a6ff53d8406c6ad864195ed144ab5c87621b6c233b548baeae6956df346ec8c17f5ea10f35ee3cbc514797ed7ddd3145464e2a0bab413

我用的是Java8,还不支持 SHA-3,所以上面代码只封装了MD5、SHA-1和SHA-2。

从Java9开始支持SHA-3

6. 完整代码

完整代码请访问我的Github,若对你有帮助,欢迎给个Star,谢谢!

https://github.com/gozhuyinglong/blog-demos/blob/main/java-source-analysis/src/main/java/io/github/gozhuyinglong/utils/MDUtil.java

7. 推荐阅读

单向散列函数及案例(代码片段)

1.概述单向散列函数(one-wayhashfunction),又称单向Hash函数、杂凑函数。有一个输入和一个输出,输入的数据称为消息(message),输出的数据称为散列值(hashvalue)。单向散列函数根据输入消息... 查看详情

单向散列函数及案例(代码片段)

1.概述单向散列函数(one-wayhashfunction),又称单向Hash函数、杂凑函数。有一个输入和一个输出,输入的数据称为消息(message),输出的数据称为散列值(hashvalue)。单向散列函数根据输入消息... 查看详情

单向散列函数及案例(代码片段)

1.概述单向散列函数(one-wayhashfunction),又称单向Hash函数、杂凑函数。有一个输入和一个输出,输入的数据称为消息(message),输出的数据称为散列值(hashvalue)。单向散列函数根据输入消息... 查看详情

一文搞懂深度常用损失函数(代码片段)

一,损失函数概述大多数深度学习算法都会涉及某种形式的优化,所谓优化指的是改变$x$以最小化或最大化某个函数$f(x)$的任务,我们通常以最小化$f(x)$指代大多数最优化问题。在机器学习中,损失函数是代价函数的一部分,而... 查看详情

一文搞懂python的内建函数,自己添加一个print函数(代码片段)

目录1、什么是内建函数?2、内置的内建函数多有哪些?3、特殊函数说明3.1exec3.2eval3.3compile3.4globals和locals4、自己增加一个内置函数5、总结写python的同学应该都用过print函数,这个函数我们没有定义为什么可以调用... 查看详情

c数据结构什么是顺序表?一文迅速搞懂!(代码片段)

顺序表的创建与常见接口函数一、什么是线性表?二、如何创建一个顺序表?三、接口函数容量审查函数:初始化函数:销毁函数:尾插函数:尾删函数:头插函数:头删函数:寻找函数:指定下标插... 查看详情

python入门到精通一文让你彻底搞懂python的函数(代码片段)

🚀作者:“大数据小禅”🚀粉丝福利:加入小禅的大数据社群🚀欢迎小伙伴们点赞👍、收藏⭐、留言💬目录Python中的函数及其调用对于函数的理解:python中的自定义函数自定义空函数Python特性之... 查看详情

一文彻底搞懂python中的装饰器偏函数(代码片段)

装饰器要讲清楚装饰器,首先要知道一些前置概念。下文涉及到这些概念的地方,会展开讲述。什么是装饰器?装饰器是一种AOP(面向切面编程)的设计模式。面向对象编程往往需要通过继承或组合依赖等方... 查看详情

一文搞懂传统单节点网站的serverless上云(代码片段)

背景什么是函数?刚刚考完数学没多久的我,脑里立马想到的是自变量、因变量、函数值,也就是y=f(x)。当然,在计算机里,函数function往往指的是一段被定义好的代码程序,我们可以通过传参调用这个... 查看详情

一文搞懂防抖和节流(代码片段)

一文搞懂防抖和节流闭包作用域链闭包防抖在了解防抖和节流之前,首先要熟悉闭包的概念,因为防抖和节流函数就是使用闭包写的闭包作用域链Es6新推出了块级作用域的概念,就是大括号里面的变量都是属于这个块... 查看详情

c++基础入门丨7.指针——一文搞懂指针(代码片段)

操作系统:Windows10IDE:VisualStudio2019文章目录1什么是指针2指针变量的定义和使用3指针所占用的空间4空指针和野指针5const修饰指针6指针和数组7指针和函数8指针、函数、数组1什么是指针我们知道每一个变量都有一个内存... 查看详情

c++基础入门丨7.指针——一文搞懂指针(代码片段)

操作系统:Windows10IDE:VisualStudio2019文章目录1什么是指针2指针变量的定义和使用3指针所占用的空间4空指针和野指针5const修饰指针6指针和数组7指针和函数8指针、函数、数组1什么是指针我们知道每一个变量都有一个内存... 查看详情

c++基础入门丨7.指针——一文搞懂指针(代码片段)

操作系统:Windows10IDE:VisualStudio2019文章目录1什么是指针2指针变量的定义和使用3指针所占用的空间4空指针和野指针5const修饰指针6指针和数组7指针和函数8指针、函数、数组1什么是指针我们知道每一个变量都有一个内存... 查看详情

mysql一文搞懂mysql语法(进阶)(代码片段)

数据库语法进阶前言一、数据库约束1.2NOTNULL约束1.3UNIQUE:唯一约束1.4DEFAULT:默认值约束1.5PRIMARYKEY:主键约束1.6FOREIGNKEY:外键约束1.7CHECK约束(了解)二、表的设计三、新增四、查询4.1聚合查询4.1.1聚合函... 查看详情

一文搞懂跨域问题(代码片段)

一文搞懂跨域问题同源策略跨域解决跨域办法同源策略是浏览器的一个安全策略,保证浏览器的安全运行,要求网页中的协议名,域名和端口号要完全一致,跨域违背了同源策略就会产生跨域,比如说在百度... 查看详情

一文教你搞懂python函数装饰器(wrapper)(代码片段)

python函数装饰器函数装饰器,定义一个装饰器后,调用改装饰器,个人理解是在目标函数前后做一些操作。例如:定义一个鉴权的函数装饰器,在给目标函数的时候添加装饰函数就可以做到先鉴权,鉴权成... 查看详情

一文教你搞懂python函数装饰器(wrapper)(代码片段)

python函数装饰器函数装饰器,定义一个装饰器后,调用改装饰器,个人理解是在目标函数前后做一些操作。例如:定义一个鉴权的函数装饰器,在给目标函数的时候添加装饰函数就可以做到先鉴权,鉴权成... 查看详情

一文搞懂*argv和**kwargs(代码片段)

大多刚接触Python的学员,会对*argv和**kwargs这两个魔法变量的用法产生困惑,今天本文将全面梳理下这两个小可爱的使用方法,让各位同学彻底搞懂它们的规则。这两个变量主要用户函数定义,有了它俩你可以将不定数量的参数... 查看详情