代码改变世界

nodeJS之crypto模块md5和Hmac加密

2018-06-03 12:10 by 龙恩0707, ... 阅读, ... 评论, 收藏, 编辑

nodeJS之crypto模块md5和Hmac加密

在nodejs中,可以使用crypto模块来实现各种不同的加密与解密处理,在crypto模块中包含了类似MD5或SHA-1这些散列算法,我们可以通过crypto模块来实现HMAC运算。
什么是HMAC运算?
HMAC的中文意思是:散列运算消息认证码;运算使用散列算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出。HMAC运算可以用来验证两段数据是否匹配,以确认该数据没有被篡改。

在crypto模块中,为每一种加密算法定义了一个类。可以使用getCiphers方法来查看nodejs中能够使用的所有加密算法。该方法返回一个数组,包括了nodejS中能够使用的所有散列算法。使用方法如下所示:

const crypto = require('crypto');
console.log(crypto.getCiphers());
/*
 输出如下
 [ 'aes-128-cbc',
  'aes-128-cbc-hmac-sha1',
  'aes-128-cbc-hmac-sha256',
  'aes-128-ccm',
  'aes-128-cfb',
  'aes-128-cfb1',
  'aes-128-cfb8',
  'aes-128-ctr',
  'aes-128-ecb',
  'aes-128-gcm',
  'aes-128-ofb',
  'aes-128-xts',
  'aes-192-cbc',
  'aes-192-ccm',
  'aes-192-cfb',
  'aes-192-cfb1',
  'aes-192-cfb8',
  'aes-192-ctr',
  'aes-192-ecb',
  'aes-192-gcm',
  'aes-192-ofb',
  'aes-256-cbc',
  'aes-256-cbc-hmac-sha1',
  'aes-256-cbc-hmac-sha256',
  'aes-256-ccm',
  'aes-256-cfb',
  'aes-256-cfb1',
  'aes-256-cfb8',
  'aes-256-ctr',
  'aes-256-ecb',
  'aes-256-gcm',
  'aes-256-ofb',
  'aes-256-xts',
  'aes128',
  'aes192',
  'aes256',
  'bf',
  'bf-cbc',
  'bf-cfb',
  'bf-ecb',
  'bf-ofb',
  'blowfish',
  'camellia-128-cbc',
  'camellia-128-cfb',
  'camellia-128-cfb1',
  'camellia-128-cfb8',
  'camellia-128-ecb',
  'camellia-128-ofb',
  'camellia-192-cbc',
  'camellia-192-cfb',
  'camellia-192-cfb1',
  'camellia-192-cfb8',
  'camellia-192-ecb',
  'camellia-192-ofb',
  'camellia-256-cbc',
  'camellia-256-cfb',
  'camellia-256-cfb1',
  'camellia-256-cfb8',
  'camellia-256-ecb',
  'camellia-256-ofb',
  'camellia128',
  'camellia192',
  'camellia256',
  'cast',
  'cast-cbc',
  'cast5-cbc',
  'cast5-cfb',
  'cast5-ecb',
  'cast5-ofb',
  'des',
  'des-cbc',
  'des-cfb',
  'des-cfb1',
  'des-cfb8',
  'des-ecb',
  'des-ede',
  'des-ede-cbc',
  'des-ede-cfb',
  'des-ede-ofb',
  'des-ede3',
  'des-ede3-cbc',
  'des-ede3-cfb',
  'des-ede3-cfb1',
  'des-ede3-cfb8',
  'des-ede3-ofb',
  'des-ofb',
  'des3',
  'desx',
  'desx-cbc',
  'id-aes128-CCM',
  'id-aes128-GCM',
  'id-aes128-wrap',
  'id-aes192-CCM',
  'id-aes192-GCM',
  'id-aes192-wrap',
  'id-aes256-CCM',
  'id-aes256-GCM',
  'id-aes256-wrap',
  'id-smime-alg-CMS3DESwrap',
  'idea',
  ... 19 more items ]
*/

一:散列算法

散列(也可以叫哈希)算法,它是用来对一段数据进行验证前,将该数据模糊化,或者也可以为一大段数据提供一个校验码。
在nodejs中,为了使用该散列算法,我们先要使用 createHash方法创建一个hash对象。使用方法如下:

crypto.createHash(params);

在如上方法中,需要使用一个参数,其参数值为一个在Node.js中可以使用的算法,比如 'sha1', 'md5', 'sha512' 等等这样的,用于指定需要使用的散列算法,该方法返回被创建的hash对象。

