nodeJS之crypto模組md5和Hmac加密

龍恩0707發表於2018-06-03

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

相關文章