最早在大学的时候, 只知道用 MD5 来存用户的账号的密码, 但其实这非常不安全, 而所用到的哈希函数, 深入挖掘, 也发现并不简单......
一, 普通的 Hash 函数
哈希 (散列) 函数是什么就不赘述了.
1, 不推荐
RC4, MD4, MD5, SHA-0, SHA-1, DES, 2DES 等
2, 推荐
SHA-2(SHA-256, SHA-384, SHA-512),SHA-3,Blake2 等
美国国家标准和技术协会 (NIST) 宣布, 2010 年后开始逐步取消 SHA-1 作为安全哈希算法的资格, 取而代之的是其更强大的变异算法: SHA-224,SHA-256,SHA-384 和 SHA-512. 无论是否遵循 NIST 的标准, 至少使用 SHA-256 算法加密密码总是好的.
二, 应对普通哈希容易被破解的策略
就像攻与矛的互相增强, 哈希函数哪怕用到 SHA-3 以上, 都还是有被轻易破解的风险. 于是我们有其他额外的办法来解决这个问题.
1, 加盐(salt)
加盐就是对目标字段哈希前, 拼接上另一个字段(salt).
注: 盐值加到字段之前较为普遍.
加盐对防彩虹表很有效.
注意点:
盐不能太短
盐不能重复使用(否则一破解, 所有的都遭殃)
盐随机变化(例如, 虽用户名不重复, 但用户名不能拿来当盐)
盐的本质是将无差别攻击转化为针对性攻击.
1.1,[拓展] 针对 salt 的另一种做法 -- HMAC
HMAC(Keyed-Hashing for Message Authentication)其实也是一种特殊的加盐, 只是这个 salt 用更安全的密钥代替了.
具体介绍可以看我之前一篇:《破解另一家网站的反爬机制 & HMAC 算法》
2, 慢哈希
高端的显卡 (GPU) 和定制的硬件可以每秒进行数十亿次哈希计算, 因此这类攻击依然可以很高效. 为了降低攻击者的效率, 我们可以使用慢哈希, 即迭代进行很多次哈希运算.
那么迭代多少次比较安全呢? 来自 NIST 官方的建议:
2000 年 9 月, 建议迭代一千次
2015 年 - 2018 年, 建议迭代一万次
2017 年 6 月, 建议迭代十万次
三, 密码哈希函数(Password Hash)
密码哈希函数 (Password Hash) 可以用来应对普通哈希容易被破解的问题(也用到了上面所提到的两个策略).
下面列举的顺序是按照时间顺序, 安全程度和推荐指数也逐级递增.
1,PBKDF2
比较老, 很少有人用了, 略.
2,Bcrypt
这是我司目前用的.(不过有过时的隐患, 建议换掉)
(1)介绍
bcrypt 是由 Niels Provos 和 DavidMazières 基于 Blowfish 密码设计的密码哈希函数, 于 1999 年在 USENIX 上提出.
bcrypt 函数是 OpenBSD 和其他系统 (包括某些 Linux 发行版, 例如 SUSE Linux) 的默认密码哈希算法.
(2)使用(Node.JS)
安装: NPM i bcryptjs
bcryptjs 跟 C++ 的 bcrypt 兼容, 但因为是纯 JavaScript 编写的, 因此速度较慢(约 30%).
用法:
Sync 方法(Async 方法略):
- const bcryptjs = require('bcryptjs');
- // 1, 生成 安全因子
- const salt = bcrypt.genSaltSync(10);
- // 2, 执行 哈希函数
- const password = bcryptjs.hashSync(plainPassword, bcryptjs.genSaltSync(salt));
- // 另一种方法: 快速执行
- const SALT_FACTOR = 10;
- const password = bcryptjs.hashSync(plainPassword, bcryptjs.genSaltSync(SALT_FACTOR));
- // 3, 比较是否相等
- bcryptjs.compareSync(plainPassword, password);
注: 代码里出现的安全因子, 值的大小决定了哈希函数会有多慢.(即慢哈希)
3,Scrypt
没用过, 略.
4,Argon2
(1)介绍
2013 年 NIST(美国国家标准与技术研究院)邀请了一些密码学家一起, 举办了密码哈希竞赛 PHC(Password Hashing Competition).Argon2 在 2015 年 7 月赢得了冠军.
大赛列出了参赛算法可能面临的攻击手段:
哈希算法破解(原值还原, 哈希碰撞等);
查询表 / 彩虹表攻击;
CPU 优化攻击;
GPU,FPGA,ASIC 等专用硬件攻击;
旁路攻击;
(2)使用(Node.JS)
1, 准备
- You can skip this section if the prebuilt binaries work for you.
- You MUST have a node-gyp global install before proceeding with install, along with GCC>= 5 / Clang>= 3.3. On Windows, you must compile under Visual Studio 2015 or newer.
- node-argon2 works only and is tested against Node>=10.0.0.
- ---
- OS X
- To install GCC>= 5 on OS X, use homebrew:
- $ brew install gcc
- Once you've got GCC installed and ready to run, you then need to install node-gyp, you must do this globally:
- $ NPM install -g node-gyp
- Finally, once node-gyp is installed and ready to go, you can install this library, specifying the GCC or Clang binary to use:
- $ CXX=g++-6 NPM install argon2
- NOTE: If your GCC or Clang binary is named something different than g++-6, you'll need to specify that in the command.
2, 安装
NPM i argon2
3, 使用
- const argon2 = require('argon2');
- (async () => {
- try {
- // const hash = await argon2.hash("password");
- // 更多选项(以下都是默认值)
- const hash = await argon2.hash("password", {
- type: argon2.argon2i,
- hashLength: 32, // 哈希函数输出的字节长度(请注意, 生成的哈希是使用 Base64 编码的, 因此长度将增加约 1/3)
- timeCost : 3, // 时间成本是哈希函数使用的通过次数(迭代次数)
- memoryCost: 2 ** 16, // 默认 4096(单位 KB, 即 4MB)
- parallelism :1, // 用于计算哈希值的线程数量. 每个线程都有一个具有 memoryCost 大小的内存池
- })
- console.log("hash", hash)
- const is = await argon2.verify(hash, "password")
- console.log("is", is) // true
- } catch (err) {
- console.error("err", err)
- }
- })()
(3)参数
1,type:
argon2d 更快且对 GPU 攻击具有高度抵抗力, 这对于加密货币很有用
argon2i 速度较慢且可以抵御权衡攻击, 因此首选用于密码哈希和密钥派生
argon2id 是上述内容的混合组合, 可以抵抗 GPU 和权衡攻击
因为我们是用于密码的 hash, 用默认的 argon2i 即可.
2,(慢)哈希相关参数
1 memoryCost 内存开销, 它定义了内存的使用情况
好的起点是 0.75 *(RAM / number_of_users) 起步.
2 parallelism 并行程度, 它定义了线程的数量
最佳起点是内核数.
3 timeCost 时间开销, 它定义了执行的时间
建议在系统上运行它, 并确定与内存和处理器使用时间限制相匹配的最大参数.
如前所述, 本质是在安全性和可用性之间取得平衡.
3, 其他参数
salt: 默认值是未设置, 将生成加密安全的随机盐.
saltLength: 默认 16.
version: 您不应更改此设置, 因为最新版本更强大.
5, 密码哈希是如何解决普通哈希容易被破解的问题
上面介绍了 二, 应对普通哈希容易被破解的策略 , 我们可以看看密码哈希是如何运用并符合这些策略的.
(1)针对 salt
密码哈希使用 CSPRNG(Cryptographically Secure Pseudo-Random Number Generator)密码学安全伪随机数生成器生成盐.
CSPRNG 是加密安全 (Cryptographically Secure) 的,(加密安全的意思即)意味着用它产生的随机数更加随机, 且不可预测.
普通的计算机随机数算法并不是很随机.
注: 盐值本身就在存在于哈希后的字符串中(其实还可能包括版本, 慢哈希迭代次数等), 当调用跟明文比对的方法时, 模块内部会提取出盐值进行验证.
(2)针对 慢哈希
Bcryoy 的安全因子和 Argon2 的 timeCost 参数, 都是针对慢哈希的配置.
6, 结论
我司使用的 Bcrypt 其实在今年(2020 年), 已经不安全了, 推荐至少使用 Scrypt, 有条件上 Argon2.
四, 常见问题
问 1: 我用我自己实现哈希算法, 不用公开现成的, 越古怪越好, 坏人不就猜不到了吗?
答: 不建议.
首先介绍下密码学上的柯克霍夫原则 (Kerckhoffs's principle, 也称为柯克霍夫假说, 公理, 或定律), 由奥古斯特. 柯克霍夫在 19 世纪提出: 即使密码系统的任何细节已为人悉知, 只要密匙(key, 又称密钥或秘钥) 未泄漏, 它也应是安全的. 信息论的发明者克劳德. 香农则改成说:" 敌人了解系统 ", 这样的说法则称为香农箴言.
基于这个原则:
1, 你自己实现的再古怪, 毕竟你不是密码专家, 很难确保不被坏人破解(可能自己实现后看似复杂, 实际更容易破解了).
2, 如果自己包含哈希算法的代码泄露, 它很脆弱, 难保不会被坏人破解.
问 2: 既然现在都是 https, 前端传给后端的明文密码, 就懒得加哈希了, 可以吗?
还是建议前端也进行哈希(虽然前端的哈希算法容易暴露). 不要漏掉任何一个环节.
五, 实操
Dropbox 公司曾公开分享过自己对用户账号的密码加密的策略, 使用了三层加密:
1,password
即明文密码.
2,SHA512
在 bcrypt 前做 SHA512, 是因为有些 bcrypt 实现会把散列值长度截至 72 字节, 从而降低了密码的熵值, 而有的则允许变长密码, 这样容易受到 DoS 攻击. 使用 SHA512 散列可以得到固定长度的 512 字节散列值, 避免了上述的两个问题.
"而有的则允许变长密码, 这样容易受到 DoS 攻击", 这句话我不是很理解, 待写.
3,bcrypt
上面说过, 不赘述了.
4,AES256
AES256 会用到密钥, 俗称胡椒粉(pepper). 密钥需要被单独存储, 最好存储在外部系统: 如物理上隔离的服务端, 甚至特殊的硬件设备(如 YubiHSM) .
这里的 AES256 也可以用 HMAC 代替. 不过前者安全性更好些.
来源: https://www.cnblogs.com/xjnotxj/p/12716981.html