在创建完hash对象后,可以通过使用该对象的update方法创建一个摘要。该方法的使用方式如下:

hash.update(data, [encoding]);

在如上面的方法,该方法需要使用两个参数,第一个参数是必选项,该参数值是一个Buffer对象或一个字符串,用于指定摘要内容; 第二个参数 encoding用于指定摘要内容所需使用的编码格式,可以指定为 'utf-8', 'ascii', 或 'binary'. 如果不使用第二个参数,则第一个参数data参数值必须为一个Buffer对象,我们也可以在摘要被输出前使用多次updata方法来添加摘要内容。

在第一步创建了一个hash对象后,第二步就是添加摘要内容,那么第三步我们就是使用hash对象的digest方法来输出摘要内容了;在使用hash对象的digest
方法后,不能再向hash对象中追加摘要内容,也就是说你使用了digest方法作为输出后,你再追加内容也不会执行,因此是不能向hash对象中追加内容。
使用方法如下:

hash.digest([encoding]);

该方法有一个参数,该参数是一个可选值,表示的意思是 用于指定输出摘要的编码格式,可指定参数值为 'hex', 'binary', 及 'base64'.如果使用了该参数,那么digest方法返回字符串格式的摘要内容,如果不使用该参数,那么digest方法返回一个是Buffer对象。

二:MD5算法
MD5是计算机领域使用最广泛的散列函数(可以叫哈希算法、摘要算法),注意是用来确保消息的完整和一致性。

下面我们最主要是以 md5 加密为例来了解下加密算法。
MD5算法有以下特点:
1. 压缩性: 任意长度的数据,算出的MD5值长度都是固定的。
2. 容易计算:从原数据算出MD5值很容易。
3. 抗修改性:对原数据进行任何改动,哪怕只修改一个字节,所得到的MD5值都有很大的区别。
4. 强抗碰撞:已知原数据和其MD5值,想找到一个具有相同的MD5值的伪数据是非常困难的。

MD5的作用是让大容量信息在用数字签名软件签署私人秘钥前被压缩成一种保密的格式(就是把任意长度的字符串变换成一定长的十六进制数字串)。

如下使用代码:

const crypto = require('crypto');

const str = 'abc';

// 创建一个hash对象
const md5 = crypto.createHash('md5');

// 往hash对象中添加摘要内容
md5.update(str);

// 使用 digest 方法输出摘要内容,不使用编码格式的参数 其输出的是一个Buffer对象
// console.log(md5.digest()); 
// 输出 <Buffer 90 01 50 98 3c d2 4f b0 d6 96 3f 7d 28 e1 7f 72>

// 使用编码格式的参数,输出的是一个字符串格式的摘要内容
console.log(md5.digest('hex')); // 输出 900150983cd24fb0d6963f7d28e17f72

Md5算法demo实列:

现在我们来看下一个demo,比如一些登录信息,比如密码直接以明文的方式存放在数据库中是不安全的,开发人员直接可以通过肉眼就可以知道,可以记下来,因此我们需要使用md5来加密一下;因此我们可以做如下代码加密:

const crypto = require('crypto');

var cryptoPassFunc = function(password) {
  const md5 = crypto.createHash('md5');
  return md5.update(password).digest('hex');
};

const password = '123456';
const croptyPass = cryptoPassFunc(password);

const croptyPass2 = cryptoPassFunc(password);

console.log(croptyPass); // e10adc3949ba59abbe56e057f20f883e

console.log(croptyPass2); // e10adc3949ba59abbe56e057f20f883e

如上console.log输出的是通过md5加密后代码了;

只对md5加密的缺点:

通过上面对md5加密后确实比明文好很多,至少很多人直接使用肉眼看到的并记不住,也不知道密码多少,但是只对md5加密也存在缺点,如上代码使用console.log打印两次后,加密后的代码是一样,也就是说 相同的明文密码,加密后,输出两次,md5的值也是一样的。 所以这样也是不安全的。

密码加盐:

什么意思呢?就是在密码的特定位置上插入特定的字符串后,再对修改后的字符串进行md5加密,这样做的好处是每次调用代码的时候,插入的字符串不一样,会导致最后的md5值会不一样的。代码如下:

const crypto = require('crypto');

