从 01 开始 从 01 开始
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • RSS
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金等
  • 职场规划
  • 关于离职
  • 杂谈
  • 📖 读书

    • 读书工具
    • 读书笔记
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档

晓林

程序猿,自由职业者,博主,英语爱好者,健身达人
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • RSS
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金等
  • 职场规划
  • 关于离职
  • 杂谈
  • 📖 读书

    • 读书工具
    • 读书笔记
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档
  • JavaSE

    • 我的 Java 学习路线
    • 安装 Java
    • Java数据类型

    • Java 多版本配置
    • 面向对象

    • Java核心类

    • IO

    • Java与时间

    • 异常处理

    • 哈希和加密算法

      • 什么是哈希算法
        • 哈希算法有什么用
        • Java 中的 hash
        • 哈希算法的碰撞
        • 常见的哈希算法
        • MD5 算法
        • SHA-1
        • 哈希与存储密码
        • 重复输入密码
        • 彩虹表
        • 加盐
        • 小结
      • 第三方库的哈希算法
      • MAC 算法
      • 加密算法介绍
      • 对称加密算法
      • 口令加密算法
      • 非对称加密算法介绍
      • RSA 算法原理(一):数学知识
      • RSA算法原理(二):概述
      • RSA 算法原理(三):补充知识
      • Java 中的 RSA 算法
      • DH 算法
      • 数字签名
      • SSL 和 TLS 协议介绍
    • Java8新特性

    • 网络编程

  • JavaSenior

  • JavaEE

  • JavaWeb

  • Spring

  • 主流框架

  • SpringMVC

  • SpringBoot

  • Java
  • JavaSE
  • 哈希和加密算法
2023-03-20
目录

什么是哈希算法

# 什么是哈希算法

哈希算法(Hash)又称摘要算法(Digest),它的作用是:对任意一组输入数据进行计算,得到一个固定长度的输出摘要。

哈希算法最重要的特点就是:

  • 相同的输入一定得到相同的输出;
  • 不同的输入大概率得到不同的输出。

# 哈希算法有什么用

哈希算法的目的就是为了验证原始数据是否被篡改。

举个例子,你写了一封邮件给别人,并且算出了哈希是 1234;

如果邮件在传输的过程中,被人截取并篡改了里面的内容,那么算出的哈希大概率就不是 1234 了。这样别人在和你检查哈希是否一致的时候,就会发现哈希不一样,从而知道原始数据被篡改过了。

这里的原始数据不仅仅指邮件这种文本类的数据,还可以是二进制文件,例如软件的安装包。我们在网站上下载软件的时候,经常看到下载页显示的哈希:

截图来自 MySQL :: Download MySQL Community Server (opens new window),MD5 也是一种哈希算法,我们后续会讲。

我们下载软件安装包后,可以检查下载后的文件的哈希,是否对的上官网的哈希,哈希一致则说明下载的过程中,原始数据没有被人篡改,从而避免下载到一些恶意的软件或者病毒等。

# Java 中的 hash

Java 字符串的 hashCode() 就是一个哈希算法,它的输入是任意字符串,输出是固定的 int 整数:

System.out.println("hello".hashCode()); //99162322
System.out.println("hello, java".hashCode()); //2057144552
System.out.println("hello, peter".hashCode());  //-647369722
1
2
3

两个相同的字符串永远会计算出相同的 hashCode,否则基于 hashCode定位的 HashMap就无法正常工作。这也是为什么当我们自定义一个 class 时,覆写 equals()方法时我们必须正确覆写 hashCode()方法。

如果你还不知道 HashMap 是什么,也没关系,学 HashMap 的时候还会提到这一节的知识点

# 哈希算法的碰撞

哈希碰撞是指,两个不同的输入得到了相同的输出:

System.out.println("AaAaAa".hashCode());  //1952508096
System.out.println("BBAaBB".hashCode());  //1952508096
1
2

前面我们说过,哈希算法的最重要的特点是:

  • 相同的输入一定得到相同的输出;
  • 不同的输入大概率得到不同的输出。

注意,只是大概率得到不同的输出,不是百分百不同。输出的字节长度是固定的,String 的 hashCode() 输出是 int 整数,只有 4 个字节,最多只有 4294967296 种输出,但输入的数据长度是不固定的,有无数种输入。所以,哈希算法是把一个无限的输入集合映射到一个有限的输出集合,必然会产生碰撞。

