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