密码学系列之:bcrypt加密算法详解
简介
今天要给大家介绍的一种加密算法叫做bcrypt, bcrypt是由Niels Provos和David Mazières设计的密码哈希函数,他是基于Blowfish密码而来的,并于1999年在USENIX上提出。
除了加盐来抵御rainbow table 攻击之外,bcrypt的一个非常重要的特征就是自适应性,可以保证加密的速度在一个特定的范围内,即使计算机的运算能力非常高,可以通过增加迭代次数的方式,使得加密速度变慢,从而可以抵御暴力搜索攻击。
bcrypt函数是OpenBSD和其他系统包括一些Linux发行版(如SUSE Linux)的默认密码哈希算法。
bcrypt的工作原理
我们先回顾一下Blowfish的加密原理。 blowfish首先需要生成用于加密使用的K数组和S-box, blowfish在生成最终的K数组和S-box需要耗费一定的时间,每个新的密钥都需要进行大概4 KB文本的预处理,和其他分组密码算法相比,这个会很慢。但是一旦生成完毕,或者说密钥不变的情况下,blowfish还是很快速的一种分组加密方法。
那么慢有没有好处呢?
当然有,因为对于一个正常应用来说,是不会经常更换密钥的。所以预处理只会生成一次。在后面使用的时候就会很快了。
而对于恶意攻击者来说,每次尝试新的密钥都需要进行漫长的预处理,所以对攻击者来说要破解blowfish算法是非常不划算的。所以blowfish是可以抵御字典攻击的。
Provos和Mazières利用了这一点,并将其进一步发展。他们为Blowfish开发了一种新的密钥设置算法,将由此产生的密码称为 "Eksblowfish"("expensive key schedule Blowfish")。这是对Blowfish的改进算法,在bcrypt的初始密钥设置中,salt 和 password 都被用来设置子密钥。然后经过一轮轮的标准Blowfish算法,通过交替使用salt 和 password作为key,每一轮都依赖上一轮子密钥的状态。虽然从理论上来说,bcrypt算法的强度并不比blowfish更好,但是因为在bcrpyt中重置key的轮数是可以配置的,所以可以通过增加轮数来更好的抵御暴力攻击。
bcrypt算法实现
简单点说bcrypt算法就是对字符串OrpheanBeholderScryDoubt 进行64次blowfish加密得到的结果。有朋友会问了,bcrypt不是用来对密码进行加密的吗?怎么加密的是一个字符串?
别急,bcrpyt是将密码作为对该字符串加密的因子,同样也得到了加密的效果。我们看下bcrypt的基本算法实现:
Function bcrypt
Input:
cost: Number (4..31) log2(Iterations). e.g. 12 ==> 212 = 4,096 iterations
salt: array of Bytes (16 bytes) random salt
password: array of Bytes (1..72 bytes) UTF-8 encoded password
Output:
hash: array of Bytes (24 bytes)
//Initialize Blowfish state with expensive key setup algorithm
//P: array of 18 subkeys (UInt32[18])
//S: Four substitution boxes (S-boxes), S0...S3. Each S-box is 1,024 bytes (UInt32[256])
P, S <- EksBlowfishSetup(cost, salt, password)
//Repeatedly encrypt the text "OrpheanBeholderScryDoubt" 64 times
ctext <- "OrpheanBeholderScryDoubt" //24 bytes ==> three 64-bit blocks
repeat (64)
ctext <- EncryptECB(P, S, ctext) //encrypt using standard Blowfish in ECB mode
//24-byte ctext is resulting password hash
return Concatenate(cost, salt, ctext)
上述函数bcrypt 有3个输入和1个输出。
在输入部分,cost 表示的是轮循的次数,这个我们可以自己指定,轮循次数多加密就慢。
salt 是加密用盐,用来混淆密码使用。
password 就是我们要加密的密码了。
最后的输出是加密后的结果hash。
有了3个输入,我们会调用EksBlowfishSetup函数去初始化18个subkeys和4个1K大小的S-boxes,从而达到最终的P和S。
然后使用P和S对"OrpheanBeholderScryDoubt" 进行64次blowfish运算,最终得到结果。
接下来看下 EksBlowfishSetup方法的算法实现:
Function EksBlowfishSetup
Input:
password: array of Bytes (1..72 bytes) UTF-8 encoded password
salt: array of Bytes (16 bytes) random salt
cost: Number (4..31) log2(Iterations). e.g. 12 ==> 212 = 4,096 iterations
Output:
P: array of UInt32 array of 18 per-round subkeys
S1..S4: array of UInt32 array of four SBoxes; each SBox is 256 UInt32 (i.e. 1024 KB)
//Initialize P (Subkeys), and S (Substitution boxes) with the hex digits of pi
P, S <- InitialState()
//Permutate P and S based on the password and salt
P, S <- ExpandKey(P, S, salt, password)
//This is the "Expensive" part of the "Expensive Key Setup".
//Otherwise the key setup is identical to Blowfish.
repeat (2cost)
P, S <- ExpandKey(P, S, 0, password)
P, S <- ExpandKey(P, S, 0, salt)
return P, S
代码很简单,EksBlowfishSetup 接收上面我们的3个参数,返回最终的包含18个子key的P和4个1k大小的Sbox。
首先初始化,得到最初的P和S。
然后调用ExpandKey,传入salt和password,生成第一轮的P和S。
然后循环2的cost方次,轮流使用password和salt作为参数去生成P和S,最后返回。
最后看一下ExpandKey的实现:
Function ExpandKey
Input:
password: array of Bytes (1..72 bytes) UTF-8 encoded password
salt: Byte[16] random salt
P: array of UInt32 Array of 18 subkeys
S1..S4: UInt32[1024] Four 1 KB SBoxes
Output:
P: array of UInt32 Array of 18 per-round subkeys
S1..S4: UInt32[1024] Four 1 KB SBoxes
//Mix password into the P subkeys array
for n <- 1 to 18 do
Pn <- Pn xor password[32(n-1)..32n-1] //treat the password as cyclic
//Treat the 128-bit salt as two 64-bit halves (the Blowfish block size).
saltHalf[0] <- salt[0..63] //Lower 64-bits of salt
saltHalf[1] <- salt[64..127] //Upper 64-bits of salt
//Initialize an 8-byte (64-bit) buffer with all zeros.
block <- 0
//Mix internal state into P-boxes
for n <- 1 to 9 do
//xor 64-bit block with a 64-bit salt half
block <- block xor saltHalf[(n-1) mod 2] //each iteration alternating between saltHalf[0], and saltHalf[1]
//encrypt block using current key schedule
block <- Encrypt(P, S, block)
P2n <- block[0..31] //lower 32-bits of block
P2n+1 <- block[32..63] //upper 32-bits block
//Mix encrypted state into the internal S-boxes of state
for i <- 1 to 4 do
for n <- 0 to 127 do
block <- Encrypt(state, block xor salt[64(n-1)..64n-1]) //as above
Si[2n] <- block[0..31] //lower 32-bits
Si[2n+1] <- block[32..63] //upper 32-bits
return state
ExpandKey主要用来生成P和S,算法的生成比较复杂,大家感兴趣的可以详细研究一下。
bcrypt hash的结构
我们可以使用bcrypt来加密密码,最终以bcrypt hash的形式保存到系统中,一个bcrypt hash的格式如下:
$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长度的字符。
hash的历史
这种hash格式是遵循的是OpenBSD密码文件中存储密码时使用的Modular Crypt Format格式。最开始的时候格式定义是下面的:
$1$
: MD5-based crypt ('md5crypt')$2$
: Blowfish-based crypt ('bcrypt')$sha1$
: SHA-1-based crypt ('sha1crypt')$5$
: SHA-256-based crypt ('sha256crypt')$6$
: SHA-512-based crypt ('sha512crypt')
但是最初的规范没有定义如何处理非ASCII字符,也没有定义如何处理null终止符。修订后的规范规定,在hash字符串时:
- String 必须是UTF-8编码
- 必须包含null终止符
因为包含了这些改动,所以bcrypt的版本号被修改成了 $2a$
。
但是在2011年6月,因为PHP对bcypt的实现 crypt_blowfish 中的一个bug,他们建议系统管理员更新他们现有的密码数据库,用$2x$
代替$2a$
,以表明这些哈希值是坏的(需要使用旧的算法)。他们还建议让crypt_blowfish对新算法生成的哈希值使用头$2y$
。 当然这个改动只限于PHP的crypt_blowfish。
然后在2014年2月,在OpenBSD的bcrypt实现中也发现了一个bug,他们将字符串的长度存储在无符号char中(即8位Byte)。如果密码的长度超过255个字符,就会溢出来。
因为bcrypt是为OpenBSD创建的。所以当他们的库中出现了一个bug时, 他们决定将版本号升级到$2b$
。
本文已收录于 http://www.flydean.com/37-bcrypt/
最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!
密码学系列之:bcrypt加密算法详解的更多相关文章
- 密码学系列之:Argon2加密算法详解
目录 简介 密钥推导函数key derivation function Password Hashing Competition Argon2算法 Argon2的输入参数 处理流程 简介 Argon2 ...
- ASP.NET MVC深入浅出系列(持续更新) ORM系列之Entity FrameWork详解(持续更新) 第十六节:语法总结(3)(C#6.0和C#7.0新语法) 第三节:深度剖析各类数据结构(Array、List、Queue、Stack)及线程安全问题和yeild关键字 各种通讯连接方式 设计模式篇 第十二节: 总结Quartz.Net几种部署模式(IIS、Exe、服务部署【借
ASP.NET MVC深入浅出系列(持续更新) 一. ASP.NET体系 从事.Net开发以来,最先接触的Web开发框架是Asp.Net WebForm,该框架高度封装,为了隐藏Http的无状态模 ...
- Hexo系列(三) 常用命令详解
Hexo 框架可以帮助我们快速创建一个属于自己的博客网站,熟悉 Hexo 框架提供的命令有利于我们管理博客 1.hexo init hexo init 命令用于初始化本地文件夹为网站的根目录 $ he ...
- Signalr系列之虚拟目录详解与应用中的CDN加速实战
目录 对SignalR不了解的人可以直接移步下面的目录 SignalR系列目录 前言 前段时间一直有人问我 在用SignalR 2.0开发客服系统[系列1:实现群发通讯]这篇文章中的"/Si ...
- 转载爱哥自定义View系列--文字详解
FontMetrics FontMetrics意为字体测量,这么一说大家是不是瞬间感受到了这玩意的重要性?那这东西有什么用呢?我们通过源码追踪进去可以看到FontMetrics其实是Paint的一个内 ...
- 转载爱哥自定义View系列--Paint详解
上图是paint中的各种set方法 这些属性大多我们都可以见名知意,很好理解,即便如此,哥还是带大家过一遍逐个剖析其用法,其中会不定穿插各种绘图类比如Canvas.Xfermode.ColorFilt ...
- kubernetes系列07—Pod控制器详解
本文收录在容器技术学习系列文章总目录 1.Pod控制器 1.1 介绍 Pod控制器是用于实现管理pod的中间层,确保pod资源符合预期的状态,pod的资源出现故障时,会尝试 进行重启,当根据重启策略无 ...
- Cobaltstrike系列教程(三)-beacon详解
0x000--前文 Cobaltstrike系列教程(一)-简介与安装 Cobaltstrike系列教程(二)-Listner与Payload生成 heatlevel 0x001-Beacon详解 1 ...
- CSS系列 (05):浮动详解
浮动的框可以向左或向右移动,直到它的外边缘碰到包含框或另一个浮动框的边框为止.由于浮动框不在文档的普通流中,所以文档的普通流中的块框表现得就像浮动框不存在一样. -- W3C 文字环绕 float可以 ...
随机推荐
- 配置SSH公钥以及创建远程仓库
一.配置SSH公钥 1.生成SSH公钥 在我们自己电脑的桌面上右键菜单,打开git命令行,输入以下命令: ssh-keygen -t rsa 一直敲回车之后,显示以下信息即表示成功生成SSH公钥,并且 ...
- 常见的嵌入式linux学习和如何选择ARM芯片问答
常见的ARM嵌入式学习问答,设计者和学习者最关心的11个问题: 1. ARM嵌入式是学习硬件好还是学习软件好? 2. 嵌入式软件和硬件,哪一种职位待遇更高?或者说, ...
- Send Excerpts from Jenkins Console Output as Email Contents
Sometimes we need to send some excerpts from Jenkins console output (job logs) as email, such as tes ...
- Redis配置及攻击利用
Redis配置及攻击利用 Redis及其安全配置 Redis介绍 redis默认会绑定在 0.0.0.0:6379,如果没有进行采用相关的策略,比如添加防火墙规则避免其他非信任来源 ip 访问等,这样 ...
- 题解 big
传送门 题面里那个式子 考场上我推了半天那个式子代表什么意思,但就是没想到位运算 \(\lfloor \frac{2x}{2x^n} \rfloor \iff x\gg(n-1)\), 即将x的第n位 ...
- 黑马JVM教程——自学笔记(二)
三.垃圾回收 3.1.如何判断对象可以回收 3.1.1 引用计数法 弊端:循环引用时,两个对象的计数都为1,导致两个对象都无法被释放 3.1.2 可达性分析算法 JVM中的垃圾回收器通过可达性分析来探 ...
- 【AIOT】智能感知--物
From: https://liudongdong1.github.io/ 1. 物体检测 .1. 流体 D. V. Q. Rodrigues, D. Rodriguez and C. Li, &qu ...
- Thread类中yield方法
Yield方法可以暂停当前正在执行的线程对象,让其他有相同优先级的线程执行.它是一个静态方法而且只保证当前线程放弃CPU占用而不能保证其它线程一定能占用CPU,执行yield()的线程有可能在进入到暂 ...
- C# prism 框架
定义Region (RegionManager) 定义Region 的方式有两种,一个是在XMAL界面指定,另一种这是代码当中指定. RegionManager.RegionName(XMAL) Re ...
- nodejs 更改项目端口号的 方法
我这里是 koa2 项目 1.项目目录 serverConf.js 这里面配置端口 代码如下: const ServerConf= { ServicePort: 3036 }; module.expo ...