碰撞不可怕,我们担心的不是碰撞,而是碰撞的概率,因为碰撞概率的高低关系到哈希算法的安全性。为了保证哈希算法的安全性,必须得让碰撞的概率低,并且不能随意猜测输出。

不能猜测输出是指,输入的任意一个 bit 的变化会造成输出完全不同,这样就很难从输出反推输入(只能依靠暴力穷举)。假设一种哈希算法有如下规律:

hashA("java001") = "123456"
hashA("java002") = "123457"
hashA("java003") = "123458"
1
2
3

那么很容易从输出 123459 反推输入,这种哈希算法就不安全。安全的哈希算法从输出是看不出任何规律的:

hashB("java001") = "123456"
hashB("java002") = "580271"
hashB("java003") = ???
1
2
3

# 常见的哈希算法

目前市面上比较常见的哈希算法有:

算法 输出长度(位) 输出长度(字节)
MD5 128 bits 16 bytes
SHA-1 160 bits 20 bytes
RipeMD-160 160 bits 20 bytes
SHA-256 256 bits 32 bytes
SHA-512 512 bits 64 bytes

根据碰撞概率,哈希算法的输出长度越长,就越难产生碰撞,也就越安全。Java 标准库提供了常用的哈希算法,并且有一套统一的接口。

类似的,计算 SHA-256,我们需要传入名称 "SHA-256",计算 SHA-512,我们需要传入名称 "SHA-512"。Java 标准库支持的所有哈希算法可以在这里 (opens new window)查到。

# MD5 算法

MD5 即 Message-Digest Algorithm 5(信息-摘要算法 5),早期使用还是很广泛的,主流的编程语言普遍实现了 MD5。MD5 的前身有 MD2、MD3 和 MD4。但随着时间的推移,16Bit 不太够看了,MD5 因为输出长度较短,短时间内破解是可能的,目前已经不推荐使用。

Java 标准库提供了常用的哈希算法,并且有一套统一的接口。我们以 MD5 算法为例,看看如何对输入计算哈希:

import java.math.BigInteger;
import java.security.MessageDigest;

