nodejs crypto 加密模塊分析及ASE加密、解密的實現

crypto 加密模塊

  1. crypto 模塊提供了加密功能,包括對OpenSSL 的哈希、HMAC、加密、解密、簽名、以及驗證功能的以整套封裝。

  2. 使用 require('crypto') 來訪問該模塊

  3. 查看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 加密
      1. ASE加密是高級加密標準,爲美國聯邦政府採用的一種區塊加密標準,高級加密標準已然成爲對稱密鑰加密中最流行的算法之一。

      2. ASE使用的密鑰長度可以128位、192位或256位,所以你可以看到加密算法:aes-128/196/256,表示的都是密鑰的位數。最後一段是AES的工作模式,最常用的是 ECB、CBC、CFB、和OFB四種。

      3. ASE 加密時的key(原始密鑰) 和 iv(初始化向量的長度)

        長度 密鑰長度 向量長度
        128位 16 16
        192位 24 16
        256位 32 16
  4. crypto.scrypt(password,salt,keylen[,options],callback) 方法:

    • scrypt() 是一個異步的密鑰派生函數,被設計爲在計算和內存方面的成本都非常高,目的是使它無法暴力破解。
    • salt: 鹽值,應該儘可能獨特。建議鹽值是隨機的並且至少16個字節長。
    • keylen: 生成的密鑰的長度。
    • callback 回調函數有兩個參數:errderivedKey, 當密鑰派生失敗時, err 是一個異常對象,否則 err 爲 null。 derivedKey 會作爲 Buffer 傳給回調。
  5. crypto.randomBytes(size[,callback]) 方法:

    • 生成加密的強僞隨機數據。size 參數是生成隨機數的字節數。
    • callback 回調函數有兩個參數:errbuf 。如果發生錯誤,則err 是一個 Error 對象,否則爲nullbuf 參數是包含生成字節的 Buffer

###Cipher

  1. Cipher
    • Cipher 是密碼的意思。Cipher` 類實例用來加密數據。使用方式有以下兩種,任選一種即可:
    • 作爲一個可讀可寫的stream流,這樣可以將原生未加密的數據寫入並在可讀側生成加密的數據。
    • 使用 cipher.update 和 cipher.final 方法來生成加密數據
  2. crypto.createCipheriv(algorithm, key, iv[, options]) 方法:
    • 使用給定的 algorithm(算法)、key 和初始化向量 iv 創建並返回一個 Cipher 對象。
    • algorithm 取決於 OpenSSL,列如:'aes-128-cbc'
    • keyalgorithm 使用的原始密鑰, iv 是初始化向量。兩個參數都必須是 utf8 編碼的字符串、BufferTypedArrayDataView
    • 初始化向量應該是不可預測的且唯一的,理想情況下,它們在密碼上是隨機的。
  3. cipher.update(data[, inputEncoding][, outputEncoding]) 方法:
    • 加密data ,生產加密數據。 如果指定了 inputEncoding 參數,則 data 參數是使用了指定的字符編碼的字符串。 如果未指定 inputEncoding 參數,則 data 必須是一個 BufferTypedArrayDataView。 如果 data 是一個 BufferTypedArrayDataView,則 inputEncoding 會被忽略。
    • outputEncoding 指定了加密的數據的輸出格式。 如果指定了 outputEncoding,則返回使用了指定的字符編碼的字符串。 如果未提供 outputEncoding,則返回 Buffer
    • 可以使用新數據多次調用 cipher.update() 方法,直到 cipher.final() 被調用。
  4. cipher.final([outputEncoding]) 方法:
    • 返回任何剩餘的加密內容。如果指定了 outputEncoding,則返回一個字符串。如果未提供 outputEncoding,則返回 Buffer
    • 一旦調用了 cipher.final() 方法,則 Cipher 對象就不能再用於加密數據。 如果試圖多次調用 cipher.final(),則將會導致拋出錯誤。
  5. 注意:加密時要將生成密鑰的密碼:password ,鹽值:salt 以及初始化向量 iv 一同寫入密文,解密時通過密文來獲取 passwordsaltiv

###Decipher

  1. Decipher 類的實例用於解密數據。 該類可以通過以下兩種方式之一使用:
    • 作爲可讀寫的流,其中寫入加密的數據以在可讀側生成未加密的數據。
    • 使用 decipher.update()decipher.final() 方法生成未加密的數據。
  2. crypto.createDecipheriv(algorithm, key, iv[, options])方法:
    • 使用給定的 algorithm(算法)、 key 和初始化向量(iv)創建並返回一個 Decipher 對象。
    • algorithm :算法
    • key: 是 algorithm使用的原始密鑰
    • iv : 初始化向量
  3. decipher.update(data[, inputEncoding][, outputEncoding]) 方法:
    • 解密使用 Cipher 類實例加密後的密文
    • inputEncoding : 如果指定了 inputEncoding 參數,則 data 參數是使用了指定的字符編碼的字符串。
    • outputEncoding 指定了解密的數據的輸出格式
  4. decipher.final([outputEncoding]) 方法:
    • 返回任何剩餘的解密內容
    • 如果指定了 outputEncoding,則返回一個字符串。如果未提供 outputEncoding,則返回 Buffer
  5. 注意:解密時的算法,密鑰密碼, 鹽值,初始化向量必須要和加密時的一致,否則就會出現下面常見的錯誤
    06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
    

加密解密模塊

加密解密模塊實現:

  1. 加密解密模塊被放在了一個叫 tools 的工具文件夾下面
  2. 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
};


  1. 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);
	});

});

  1. 測試結果

加密數據寫入成功
今年(應是2003年)是我大學畢業滿10年的日子,也是我投身IT技術的第10年。
true

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章