转自:https://blog.csdn.net/DavidHuang2017/article/details/80283469

一、什么是加盐?

1.背景

现在很多公司后台以hash值形式存储用户密码(虽然本文以MD5哈希函数为例,但becrypt函数最常用的),用于哈希函数存在碰撞的特性,当后台数据库被攻击然后获取到用户密码哈希值时,还是能通过一定的方法(比如彩虹表攻击)破解用户密码。

举个例子:http://www.cmd5.com/

能破解。

2.加盐原理简介

简单来说:由原来的H(p)变成了H(p+salt),相当于哈希函数H发生了变化,每次哈希计算使用的salt是随机的

H如果发生了改变,则已有的彩虹表数据就完全无法使用,必须针对特定的H重新生成,这样就提高了破解的难度。

二、如何加盐?

如何加盐,不同的哈希算法不同的公司不尽相同但思路都是差不多的。本文以MD5的一个简单加盐处理为例,讲解加盐的java实现:

1.生成盐

private static char[] hex = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

/**
* @Author: DavidHuang
* @Time: 2018/5/10 21:14
* @return: salt
* @params:
* @Descrption: 自定义简单生成盐,是一个随机生成的长度为16的字符串,每一个字符是随机的十六进制字符
*/
public static String salt() {
Random random = new Random();
StringBuilder sb = new StringBuilder(16);
for (int i = 0; i < sb.capacity(); i++) {
sb.append(hex[random.nextInt(16)]);
}
return sb.toString();
}
        这只是一个生成盐的想法而已,你可以按自己的想法来,只要保证每次执行生成的盐随机即可。

2.输入加盐

String inputWithSalt = inputStr + salt;//加盐,输入加盐
       加盐非常简单吧

3.输出带盐

输出带盐是我自己取的一个名字而已,这个过程可选不要。实际上是将这次哈希计算过程用到的salt存储到这次hash值中,用于后面进行验证密码时进行hash计算,即注册存储密码时和登陆验证密码时用到的salt要一样,免除了另存hash操作。

/**
*@Author: DavidHuang
*@Time: 2018/5/11 14:47
*@return:
*@params: [inputStr, type] inputStr是输入的明文;type是处理类型,0表示注册存hash值到库时,1表示登录验证时
*@Descrption: MD5加盐,盐的获取分两种情况;输入明文加盐;输出密文带盐(将salt存储到hash值中)
*/
public static String MD5WithSalt(String inputStr, int type) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");//申明使用MD5算法,更改参数为"SHA"就是SHA算法了

String salt = null;
if (type == 0) {//注册存hash值到库时,new salt
salt = salt();
} else if (type == 1) {//登录验证时,使用从库中查找到的hash值提取出的salt
String queriedHash=null;//从库中查找到的hash值
salt=getSaltFromHash(queriedHash);
}

String inputWithSalt = inputStr + salt;//加盐,输入加盐
String hashResult = byte2HexStr(md.digest(inputWithSalt.getBytes()));//哈希计算,转换输出
System.out.println("加盐密文:"+hashResult);

/*将salt存储到hash值中,用于登陆验证密码时使用相同的盐*/
char[] cs = new char[48];
for (int i = 0; i < 48; i += 3) {
cs[i] = hashResult.charAt(i / 3 * 2);
cs[i + 1] = salt.charAt(i / 3);//输出带盐,存储盐到hash值中;每两个hash字符中间插入一个盐字符
cs[i + 2] = hashResult.charAt(i / 3 * 2 + 1);
}
hashResult = new String(cs);
return hashResult;
} catch (Exception e) {
e.printStackTrace();
return e.toString();
}
}
        将salt存到hash值的操作也很简单,假定输出hash值是32字节,我们生成的是16字节的盐,我们可以简单的每两个hash字符中间插入一个盐字符。带盐也很简单吧。
三、后台密码存储和验证过程

这里假定从前端传到后台的密码时明文。