var saltPasswordFunc = function(password, salt) {

  // 密码加盐
  const saltPassword = password + ':' + salt;
  console.log('原始密码:%s', password); 
  console.log('加盐后的密码:%s', saltPassword);


  const md5 = crypto.createHash('md5');
  const result = md5.update(saltPassword).digest('hex');
  console.log('加盐密码后的md5的值为:%s', result);

};

const password = '123456';

saltPasswordFunc(password, 'abc');
/*
 输出结果为:
 原始密码:123456
 加盐后的密码:123456:abc
 加盐密码后的md5的值为:51011af1892f59e74baf61f3d4389092
*/

saltPasswordFunc(password, 'def');
/*
 输出结果为:
 原始密码:123456
 加盐后的密码:123456:def
 加盐密码后的md5的值为:5091d17d6b08ba9a95ccef51f598b249
*/

密码加密:随机盐值

如上通过密码加盐,比单单的使用md5加密,安全性相对来说更高点,但是也存在问题,比如字符串拼接算法中的字符串开发者知道的,第二个是盐值固定,也就是说拼接的字符串的盐值是固定的;所以存在这几个问题,因此下面我们需要一个随机数来生成随机盐值。这样安全性或许会更高点。

因此优化后的代码如下:(优化点无非就是生成一个随机数当做盐值)

const crypto = require('crypto');

var getRandomSalt = function() {
  // 使用六位随机数吧
  const randomSalt = Math.random().toString().slice(2, 8);
  console.log(randomSalt);
  return randomSalt;
};

var saltPasswordFunc = function(password, salt) {
  // 密码加盐
  const saltPassword = password + ':' + salt;
  console.log('原始密码:%s', password); 
  console.log('加盐后的密码:%s', saltPassword);


  const md5 = crypto.createHash('md5');
  const result = md5.update(saltPassword).digest('hex');
  console.log('加盐密码后的md5的值为:%s', result);
};

const password = '123456';

saltPasswordFunc(password, getRandomSalt());
/*
 输出结果为:
 原始密码:123456
 加盐后的密码:123456:随机生成6位数字
 加盐密码后的md5的值为:密码+ ':' + 随机生成6位数字 的md5值
*/

saltPasswordFunc(password, getRandomSalt());
/*
 输出结果为:
 原始密码:123456
 加盐后的密码:123456:随机生成6位数字
 加盐密码后的md5的值为:密码+ ':' + 随机生成6位数字 的md5值
*/

这样做的好处是:每次运行的时候,或者说叫请求的时候,盐值是不一样的,导致每次生成的md5加密后的密码是不一样的。

三:HMAC算法

HMAC算法是将散列算法与一个密钥结合在一起,以阻止对签名完整性破坏,其实就是类似于上面的提到的md5密码中加盐道理是类似的。
使用HMAC算法前,我们使用createHmac方法创建一个hmac对象,创建方法如下所示:

crypto.createHmac(params, key);

该方法中使用两个参数,第一个参数含义是在Node.js中使用的算法,比如'sha1', 'md5', 'sha256', 'sha512'等等,该方法返回的是hmac对象。
key参数值为一个字符串,用于指定一个PEM格式的密钥。

在创建完成hmac对象后,我们也是一样使用一个update方法来创建一个摘要,该方法使用如下所示:

hmac.update(data);

在update方法中,使用一个参数,其参数值为一个Buffer对象或一个字符串,用于指定摘要内容。也是一样可以在被输出之前使用多次update方法来添加摘要内容。

最后一步就是 使用hmac对象的digest方法来输出摘要内容了;在使用hmac对象的digest方法后,不能再向hmac对象中追加摘要内容,也就是说你使用了digest方法作为输出后,因此是不能向hmac对象中追加内容。使用方法如下:

hmac.digest([encoding]);

该方法有一个参数,该参数是一个可选值,表示的意思是 用于指定输出摘要的编码格式,可指定参数值为 'hex', 'binary', 及 'base64'.
如果使用了该参数,那么digest方法返回字符串格式的摘要内容,如果不使用该参数,那么digest方法返回一个是Buffer对象。

如下使用一个简单的demo

const crypto = require('crypto');

// 创建一个hmac对象
const hmac = crypto.createHmac('md5', 'abc');

// 往hmac对象中添加摘要内容
const up = hmac.update('123456');

// 使用 digest 方法输出摘要内容

const result = up.digest('hex'); 

console.log(result); // 8c7498982f41b93eb0ce8216b48ba21d