天天看點

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

      回調函數有兩個參數:

      err

      derivedKey

      , 當密鑰派生失敗時, err 是一個異常對象,否則 err 為 null。 derivedKey 會作為 Buffer 傳給回調。
  5. crypto.randomBytes(size[,callback])

    方法:
    • 生成加密的強僞随機資料。size 參數是生成随機數的位元組數。
    • callback 回調函數有兩個參數:

      err

      buf

      。如果發生錯誤,則

      err

      是一個

      Error

      對象,否則為

      null

      buf

      參數是包含生成位元組的

      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'

    • key

      algorithm

      使用的原始密鑰,

      iv

      是初始化向量。兩個參數都必須是

      utf8

      編碼的字元串、

      Buffer

      TypedArray

      DataView

    • 初始化向量應該是不可預測的且唯一的,理想情況下,它們在密碼上是随機的。
  3. 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()

      被調用。
  4. cipher.final([outputEncoding])

    方法:
    • 傳回任何剩餘的加密内容。如果指定了

      outputEncoding

      ,則傳回一個字元串。如果未提供

      outputEncoding

      ,則傳回

      Buffer

    • 一旦調用了

      cipher.final()

      方法,則

      Cipher

      對象就不能再用于加密資料。 如果試圖多次調用

      cipher.final()

      ,則将會導緻抛出錯誤。
  5. 注意:加密時要将生成密鑰的密碼:

    password

    ,鹽值:

    salt

    以及初始化向量

    iv

    一同寫入密文,解密時通過密文來擷取

    password

    salt

    iv

###

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