密码在服务器一般不能明文存储,所以这里就涉及到加密处理的问题。
我们采用hash算法对密码进行加密后存储在数据库中,
Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(又叫做预哈希, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。具有单向的特点。
这里我们总结出hash对于我们密码加密非常有用的两个特性:
1 不可逆,即hash过程过程不可逆,你不可以通过hash后的值得到原值。
2 无冲突,你知道一个值(x),但无法求出另一个值(y),使这两个值得hash值相同。
以上性质对于对于密码加密而言就是
(1)原始密码经哈希函数计算后得到一个哈希值
(2)改变原始密码,哈希函数计算出的哈希值也会相应改变
(3) 同样的密码,哈希值也是相同的
(4) 哈希函数是单向、不可逆的。也就是说从哈希值,你无法推算出原始的密码是多少
###1.2 常用的hash实现
MD5:它对输入仍以512位分组,其输出是4个32位字的级联
SHA-1 SHA256等:它对长度小于264的输入,产生长度为160bit的散列值
密码中混入一段“随机”的字符串再进行哈希加密,这个被字符串被称作盐值。
为什么要加盐:同一个密码经过哈希算法后得到的密码是一致,攻击者可以通过建立一个密码和哈希机密后的表的对应关系的表提高破解效率。
如果加盐,这使得同一个密码每次都被加密为完全不同的字符串。为了校验密码是否正确,我们需要储存盐值。通常和密码哈希值一起存放在账户数据库中,或者直接存为哈希字符串的一部分。
每次哈希加密都使用相同的盐值是很容易犯的一个错误,这个盐值要么被硬编码到程序里,要么只在第一次使用时随机获得。这样加盐的方式是做无用功,因为两个相同的密码依然会得到相同的哈希值。攻击者仍然可以使用反向查表法对每个值进行字典攻击,只需要把盐值应用到每个猜测的密码上再进行哈希即可。如果盐值被硬编码到某个流行的软件里,可以专门为这个软件制作查询表和彩虹表,那么破解它生成的哈希值就变得很简单了。
用户创建账户或每次修改密码时,都应该重新生成新的盐值进行加密。
**加盐使攻击者无法采用特定的查询表和彩虹表快速破解大量哈希值,但是却不能阻止他们使用字典攻击或暴力攻击。**高端的显卡(GPU)和定制的硬件可以每秒进行数十亿次哈希计算,因此这类攻击依然可以很高效。为了降低攻击者的效率,我们可以使用一种叫做密钥扩展的技术。
这种技术的思想就是**把哈希函数变得很慢,于是即使有着超高性能的GPU或定制硬件,字典攻击和暴力攻击也会慢得让攻击者无法接受。**最终的目标是把哈希函数的速度降到足以让攻击者望而却步,但造成的延迟又不至于引起用户的注意。这里推荐一种标准算法:bcrypt。
这类算法使用一个安全因子或迭代次数作为参数,这个值决定了哈希函数会有多慢。对于桌面软件或者手机软件,获取参数最好的办法就是执行一个简短的性能基准测试,找到使哈希函数大约耗费0.5秒的值。这样,你的程序就可以尽可能保证安全,而又不影响到用户体验。
因为bcrypt采用了一系列各种不同的Blowfish加密算法,并引入了一个work factor,这个工作因子可以让你决定这个算法的代价有多大。因为这些,这个算法不会因为计算机CPU处理速度变快了,而导致算法的时间会缩短了。因为,你可以增加work factor来把其性能降下来。
bcrypt到底有多慢?如果和MD5一起来比较的话,如果使用值为12的work factor的话,如果加密“cool”的话,bcrypt需要0.3秒,而MD5只需要一微秒(百万分之一秒)。也就是说,前面我们说的那个只需要40秒就可以穷举完所有的可能的MD5编码的口令的算法,在使用bcrypt下,需要12年。
来自:https://coolshell.cn/articles/2078.html、
如何生成密文
1).首先我们得到的是明文的hash值
2).进行计算获取MD5明文hash值
3).随机生成加盐值并插入
4).MD5插入加盐值得到的hash
5).得到最终的密文
如何验证密码
得到用户的密码哈希值和对应盐值
将盐值混入用户输入的密码,并且使用同样的哈希函数进行加密
比较上一步的结果和数据库储存的哈希值是否相同,如果相同那么密码正确,反之密码错误
bcrypt是一个第三方密码加密库,自带加盐和慢哈希功能
模块地址:https://www.npmjs.com/package/bcrypt
https://blog.csdn.net/beijiyang999/article/details/78436876
API 说明
bcrypt.hash(myPlaintextPassword, saltRounds, function(err, hash) {
// Store hash in your password DB.
});
bcrypt.compare(password, encryptPassword, function(err, res) {
if (res === true)
console.log("password is true");
})
Sample code:
var bcrypt = require('bcrypt');
var password = "helloworld";
bcrypt.hash(password, 10, function(err, encryptPassword) {
console.log("bcrypt password:"+encryptPassword);
bcrypt.compare(password, encryptPassword, function(err, res) {
if (res === true)
console.log("password is true");
})
});
bcrypt.hash(password, 8, function(err, encryptPassword1) {
console.log("bcrypt password:"+encryptPassword1);
bcrypt.compare(password, encryptPassword1, function(err, res) {
if (res === true)
console.log("password is true");
})
});
输出
bcrypt password:$2b$08$dmg3Epmn3KGOBbB/15yb/OR8PNiKVwt1c5FTuuIy6sHHlvubMi9oq
password is true
bcrypt password:$2b$10$CGa8jvxFcCX5IwsS8HAUiOIaYjkWR45eUpD/MMLxT/gR1T7ro1XP2
password is true
关于该模块盐位置说明,盐值本身就在 处理后的字符串中,我们用于验证时,模块会提取出盐值进行验证。
加密后的字符串还可以看的出crypt()版本 和加密轮数
3 参考链接
https://coolshell.cn/articles/2078.html、
https://www.zhihu.com/question/20820286
https://crackstation.net/hashing-security.htm
https://blog.csdn.net/coslay/article/details/50382252