FrankKai / FrankKai.github.io

FE blog
https://frankkai.github.io/
362 stars 39 forks source link

[译]md5的salt是什么? #171

Open FrankKai opened 4 years ago

FrankKai commented 4 years ago

https://www.md5online.org/blog/md5-salt-hash/

FrankKai commented 4 years ago

什么是MD5 salt并且如何使用它?

你可能已经知道MD5哈希不是存储密码安全的方式,如果你知道我们的服务,你可能知道我们有一个超大的数据库存储了非常多的单词,可以在几秒内破解出密码。通过使用salt,可以尽可能保护你不受这种数据库的影响,但是不会保护太多...

什么是MD5 Salt并且如何使用它呢? 在密码学中,salt是一个添加到输入单词上的随机字符串,去生成一个单独使用该单词的不同的hash。MD5实际上没有在加密算法中添加这种功能,但是你可以连接两个字符串去获得相同的结果。

在这篇博客中,我会向你解释MD5算法中的salt是什么,以及如何在你的代码中使用salt,以及为什么要使用它。

FrankKai commented 4 years ago

什么是MD5 salt?

介绍中给出了一些解释,但是在这里会给你一个示例。

没有MD5 salt的数据库

假设你想在数据库中存储用户密码,通常来说我们会使用md5加密密码。 比如下面这样:

username password
b.king 5f4dcc3b5aa765d61d8327deb882cf99
m.donald ab4f63f9ac65152575886860dde480a1

但是用一些md5破解网站,很快就能破解出密码是多少。 裸MD5密码用https://hashkiller.co.uk/Cracker/MD5 很快就解析出。

添加salt

如果想使用salt,需要将字符串连接到使用密码。有两个办法做这件事。

对所有用户使用静态salt

在所有密码前面加上类似”randomstringforsalt“,如果m.donald的密码是"azerty",你加密后的密码是"randomstringforsaltazerty"。 这只是做到安全的第一步。m.donald的密码是很强壮的,但是确实世界上最弱的。 猜猜还能怎么做?动态salt。

使用动态salt

如果你经常使用相同的salt,攻击者可以找到它,然后网站就不安全了。 如果他知道你在每个密码前加了”randomstringforsalt“,你的salt就不再有用了。 为了避免他破解出你的salt,可以使用动态salt。 例如,你可以用账号创建日期当做salt:”azerty20190512“。 如果想更安全一点的话,可以对动态salt做MD5处理,生成:”azertyd003a3d8626f9a78abc9ce900b217819“。 这是一个基础的例子,你需要找到更好的salt,它看起来是一个随机值但是你可以很容易找到它来生成密码散列。

有salt的数据库

有salt的数据库和之前看起来没有差别。这就是攻击者的力量,无论你是否使用salt,攻击者都不会直接攻击你。所以他会尝试没有,也许永远不会找到你的密码。

这里有一个加了静态salt的账号和密码。

username password
b.king 81345f0d478885f72dd51c07cc3ab146
m.donald 244b7f46f6aa268fc862e73d81cfc832

加了salt的密码用https://hashkiller.co.uk/Cracker/MD5 解析不出。

FrankKai commented 4 years ago

为什么你的MD5需要salt?

如何解密一个MD5?

MD5算法的弱点在于它的速度。你可以在很短时间内加密大量的单词。所以可以每秒做很多次尝试通过MD5 hash找密码。

为了解密一个密码,一个黑客会用两种方法:

如果你想学习更多MD5解密的方法,可以参考这个链接:https://www.md5online.org/blog/how-md5-decryption-works/ 最直接的就是通过网站破解:https://hashkiller.co.uk/Cracker/MD5,https://www.md5online.org/md5-decrypt.html

为什么需要使用salt?

上面两个方法,对于黑客来说密码的长度都是它解密的问题。 在蛮力破解模式中,黑客可能会从最常见的密码试起,比如a,b,...aa,ab,...等等。 我不知道密码多长最好,但是密码字符更多显然是更好的。如果你的salt有32个字符长,无论密码有多大,你已经足够安全了。

用MD5 hash 数据库的话,会有同样的问题。 如果我们不考虑特殊的字符串,每个字符有62种可能:

所以总hash数爆炸式增长:

是一个指数函数。可以看到6位密码比3位密码安全多了。但是不,它为每个额外的字符增加了更多的可能性。您应该知道,如果密码是30个字符或更多,那么除了基本短语之外,可能没有任何数据库会包含这些信息,这就是为什么使用salt,或者至少要求使用长密码是一个很好的做法。

FrankKai commented 4 years ago

Node如何往MD5添加salt?

自定义普通加盐

var crypto = require('crypto');
function cryptPwd(password, salt) {
    // 密码“加盐”
    var saltPassword = password + ':' + salt;
    console.log('原始密码:%s', password);
    console.log('加盐后的密码:%s', saltPassword);
    // 加盐密码的md5值
    var md5 = crypto.createHash('md5');
    var result = md5.update(saltPassword).digest('hex');
    console.log('加盐密码的md5值:%s', result);
}
cryptPwd('123456', 'abc');
// 输出:
// 原始密码:123456
// 加盐后的密码:123456:abc
// 加盐密码的md5值:51011af1892f59e74baf61f3d4389092
cryptPwd('123456', 'bcd');
// 输出:
// 原始密码:123456
// 加盐后的密码:123456:bcd
// 加盐密码的md5值:55a95bcb6bfbaef6906dbbd264ab4531

自定义动态加盐

// 加了一个3位的盐
var crypto = require('crypto');
function getRandomSalt(){
    return Math.random().toString().slice(2, 5);
}
function cryptPwd(password, salt) {
    // 密码“加盐”
    var saltPassword = password + ':' + salt;
    console.log('原始密码:%s', password);
    console.log('加盐后的密码:%s', saltPassword);
    // 加盐密码的md5值
    var md5 = crypto.createHash('md5');
    var result = md5.update(saltPassword).digest('hex');
    console.log('加盐密码的md5值:%s', result);
}
var password = '123456';
cryptPwd('123456', getRandomSalt());
// 输出:
// 原始密码:123456
// 加盐后的密码:123456:498
// 加盐密码的md5值:af3b7d32cc2a254a6bf1ebdcfd700115
cryptPwd('123456', getRandomSalt());
// 输出:
// 原始密码:123456
// 加盐后的密码:123456:287
// 加盐密码的md5值:65d7dd044c2db64c5e658d947578d759

引用:https://www.cnblogs.com/chyingp/p/nodejs-learning-crypto-md5.html

官方盐

Nodejs v10.5.0后 crypto模块可以加盐的方法 crypto.scrypt(password, salt, keylen[, options], callback)

const crypto = require('crypto');
// Using the factory defaults.
crypto.scrypt('secret', 'salt', 64, (err, derivedKey) => {
  if (err) throw err;
  console.log(derivedKey.toString('hex'));  // '3745e48...08d59ae'
});
// Using a custom N parameter. Must be a power of two.
crypto.scrypt('secret', 'salt', 64, { N: 1024 }, (err, derivedKey) => {
  if (err) throw err;
  console.log(derivedKey.toString('hex'));  // '3745e48...aa39b34'
});

https://nodejs.org/api/crypto.html#crypto_crypto