1.注册时存储密码

(1)用户注册时输入的账号、密码p1从前端传到后台;

(2)后台随机生成一个salt;

(3)H(p1+salt)生成哈希值,将此哈希值带盐(存储salt)后的结果hash1存储到数据库中;

2.登录时验证密码

(1)用户登陆时输入的账号、密码p2从前端传到后台;
(2)用登陆账号在数据库中查询账号相同的记录,取其哈希值hash2

(3)从hash2获取salt(保证存储时和验证时salt相同)

(4)H(p2+salt)生成哈希值hash3,判断if(hash2==hash3);若相等登陆成功,否则登陆失败。

四、java实现

哈希函数选择MD5,javaApi中MD5没有加盐的过程,需要我们自己实现加盐。关于Becrypt的加盐更简单,可以看我Becrypt那篇博客的源码。

package EncryptAndDecrypt;

import java.security.MessageDigest;
import java.util.Random;

/**
* 散列加密之32位哈希值的MD5算法,调用JDK里的API
*ps:准确来说散列加密不是加密算法,因为它是不可逆的(只能加密,不能解密)
*/
public class MyMD5 {

private static char[] hex = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

public static void main(String[] args) throws Exception {
String input = "123456";
System.out.println("MD5加密" + "\n"
+ "明文:" + input + "\n"
+ "无盐密文:" + MD5WithoutSalt(input));
System.out.println("带盐密文:" + MD5WithSalt(input,0));
}

/**
*@Author: DavidHuang
*@Time: 2018/5/11 14:55
*@return:
*@params: [inputStr] 输入明文
*@Descrption: 不加盐MD5
*/
public static String MD5WithoutSalt(String inputStr) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");//申明使用MD5算法,更改参数为"SHA"就是SHA算法了
return byte2HexStr(md.digest(inputStr.getBytes()));//哈希计算,转换输出
} catch (Exception e) {
e.printStackTrace();
return e.toString();
}

}

/**
*@Author: DavidHuang
*@Time: 2018/5/11 14:47
*@return:
*@params: [inputStr, type] inputStr是输入的明文;type是处理类型,0表示注册存hash值到库时,1表示登录验证时
*@Descrption: MD5加盐,盐的获取分两种情况;输入明文加盐;输出密文带盐(将salt存储到hash值中)
*/
public static String MD5WithSalt(String inputStr, int type) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");//申明使用MD5算法,更改参数为"SHA"就是SHA算法了

String salt = null;
if (type == 0) {//注册存hash值到库时,new salt
salt = salt();
} else if (type == 1) {//登录验证时,使用从库中查找到的hash值提取出的salt
String queriedHash=null;//从库中查找到的hash值
salt=getSaltFromHash(queriedHash);
}

String inputWithSalt = inputStr + salt;//加盐,输入加盐
String hashResult = byte2HexStr(md.digest(inputWithSalt.getBytes()));//哈希计算,转换输出
System.out.println("加盐密文:"+hashResult);

/*将salt存储到hash值中,用于登陆验证密码时使用相同的盐*/
char[] cs = new char[48];
for (int i = 0; i < 48; i += 3) {
cs[i] = hashResult.charAt(i / 3 * 2);
cs[i + 1] = salt.charAt(i / 3);//输出带盐,存储盐到hash值中;每两个hash字符中间插入一个盐字符
cs[i + 2] = hashResult.charAt(i / 3 * 2 + 1);
}
hashResult = new String(cs);
return hashResult;
} catch (Exception e) {
e.printStackTrace();
return e.toString();
}
}

/**
* @Author: DavidHuang
* @Time: 2018/5/10 21:14
* @return: salt
* @params:
* @Descrption: 自定义简单生成盐,是一个随机生成的长度为16的字符串,每一个字符是随机的十六进制字符
*/
public static String salt() {
Random random = new Random();
StringBuilder sb = new StringBuilder(16);
for (int i = 0; i < sb.capacity(); i++) {
sb.append(hex[random.nextInt(16)]);
}
return sb.toString();
}