public class Hash2MD5 {
  public static void main(String[] args) throws Exception{
    MessageDigest md = MessageDigest.getInstance("MD5");
    md.update("Hello".getBytes("UTF-8"));
    md.update("World".getBytes("UTF-8"));
    byte[] result = md.digest();
    System.out.println(new BigInteger(1, result).toString(16));
    // output: 68e109f0f40ca72a15e05cc22786f8e6
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

使用 MessageDigest时,我们首先根据哈希算法获取一个 MessageDigest实例,然后,反复调用 update(byte[])输入数据。

当输入结束后,调用 digest() 方法获得 byte[] 数组表示的摘要,最后,把它转换为十六进制的字符串。

运行上述代码,可以得到输入 HelloWorld 的 MD5 是 68e109f0f40ca72a15e05cc22786f8e6。

# SHA-1

SHA-1 也是一种哈希算法,它的输出是 160 bits,即 20 字节。SHA-1 是由美国国家安全局开发的,SHA 算法实际上是一个系列,包括 SHA-0(已废弃)、SHA-1、SHA-256、SHA-512 等。

在 Java 中使用 SHA-1,和 MD5 完全一样,只需要把算法名称改为 "SHA-1":

    MessageDigest md = MessageDigest.getInstance("SHA-1");
    md.update("Hello".getBytes("UTF-8"));
    md.update("World".getBytes("UTF-8"));
    byte[] result = md.digest();
    System.out.println(new BigInteger(1, result).toString(16));
    // output: db8ac1c259eb89d4a131b253bacfca5f319d54f2
1
2
3
4
5
6

# 哈希与存储密码

哈希算法的另一个重要用途是存储用户口令。如果直接将用户的原始口令存放到数据库中,会产生极大的安全风险:

  • 数据库管理员能够看到用户明文口令;
  • 数据库数据一旦泄漏,黑客即可获取用户明文口令

已有不少公司因为明文存储密码,导致数据库泄漏后造成重大问题:

2011 年 12 月,CSDN 的安全系统遭到黑客攻击,600 万用户的登录名、密码及邮箱遭到泄漏。随后,CSDN"密码外泄门"持续发酵,天涯、世纪佳缘等网站相继被曝用户数据遭泄密。天涯网于 12 月 25 日发布致歉信,称天涯 4000 万用户隐私遭到黑客泄露。此次失窃的只是密码集,用户只要及时修改密码即可避免隐私失窃,因此不用恐慌。但用户修改密码只是“治标”,网站改变数据存放策略才是“治本”。

密码外泄门_百度百科 (opens new window)

那如果我们存储密码的哈希呢?用户在注册的时候,输入密码,我们不直接存储密码,而是存储哈希;而当用户登录时,在用户输入原始口令后,计算用户输入的原始口令的 MD5 并与数据库存储的 MD5 对比,如果一致,说明口令正确,否则,口令错误。

因此,数据库存储用户名和口令的表内容应该像下面这样:

username password
bob f30aa7a662c728b7407c54ae6bfd27d1
alice 25d55ad283aa400af464c76d713c07ad
tim bed128365216c019988915ed3add75fb

这样一来有几个好处:

  1. 数据库管理员看不到用户的原始口令。
  2. 即使数据库泄漏,黑客也无法拿到用户的原始口令。想要拿到用户的原始口令,必须用暴力穷举的方法,一个口令一个口令地试,直到某个口令计算的 MD5 恰好等于指定值。

这种存储方式也是目前主流的存储敏感信息的措施。

# 重复输入密码

在设置密码的时候,通常屏幕上不会直接显示明文,防止密码被人看到;但这样有个缺点,万一用户输错了,就会导致登录失败。例如用户想输入的是 Abc123,但用户错输入成了 abc123,这样用户以为密码是 Abc123,导致后续登录用的密码也是 Abc123,最后登录不了。

因此,现在通常要用户输入两次密码的,两次都输入错了概率较低,能保证用户输入的密码,和想要输入的密码,是一致的:

再举个例子延伸下:笔者所在的公司里,数据库密码是分两个人保管的,每个人只知道一半的密码。有一份需求中,我需要用到数据库密码生成的密文,对数据库进行操作;此时可以生成密文后,再生成一次密文,比较两者是否一致,这样能避免输入密码的时候输入错误了,导致生成的密文也是错的。

# 彩虹表

黑客并不笨,暴力穷举会消耗大量的算力和时间。但是,如果有一个预先计算好的常用口令和它们的 MD5 的对照表:

常用口令 MD5
hello123 f30aa7a662c728b7407c54ae6bfd27d1
12345678 25d55ad283aa400af464c76d713c07ad
passw0rd bed128365216c019988915ed3add75fb
19700101 570da6d5277a646f6552b8832012f5dc
… …
20201231 6879c0ae9117b50074ce0a0d4c843060

这个表就是彩虹表。如果用户使用了常用口令,黑客从 MD5 一下就能反查到原始口令:

用户 bob 的密码 MD5:f30aa7a662c728b7407c54ae6bfd27d1,原始口令:hello123;

用户 alice 的密码 MD5:25d55ad283aa400af464c76d713c07ad,原始口令:12345678;

用户 tim 的密码 MD5:bed128365216c019988915ed3add75fb,原始口令:passw0rd。

这就是为什么不要使用弱密码,以及不要使用生日作为密码的原因。

# 加盐

即使用户使用了常用密码,我们也可以采取措施来抵御彩虹表攻击,方法是对每个口令额外添加随机数(称为盐),然后加密整个字符串,这个方法称之为加盐(salt),伪代码如下:

digest = md5(salt+inputPassword)
1

当用户登录的时候,将用户输入的密码和盐一起计算哈希值,然后和数据库中存储的密码进行比较。盐的目的在于使黑客的彩虹表失效,即使用户使用常用口令,也无法从 MD5 反推原始口令。

# 小结

哈希算法可用于验证数据完整性,具有防篡改检测的功能;

常用的哈希算法有 MD5、SHA-1 等;

用哈希存储口令时要考虑加盐防止彩虹表攻击。

上次更新: 2024/10/1 17:03:07
return 和 finally 谁先执行
第三方库的哈希算法

← return 和 finally 谁先执行 第三方库的哈希算法→

最近更新
01
吐槽一下《僵尸校园》
05-15
02
2025 年 4 月记
04-30
03
山西大同 “订婚强奸案” 将会给整个社会带来的影响有多严重? - 知乎 转载
04-26
更多文章>
Theme by Vdoing | Copyright © 2022-2025 | 粤 ICP 备 2022067627 号 -1 | 粤公网安备 44011302003646 号 | 点击查看十年之约
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式