BCrypt详解
引言
引言
如果你对密码学入门感兴趣,那就来对地方了。也许你刚开始接触 Rails,想添加用户登录/注销功能。也许你对加密哈希算法非常感兴趣。我就是!如果你是真正的初学者,可汗学院(Khan Academy)这个网站有一些非常棒的密码学视频。
这其实不算教程,但是使用 BCrypt 时,请务必记得在 Rails Gemfile 中取消注释该 gem。
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby '2.6.1'
...
# Use Active Model has_secure_password
# gem 'bcrypt', '~> 3.1.7'
gem 'bcrypt', '~> 3.1.7'
...
现在,你可以做像这样很酷的事情:
# Encrypts password
my_password = BCrypt::Password.create('iLOVEdogs123')
=> "$2a$10$kypbnGGCpJ7UQlysnqzJG.6H.dUewn7UPVWA3Ip.E.8U4jlVnFNnu"
# Tests if input matches
my_password == 'iLOVEdogs123'
=> true
my_password == 'ilovedogs12'
=> false
# Not super important for this post, but this is why the above crypt begins with "$2a$"
my_password.version
=> "2a"
# "Cost" factor - how quickly the password is encrypted
my_password.cost
=> 10
现在,你可以使用 BCrypt 为你的应用实现很多功能——身份验证、授权、登录、注销等等!但是已经有很多博客文章和文档详细介绍了如何编写这些代码。我更感兴趣的是幕后实现。
历史
BCrypt 是一种哈希算法,由 OpenBSD 项目的 Niels Provos 和 David Mazières 于 1999 年设计。B 代表……
河豚!
Blowfish是一种对称密钥分组密码,由 Bruce Schneier 于 1993 年设计。现在有一个更现代的版本叫做 Twofish,但我们在这里并不关心它!
令人惊讶的是,他们20 多年前提出的最初方案名为“面向未来的密码方案”,因为它似乎真正经受住了时间的考验。
他们设想了一种算法,其计算成本会随着硬件的改进而增加。或者说,随着计算机速度的提升和密码破解能力的增强,加密速度应该变慢,或者说成本更高。请注意下一行:
pw = BCrypt::Password.create('password123')
=> "$2a$10$kypbnGGCpJ7UQlysnqzJG.6H.dUewn7UPVWA3Ip.E.8U4jlVnFNnu"
pw.cost
=> 10
cost是 10。如果成本增加,速度会降低,但黑客猜测你密码的速度也会降低。例如,攻击者使用 Ruby 编写的 MD5 算法每秒可以检查约 14 万个密码,而使用bcryptBCrypt 则每秒只能检查约 450 个密码。BCrypt 允许你根据速度/安全性权衡的重要性来配置成本。这里有一个不错的视频,展示了一些不同成本因素的示例。
哈希函数概述
一般来说,哈希算法或函数接收数据(例如密码),并将其映射到“固定长度的值”,或者说创建其“数字指纹”或哈希值。这个哈希值与 Ruby类并不完全相同,但它们很相似。哈希算法就像密码及其加密值的键值对,但你肯定不会想以这种方式存储或保存它们!这个过程永远不是真正意义上的“可逆的”,也就是说,如果我对一个密码列表进行哈希处理,而你得到的只是一系列唯一的加密值,那么你“破解”我的密码的唯一方法就是暴力破解。但你永远无法将哈希值还原成它的原始形式!
这就是为什么模运算和XOR门/运算符对于密码学以及理解 BCrypt 算法背后的奥秘如此重要。` XOR\include` 代表“异或”,它表示 A 和 B 之间的逻辑关系,其中只有一个必须为真。因此,A XOR B当 A 为真或 B 为真时,`\include` 返回真,但不能两者XOR都为真。如果输入为真的数量为奇数,则 `\include` 门返回真或 1,因此它也被认为是 `\include` addition mod 2。对我来说,模运算是理解为什么所有现代密码学、哈希、加密等都是“不可逆”的最清晰方式——12 mod 7 = 5即使40 mod 7 = 5你知道输出是 5,并且算法是`\include` mod 7,你也无法通过任何数学方法(除了猜测)确定原始数字。
这些都是BCrypt背后的基本理念,而BCrypt本身显然要复杂得多。但令我感兴趣的是,BCrypt在很大程度上是在这些理念的基础上发展而来的,而其他加密技术却没有这样做。
例如,一些常见的“通用”哈希函数,如MD5、SHA1、SHA2和SHA3,速度很快,但安全性不高。一台现代服务器每秒可以计算大约330MB数据的MD5哈希值。对于小写字母、数字组合的6位密码,大约需要40秒才能尝试所有可能的哈希值组合。
那么 BCrypt 是如何利用所有这些信息的呢?
BCrypt 实际上并不会保存或存储这些哈希值。它实际上是对所有输入的内容进行哈希或加密,然后比较哈希值。如果我将密码保存为“password123”,然后在 Rails 应用的登录页面输入“password”,BCrypt 会分别对这两个字符串进行哈希处理,并比较它们以进行身份验证。例如,当我将密码保存为“password123”时,BCrypt 会执行以下操作:
pw = BCrypt::Password.create('password123')
=> "$2a$10$kypbnGGCpJ7UQlysnqzJG.6H.dUewn7UPVWA3Ip.E.8U4jlVnFNnu"
如果我输入了错误的密码,例如只输入了“password”,它会计算一个单独的哈希值并与它进行比较。
not_pw = BCrypt::Password.create('password')
=> "$2a$10$vx6htugaV4KRG2ucXc8iHOo/Ch4FRfM7aa6Tpc79j9ecPo9U6APsu"
最终使得 BCrypt 能够进行比较:
pw == 'password123'
=> true
pw == 'password'
=> false
你永远不可能从那两个很长的密码开始$2a$10$往回推算。这听起来似乎显而易见,但仔细想想却很有意思——这不仅仅是因为你是人而不是电脑,所以你无法往回推算。任何人,任何电脑,甚至全能全知的存在,都无法做到!BCrypt 根本不可能拥有任何函数或方法,能够接收一个哈希密码,然后以某种方式将其逆向还原并返回原始密码。它所能做的只是比较哈希值。
但对黑客来说是个好消息,对你来说却是个坏消息——黑客在这里不需要全知全能,也不需要精通数学。事实上,理论上,暴力破解总是有效的。试想一下,如果我们拥有无限的时间和资源,那么可能任何东西都能被破解。所以,如果你感到迷茫,为自己的死亡感到悲伤,至少要知道,这意味着你没那么容易被黑客攻击。
彩虹桌
但黑客仍然可以采取很多比简单的暴力破解攻击更明智的策略来利用时间。彩虹表攻击使用一个包含所有可能密码的列表,并用相同的算法对其进行哈希处理,然后进行比较。因此,理论上你可以创建一个常用密码列表,使用类似 BCrypt 的算法对其进行哈希处理,然后将这些哈希值与实际的密码哈希列表进行比较。解决这个问题的方法——加盐!某种程度上来说是这样。
狡猾的盐!
关于盐值(salt),我们可以玩很多文字游戏,但玩笑归玩笑,盐值其实就是“用作单向哈希函数额外输入的随机数据”。盐值会为我们原本就相当随机的加密结果再增加一层随机性。所以,根据bcryptRuby gem 的文档,我们有类似这样的情况:
hash(password) #=> <unique gibberish>
hash(salt + password) #=> <really unique gibberish>
还有辣椒的概念,这同样是为了双关语而设的,但在 BCrypt 中并没有明确使用。
但是!单靠盐值是不够的。盐值有助于防止彩虹表攻击,但未必能防止字典攻击或暴力破解攻击。攻击者可以将一系列潜在密码逐个尝试破解:
hash(salt + "aadvark") =? <really unique gibberish>
hash(salt + "abacus") =? <really unique gibberish>
BCrypt 真正的革命性之处在于它的速度很慢!哈希算法通常并非设计成慢的,而是旨在尽可能快地将大量数据转换为安全的指纹。此外,BCrypt 令人印象深刻之处在于其真正的适应性——工作因子功能允许您确定哈希算法的“成本”、“效率”或“速度”。因此,随着硬件速度的提升,您可以降低 BCrypt 的速度,从而跟上摩尔定律的步伐!
在我之前的例子中,默认的工作量或成本设置为 10:
pw = BCrypt::Password.create('password123')
=> "$2a$10$kypbnGGCpJ7UQlysnqzJG.6H.dUewn7UPVWA3Ip.E.8U4jlVnFNnu"
pw.cost
=> 10
BCrypt 使用 12 的工作因子,yaaa大约 0.3 秒即可完成密码哈希运算。MD5 算法耗时不到 1 微秒。这看似微不足道,但想想看,破解密码的速度是每 40 秒一次和每 12 年一次之间的差别。您可能并不需要如此高的安全性,而需要更快的算法——BCrypt 的优势就在于它允许您在速度和安全性之间自由选择平衡点。
最后一条教程说明
如果您需要更改默认成本因子,文档中是这样描述的:可以使用以下方法,BCrypt::Engine.cost = new_value或者在创建时指定:cost为附加参数。
选项 1:
BCrypt::Engine.cost = 8
BCrypt::Password.create('password').cost
#=> 8
选项 2:
BCrypt::Password.create('password', :cost => 6).cost #=> 6
结论
BCrypt既酷炫又安全。除非你对速度、安全性和余额有非常特殊且高级的需求,否则一定要爱上它!