/**
* @Author: DavidHuang
* @Time: 2018/5/11 14:08
* @return: 十六进制字符串
* @params: [bytes]
* @Descrption: 将字节数组转换成十六进制字符串
*/
private static String byte2HexStr(byte[] bytes) {
/**
*@Author: DavidHuang
*@Time: 19:41 2018/5/10
*@return: java.lang.String
*@params: * @param bytes
*@Descrption:
*/
int len = bytes.length;
StringBuffer result = new StringBuffer();
for (int i = 0; i < len; i++) {
byte byte0 = bytes[i];
result.append(hex[byte0 >>> 4 & 0xf]);
result.append(hex[byte0 & 0xf]);
}
return result.toString();
}

/**
*@Author: DavidHuang
*@Time: 2018/5/11 14:32
*@return: 提取的salt
*@params: [hash] 3i byte带盐的hash值,带盐方法与MD5WithSalt中相同
*@Descrption: 从库中查找到的hash值提取出的salt
*/
public static String getSaltFromHash(String hash){
StringBuilder sb=new StringBuilder();
char [] h=hash.toCharArray();
for(int i=0;i<hash.length();i+=3){
sb.append(h[i+1]);
}
return sb.toString();
}

}
第一次运行结果:

MD5加密
明文:123456
无盐密文:E10ADC3949BA59ABBE56E057F20F883E
加盐密文:80D05C08F8879B2C84BA0C40143D224F
带盐密文:8D0D8053C0F8F6887791B2BC804B7A0EC4901543DD2B241F
第二运行结果:
MD5加密
明文:123456
无盐密文:E10ADC3949BA59ABBE56E057F20F883E
加盐密文:2CFFE57B054378D926A6FF14A1985F22
带盐密文:21CF7FE957CB0354C37B8DC9256AD6F0F174A719785AF222
        可以看到对于相同明文,多次MD5哈希的无盐密文相同,带盐密文和加盐密文不同。由哈希函数的特征很容易明白无盐密文相同。由于每次哈希计算生成的salt是随机的,相当于每次哈希函数不同,所以带盐密文和加盐密文不同。

由此可以看出加盐后安全性更高了吧。

参考:https://blog.csdn.net/dingsai88/article/details/51637977

https://blog.csdn.net/hao_hl1314/article/details/53141005
---------------------
作者:逍遥剑臣
来源:CSDN
原文:https://blog.csdn.net/DavidHuang2017/article/details/80283469
版权声明:本文为博主原创文章,转载请附上博文链接!

