MD5加密的防范方法和crypto模块应用

MD5简介

MD5(Message-Digest Algorithm)是计算机安全领域广泛使用的散列函数(又称哈希算法、摘要算法),主要用来确保消息的完整和一致性。常见的应用场景有密码保护、下载文件校验等。

MD5特点

  • 1.运算速度快:对jquery.js求md5值,57254个字符,耗时1.907ms。
  • 2.输出长度固定:输入长度不固定,输出长度固定(128位)。
  • 3.运算不可逆:已知运算结果的情况下,无法通过通过逆运算得到原始字符串。
  • 4.高度离散:输入的微小变化,可导致运算结果差异巨大。
  • 5.弱碰撞性:不同输入的散列值可能相同。

MD5应用场景

  • 1.文件完整性校验:比如从网上下载一个软件,一般网站都会将软件的md5值附在网页上,用户下载完软件后,可对下载到本地的软件进行md5运算,然后跟网站上的md5值进行对比,确保下载的软件是完整的(或正确的)。
  • 2.密码保护:将md5后的密码保存到数据库,而不是保存明文密码,避免拖库等事件发生后,明文密码外泄。
  • 3.防篡改:比如数字证书的防篡改,就用到了摘要算法。(当然还要结合数字签名等手段)。

常用案例:密码保护

密码首先肯定不可能明文保存在数据库里,一旦泄露后果不堪设想。最基础的做法是对密码进行一层MD5加密,最常见的老年人密码“123456”,经过md5加密后,变为:e10adc3949ba59abbe56e057f20f883e。
这么做一来避免明文暴露,二来黑客破解起来需要时间。
但仅仅是一层md5加密是远远不够的,因为md5的加密是确定性的,所以可以建立一个md5密码破解池,通过枚举,把各种排列组合的加密结果保存起来,然后通过md5密码进行查询,反解。尤其是很多用户的密码很简单,大概率会被暴力破解。

防范方法:密码加盐

“加盐”的意思就是在密码特定位置插入特定字符串后,再对修改后的字符串进行md5运算。
这样首先暴力破解的难度会加大,因为利用了“输入的微小变化,可导致运算结果差异巨大”这一特点,哪怕加入3个数字的盐“值”,md5的加密结果则会完全不同。

crypto模块应用

在nodejs中,crypto模块封装了一系列密码学相关的功能,包括摘要运算。基础例子如下,非常简单:

1
2
3
4
5
6
7
var crypto = require('crypto');
var md5 = crypto.createHash('md5');
var result = md5.update('a').digest('hex');
console.log(result);
// 输出:0cc175b9c0f1b6a831c399e269772661

给密码加盐:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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

还不够,需要随机盐值

单纯的加盐,依然会有各种问题:

  • 1.盐值过短,暴力破解会很容易。
  • 2.固定盐值,或者盐值已经外泄,那么只要把常用密码+固定盐值的hash值表算出来就可以了,本质上又回到了最初的暴力破解路线。

那么我们要做的就是给每个用户一个“随机盐值”。
示例代码如下。可以看到,密码同样是123456,由于采用了随机盐值,前后运算得出的结果是不同的。这样带来的好处是,多个用户,同样的密码,攻击者需要进行多次运算才能够完全破解。同样是纯数字3位短盐值,随机盐值破解所需的运算量,是固定盐值的1000倍。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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

随机盐值需要注意的问题是:后端也需要用户密码来校验登录,所以需要针对每一个用户设定一个随机盐值并且存储起来,每一次都要使用密码和随机盐值通过约定算法加密做校验。
这样做的话,即使盐值外泄,由于每一个用户的盐值都不同,即便暴力破解,也要破解很久,结果也就是破解了一个用户的密码,时间成本会很大。

最后

当然也可以使用只有开发者知道的加密次数,比如对md5值再次进行md5加密,然后再加密,这样也会很大的提高破解代价。
md5也存在碰撞问题,就是两个不同的字符串会得到相同的结果,这是一个需要注意的点。

Snapline wechat
扫码关注我的公众号“约翰柠檬的唱片店”
Buy me a cup of Coffee