crypto 加密模塊
-
crypto
模塊提供了加密功能,包括對OpenSSL 的哈希、HMAC、加密、解密、簽名、以及驗證功能的以整套封裝。 -
使用
require('crypto')
來訪問該模塊 -
查看nodejs支持的加密算法,
- 使用
crypto.getCiphers()
,如下所示const crypto = require('crypto') crypto.getCiphers()
- 得到的是一個比較大的數組,這裏列舉其中的幾個元素:
[
‘aes-128-cbc’, ‘aes-128-ccm’, ‘aes-128-cfb’,
‘aes256’, ‘aes256-wrap’, ‘aria-128-cbc’,
‘aria-128-ccm’, ‘aria-128-cfb’, ‘aria-128-cfb1’,
‘bf-cbc’, ‘bf-cfb’, ‘bf-ecb’,
‘camellia192’, ‘camellia256’, ‘cast’,
‘cast-cbc’,
… 71 more items
] - 這裏重點介紹 ASE 加密
-
ASE加密是高級加密標準,爲美國聯邦政府採用的一種區塊加密標準,高級加密標準已然成爲對稱密鑰加密中最流行的算法之一。
-
ASE使用的密鑰長度可以128位、192位或256位,所以你可以看到加密算法:aes-128/196/256,表示的都是密鑰的位數。最後一段是AES的工作模式,最常用的是 ECB、CBC、CFB、和OFB四種。
-
ASE 加密時的key(原始密鑰) 和 iv(初始化向量的長度)
長度 密鑰長度 向量長度 128位 16 16 192位 24 16 256位 32 16
-
- 使用
-
crypto.scrypt(password,salt,keylen[,options],callback)
方法:scrypt()
是一個異步的密鑰派生函數,被設計爲在計算和內存方面的成本都非常高,目的是使它無法暴力破解。salt
: 鹽值,應該儘可能獨特。建議鹽值是隨機的並且至少16個字節長。keylen
: 生成的密鑰的長度。callback
回調函數有兩個參數:err
和derivedKey
, 當密鑰派生失敗時, err 是一個異常對象,否則 err 爲 null。 derivedKey 會作爲 Buffer 傳給回調。
-
crypto.randomBytes(size[,callback])
方法:- 生成加密的強僞隨機數據。size 參數是生成隨機數的字節數。
- callback 回調函數有兩個參數:
err
和buf
。如果發生錯誤,則err
是一個Error
對象,否則爲null
。buf
參數是包含生成字節的Buffer
。
###Cipher
類
Cipher
類- Cipher 是密碼的意思。Cipher` 類實例用來加密數據。使用方式有以下兩種,任選一種即可:
- 作爲一個可讀可寫的stream流,這樣可以將原生未加密的數據寫入並在可讀側生成加密的數據。
- 使用 cipher.update 和 cipher.final 方法來生成加密數據
crypto.createCipheriv(algorithm, key, iv[, options])
方法:- 使用給定的
algorithm
(算法)、key
和初始化向量iv
創建並返回一個Cipher
對象。 algorithm
取決於 OpenSSL,列如:'aes-128-cbc'
key
是algorithm
使用的原始密鑰,iv
是初始化向量。兩個參數都必須是utf8
編碼的字符串、Buffer
、TypedArray
或DataView
。- 初始化向量應該是不可預測的且唯一的,理想情況下,它們在密碼上是隨機的。
- 使用給定的
cipher.update(data[, inputEncoding][, outputEncoding])
方法:- 加密
data
,生產加密數據。 如果指定了inputEncoding
參數,則data
參數是使用了指定的字符編碼的字符串。 如果未指定inputEncoding
參數,則data
必須是一個Buffer
、TypedArray
或DataView
。 如果data
是一個Buffer
、TypedArray
或DataView
,則inputEncoding
會被忽略。 outputEncoding
指定了加密的數據的輸出格式。 如果指定了outputEncoding
,則返回使用了指定的字符編碼的字符串。 如果未提供outputEncoding
,則返回Buffer
。- 可以使用新數據多次調用
cipher.update()
方法,直到cipher.final()
被調用。
- 加密
cipher.final([outputEncoding])
方法:- 返回任何剩餘的加密內容。如果指定了
outputEncoding
,則返回一個字符串。如果未提供outputEncoding
,則返回Buffer
。 - 一旦調用了
cipher.final()
方法,則Cipher
對象就不能再用於加密數據。 如果試圖多次調用cipher.final()
,則將會導致拋出錯誤。
- 返回任何剩餘的加密內容。如果指定了
- 注意:加密時要將生成密鑰的密碼:
password
,鹽值:salt
以及初始化向量iv
一同寫入密文,解密時通過密文來獲取password
、salt
、iv
###Decipher
類
Decipher
類的實例用於解密數據。 該類可以通過以下兩種方式之一使用:- 作爲可讀寫的流,其中寫入加密的數據以在可讀側生成未加密的數據。
- 使用
decipher.update()
和decipher.final()
方法生成未加密的數據。
crypto.createDecipheriv(algorithm, key, iv[, options])
方法:- 使用給定的 algorithm(算法)、 key 和初始化向量(iv)創建並返回一個 Decipher 對象。
algorithm
:算法key
: 是algorithm
使用的原始密鑰iv
: 初始化向量
decipher.update(data[, inputEncoding][, outputEncoding])
方法:- 解密使用
Cipher
類實例加密後的密文 inputEncoding
: 如果指定了 inputEncoding 參數,則 data 參數是使用了指定的字符編碼的字符串。outputEncoding
指定了解密的數據的輸出格式
- 解密使用
decipher.final([outputEncoding])
方法:- 返回任何剩餘的解密內容
- 如果指定了
outputEncoding
,則返回一個字符串。如果未提供outputEncoding
,則返回Buffer
。
- 注意:解密時的算法,密鑰密碼, 鹽值,初始化向量必須要和加密時的一致,否則就會出現下面常見的錯誤:
06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
加密解密模塊
加密解密模塊實現:
- 加密解密模塊被放在了一個叫 tools 的工具文件夾下面
- cipherCode.js
//============ 加密/解密數據模塊 ===========
'use strict';
const crypto = require('crypto');
//------------------ 加密數據 -------------------
let algorithm = 'aes-128-cbc'; // algorithm 是算法的意思
/**
* @description 加密數據
* @description params: data--要加密的數據,必須是utf8格式的字符串;callback--處理結果集的回調函數
* @param data {string}
* @param callback {function(string)}
*/
let encrypt = function (data, callback) {
let password = crypto.randomBytes(16).toString('hex'); // password是用於生產密鑰的密碼
let salt = crypto.randomBytes(16).toString('hex'); // 生成鹽值
let iv = crypto.randomBytes(8).toString('hex'); // 初始化向量
crypto.scrypt(password, salt, 16, function (err, derivedKey) {
if (err) {
throw err;
} else {
let cipher = crypto.createCipheriv(algorithm, derivedKey, iv); // 創建 cipher 實例
// 加密數據
let cipherText = cipher.update(data, 'utf8', 'hex');
cipherText += cipher.final('hex');
cipherText += (password+salt + iv);
callback(cipherText)
}
});
};
/**
* @description 解密通過 encrypt(); 加密的數據
* @description param: cipherText--通過 encrypt() 加密的數據; callback--處理結果集的回調函數。
* @param cipherText {string}
* @param callback {function(string)}
*/
let decrypt = function (cipherText, callback) {
let iv = cipherText.slice(-16); // 獲取初始化向量
let salt = cipherText.slice(-48, -16); // 獲取鹽值
let password = cipherText.slice(-80, -48); // 獲取密鑰密碼
let data = cipherText.slice(0, -80); //獲取密文
crypto.scrypt(password, salt, 16, function (err, derivedKey) {
if (err) {
throw err;
} else {
let decipher = crypto.createDecipheriv(algorithm, derivedKey, iv); // 創建 decipher 實例
// 解密數據
let txt = decipher.update(data, 'hex', 'utf8');
txt += decipher.final('utf8');
callback(txt)
}
});
};
//----------- 導出 加密/解密數據模塊 --------------
module.exports = {
encrypt,
decrypt
};
- index.js
//=========== 工具模塊 ==========
const cipher = require('./cipherCode.js');
module.exports = {
cipher
};
測試
// =========== 測試加/解密模塊 =============
const fs = require('fs');
const path = require('path');
const tools = require('../tools');
let str = '今年(應是2003年)是我大學畢業滿10年的日子,也是我投身IT技術的第10年。';
tools.cipher.encrypt(str, function (result) {
fs.writeFile(path.join(__dirname, '1.txt'), result, function (err) {
console.log('加密數據寫入成功');
});
});
fs.readFile(path.join(__dirname, '1.txt'), 'utf8', function (err, info) {
tools.cipher.decrypt(info, function (result) {
console.log(result);
console.log(result===str);
});
});
- 測試結果
加密數據寫入成功
今年(應是2003年)是我大學畢業滿10年的日子,也是我投身IT技術的第10年。
true