【密码学】轻松理解“加盐”的原理与java实现的更多相关文章

  1. 轻松理解webpack热更新原理

    一.前言 - webpack热更新 Hot Module Replacement,简称HMR,无需完全刷新整个页面的同时,更新模块.HMR的好处,在日常开发工作中体会颇深:节省宝贵的开发时间.提升开发 ...

  2. [Phoenix] 四、加盐表

    摘要: 在密码学中,加盐是指在散列之前将散列内容(例如:密码)的任意固定位置插入特定的字符串.这个在散列中加入字符串的方式称为“加盐”.其作用是让加盐后的散列结果和没有加盐的结果不相同,在不同的应用情 ...

  3. 轻松理解Redux原理及工作流程

    轻松理解Redux原理及工作流程 Redux由Dan Abramov在2015年创建的科技术语.是受2014年Facebook的Flux架构以及函数式编程语言Elm启发.很快,Redux因其简单易学体 ...

  4. [转]加盐hash保存密码的正确方式

    0x00 背景 大多数的web开发者都会遇到设计用户账号系统的需求.账号系统最重要的一个方面就是如何保护用户的密码.一些大公司的用户数据库泄露事件也时有发生,所以我们必须采取一些措施来保护用户的密码, ...

  5. [No0000132]正确使用密码加盐散列[译]

    如果你是一个 web 开发工程师,可能你已经建立了一个用户账户系统.一个用户账户系统最重要的部分是如何保护密码.用户账户数据库经常被黑,如果你的网站曾经被攻击过,你绝对必须做点什么来保护你的用户的密码 ...

  6. 看图轻松理解数据结构与算法系列(NoSQL存储-LSM树) - 全文

    <看图轻松理解数据结构和算法>,主要使用图片来描述常见的数据结构和算法,轻松阅读并理解掌握.本系列包括各种堆.各种队列.各种列表.各种树.各种图.各种排序等等几十篇的样子. 关于LSM树 ...

  7. 轻松理解 Java开发中的依赖注入(DI)和控制反转(IOC)

    前言 关于这个话题, 网上有很多文章,这里, 我希望通过最简单的话语与大家分享. 依赖注入和控制反转两个概念让很多初学这迷惑, 觉得玄之又玄,高深莫测. 这里想先说明两点: 依赖注入和控制反转不是高级 ...

  8. Flask_generate_password_hash的加盐哈希加密算法与check_password_hash的校验

    密码加密简介 密码存储的主要形式: 明文存储:肉眼就可以识别,没有任何安全性. 加密存储:通过一定的变换形式,使得密码原文不易被识别. 密码加密的几类方式: 明文转码加密算法:BASE64, 7BIT ...

  9. [diango]理解django视图工作原理

    前言:正确理解django视图view,模型model,模板的概念及其之间的关联关系,才能快速学习并上手使用django制作网页 本文主要讲解自己在学习django后对视图view的理解 在进入正文之 ...

随机推荐

  1. mysql数据库解决中文乱码的问题

    http://jingyan.baidu.com/article/647f0115937be97f2148a894.html 一个一劳永逸的方法, 修改mysql的配置文件my.ini 在这个配置文件 ...

  2. CentOS ln 链接

    ln -s /u01/app/oracle/product/11.2.0/dbhome_1/bin/sqlplus/ /usr/bin (解决bash: sqlplus: command not fo ...

  3. Python delattr() 函数

    Python delattr() 函数  Python 内置函数 描述 delattr 函数用于删除属性. delattr(x, 'foobar') 相等于 del x.foobar. 语法 dela ...

  4. django的优缺点(非原创)

    Django 大包大揽,用它来快速开发一些 Web 应用是不错的.如果你顺着 Django 的设计哲学来,你会觉得 Django 很好用,越用越顺手:相反,你如果不能融入或接受 Django 的设计哲 ...

  5. 对stm32f373XX的startup.s的文件的分析

    ;******************** (C) COPYRIGHT 2012 STMicroelectronics ********************;* File Name : start ...

  6. win静态库动态库

    静态链接库: #include "..\lib.h" #pragma comment(lib,"..\\debug\\libTest.lib") //指定与静态 ...

  7. freetype教程网址

    http://freetype.sourceforge.net/freetype2/docs/reference/ft2-system_interface.html#FT_Stream      ht ...

  8. web接口的开发

    老样子,抛出一个问题:什么是接口? 不解释了,百度吧. 了解起来先从HTTP开始说起吧. HTTP协议的特点: 1,无连接,就是指每次连接都仅仅只处理一个请求,服务器处理完客户的请求之后,收到客户的应 ...

  9. multithreading coding

    分类:公共资源问题.公交车问题 顺序:Qseater lab, bank, doctor [饭店] geust //yuec2 Yue Cheng package lab9; public abstr ...

  10. 43-将javaweb项目部署到Linux服务器

    这是第二次弄了,感觉由于上次积累了点资源,这次要少走很多弯路了,再次记录下来吧. 第一次的记录:将本地的javaweb项目部署到Linux服务器的一般操作 1. 在Linux上建立数据库,我是将本地的 ...