全局视角系统学习《推荐系统》,实战中提升竞争力

#1

download:全局视角系统学习《推荐系统》,实战中提升竞争力

非常安全的加密算法 Bcrypt

哈希(Hash)与加密(Encrypt)

哈希(Hash)是将目的文本转换成具有相同长度的、不可逆的杂凑字符串(或叫做音讯摘要),而加密(Encrypt)是将目的文本转换成具有不同长度的、可逆的密文。

哈希算法常常被设计成生成具有相同长度的文本,而加密算法生成的文本长度与明文自身的长度有关。

哈希算法是不可逆的,而加密算法是可逆的。

HASH 算法是一种音讯摘要算法,不是一种加密算法,但由于其单向运算,具有一定的不可逆性,成为加密算法中的一个构成局部。

JDK的String的Hash算法。代码如下:

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;

}

复制代码

从JDK的API能够看出,它的算法等式就是s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1],其中s[i]就是索引为i的字符,n为字符串的长度。

HashMap的hash计算时先计算hashCode(),然后停止二次hash。代码如下:

// 计算二次Hash

int hash = hash(key.hashCode());

static int hash(int h) {

h ^= (h >>> 20) ^ (h >>> 12);

return h ^ (h >>> 7) ^ (h >>> 4);

}

复制代码

能够发现,固然算法不同,但经过这些移位操作后,关于同一个值运用同一个算法,计算出来的hash值一定是相同的。

那么,hash为什么是不可逆的呢?

假设有两个密码3和4,我的加密算法很简单就是3+4,结果是7,但是经过7我不可能肯定那两个密码是3和4,有很多种组合,这就是最简单的不可逆,所以只能经过暴力破解一个一个的试。

在计算过程中原文的局部信息是丧失了。一个MD5理论上是能够对应多个原文的,由于MD5是有限多个而原文是无限多个的。

不可逆的MD5为什么是不平安的?

由于hash算法是固定的,所以同一个字符串计算出来的hash串是固定的,所以,能够采用如下的方式停止破解。

暴力枚举法:简单粗暴地枚举出一切原文,并计算出它们的哈希值,看看哪个哈希值和给定的信息摘要分歧。

字典法:黑客应用一个宏大的字典,存储尽可能多的原文和对应的哈希值。每次用给定的信息摘要查找字典,即可快速找到碰撞的结果。

彩虹表(rainbow)法:在字典法的根底上改良,以时间换空间。是如今破解哈希常用的方法。

关于单机来说,暴力枚举法的时间本钱很高(以14位字母和数字的组合密码为例,共有1.24×10^25种可能,即便电脑每秒钟能停止10亿次运算,也需求4亿年才干破解),字典法的空间本钱很高(仍以14位字母和数字的组合密码为例,生成的密码32位哈希串的对照表将占用5.7×10^14 TB的存储空间)。但是应用散布式计算和散布式存储,依然能够有效破解MD5算法。因而这两种办法同样被黑客们普遍运用。

如何防御彩虹表的破解?

固然彩虹表有着如此惊人的破解效率,但网站的平安人员依然有方法防御彩虹表。最有效的办法就是“加盐”,即在密码的特定位置插入特定的字符串,这个特定字符串就是“盐(Salt)”,加盐后的密码经过哈希加密得到的哈希串与加盐前的哈希串完整不同,黑客用彩虹表得到的密码基本就不是真正的密码。即便黑客晓得了“盐”的内容、加盐的位置,还需求对H函数和R函数停止修正,彩虹表也需求重重生成,因而加盐能大大增加应用彩虹表攻击的难度。

一个网站,假如加密算法和盐都泄露了,那针对性攻击仍然是十分不平安的。由于同一个加密算法同一个盐加密后的字符串依然还是一毛一样滴!

一个更难破解的加密算法Bcrypt

BCrypt是由Niels Provos和David Mazières设计的密码哈希函数,他是基于Blowfish密码而来的,并于1999年在USENIX上提出。

除了加盐来抵御rainbow table 攻击之外,bcrypt的一个十分重要的特征就是自顺应性,能够保证加密的速度在一个特定的范围内,即便计算机的运算才能十分高,能够经过增加迭代次数的方式,使得加密速度变慢,从而能够抵御暴力搜索攻击。

Bcrypt能够简单了解为它内部本人完成了随机加盐处置。运用Bcrypt,每次加密后的密文是不一样的。

对一个密码,Bcrypt每次生成的hash都不一样,那么它是如何停止校验的?

固然对同一个密码,每次生成的hash不一样,但是hash中包含了salt(hash产生过程:先随机生成salt,salt跟password停止hash);

在下次校验时,从hash中取出salt,salt跟password停止hash;得到的结果跟保管在DB中的hash停止比对。

在Spring Security 中 内置了Bcrypt加密算法,构建也很简单,代码如下:

@Bean

public PasswordEncoder passwordEncoder(){

return new BCryptPasswordEncoder();

}

复制代码

生成的加密字符串格式如下:

$2b$[cost]$[22 character salt][31 character hash]

复制代码

比方:

$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy

_// _/___________/

Alg Cost Salt Hash

复制代码

上面例子中,$2a$ 表示的hash算法的独一标志。这里表示的是Bcrypt算法。

10 表示的是代价因子,这里是2的10次方,也就是1024轮。

N9qo8uLOickgx2ZMRZoMye 是16个字节(128bits)的salt经过base64编码得到的22长度的字符。

最后的IjZAgcfl7p92ldGxad68LJZdL17lhWy是24个字节(192bits)的hash,经过bash64的编码得到的31长度的字符。

PasswordEncoder 接口

这个接口是Spring Security 内置的,如下:

public interface PasswordEncoder {

String encode(CharSequence rawPassword);

boolean matches(CharSequence rawPassword, String encodedPassword);

default boolean upgradeEncoding(String encodedPassword) {

  return false;

}

}

复制代码

这个接口有三个办法:

encode办法承受的参数是原始密码字符串,返回值是经过加密之后的hash值,hash值是不能被逆向解密的。这个办法通常在为系统添加用户,或者用户注册的时分运用。

matches办法是用来校验用户输入密码rawPassword,和加密后的hash值encodedPassword能否匹配。假如可以匹配返回true,表示用户输入的密码rawPassword是正确的,反之返回fasle。也就是说固然这个hash值不能被逆向解密,但是能够判别能否和原始密码匹配。这个办法通常在用户登录的时分停止用户输入密码的正确性校验。

upgradeEncoding设计的意图是,判别当前的密码能否需求晋级。也就是能否需求重新加密?需求的话返回true,不需求的话返回fasle。默许完成是返回false。

例如,我们能够经过如下示例代码在停止用户注册的时分加密存储用户密码

//将User保管到数据库表,该表包含password列

user.setPassword(passwordEncoder.encode(user.getPassword()));

复制代码

BCryptPasswordEncoder 是Spring Security引荐运用的PasswordEncoder接口完成类

public class PasswordEncoderTest {

@Test

void bCryptPasswordTest(){

PasswordEncoder passwordEncoder =  new BCryptPasswordEncoder();

String rawPassword = "123456";  //原始密码

String encodedPassword = passwordEncoder.encode(rawPassword); //加密后的密码

System.out.println("原始密码" + rawPassword);

System.out.println("加密之后的hash密码:" + encodedPassword);

System.out.println(rawPassword + "能否匹配" + encodedPassword + ":"   //密码校验:true

        + passwordEncoder.matches(rawPassword, encodedPassword));

System.out.println("654321能否匹配" + encodedPassword + ":"   //定义一个错误的密码停止校验:false

        + passwordEncoder.matches("654321", encodedPassword));

}

}

复制代码

上面的测试用例执行的结果是下面这样的。(留意:关于同一个原始密码,每次加密之后的hash密码都是不一样的,这正是BCryptPasswordEncoder的强大之处,它不只不能被破解,想经过常用密码对照表停止大海捞针你都无从下手),输出如下:

原始密码123456

加密之后的hash密码:$2a$10$zt6dUMTjNSyzINTGyiAgluna3mPm7qdgl26vj4tFpsFO6WlK5lXNm

123456能否匹配$2a$10$zt6dUMTjNSyzINTGyiAgluna3mPm7qdgl26vj4tFpsFO6WlK5lXNm:true

654321能否匹配$2a$10$zt6dUMTjNSyzINTGyiAgluna3mPm7qdgl26vj4tFpsFO6WlK5lXNm:false

复制代码

BCrypt 产生随机盐(盐的作用就是每次做出来的菜滋味都不一样)。这一点很重要,由于这意味着每次encode将产生不同的结果。