➜ Old React website
Chung Cheuk Hang MichaelJava Web Developer
HTML 網頁顯示 WebP 圖片用 Markdown 寫 doc

NodeJS + CryptoJS 加密解密

Table of contents

1 項目背景

我地想寫一個可以本地執行既 CLI(command line interface)小工具,要有以下功能:
功能描述
加密以用戶提供既密碼,加密一個檔案(任何檔案格式),輸出一個純文字檔案。
解密以用戶提供既密碼,解密一個由呢個工具既加密功能得出既純文字檔案,輸出原檔案。
而加密、解密功能都會用上 AES——最多人用既 symmetric key algorithm,而 CryptoJS 作為一個熱門既 JavaScript 加密 package 就可以提供到呢一個加密算法。

2 動手寫

2.1 NPM dependencies

package.json
1{ 2 "name": "aes-encryptor-decryptor", 3 "version": "1.0.0", 4 "private": true, 5 "type": "module", 6 "dependencies": { 7 "crypto-js": "^4.1.1" 8 } 9}

2.2 寫 JavaScript code

2.2.1 通用既 utility

我地會用 AES(symmetric key)黎加密數據,再用 Base64 表達。
utils.js
1import CryptoJS from "crypto-js"; 2 3const encrypt = (data, secret) => CryptoJS.AES.encrypt(data, secret).toString(); 4 5const decrypt = (data, secret) => CryptoJS.AES.decrypt(data, secret).toString(CryptoJS.enc.Utf8); 6 7export default { encrypt, decrypt }; 8export { encrypt, decrypt };

2.2.2 加密功能

加密功能需要用戶提供:
  1. 要加密既檔案既路徑
  2. 輸出檔案既路徑
  3. 密碼
enc.js
1import { readFile, writeFile } from "fs/promises"; 2import { encrypt } from "./utils.js"; 3 4const main = async () => { 5 const args = process.argv.slice(2); // 一定係 2 6 7 if (args.length < 3) { 8 console.log( 9 [ 10 "Required 3 args:", 11 "", 12 " enc <I> <O> <P>", 13 "", 14 "I: Input file path", 15 "O: Output file path", 16 "P: Password", 17 ].join("\n") 18 ); 19 return; 20 } 21 22 const inputFilePath = args.shift(); 23 const outputFilePath = args.shift(); 24 const secret = args.shift(); 25 26 const data = await readFile(inputFilePath, { encoding: "base64" }); 27 const encrypted = encrypt(data, secret); 28 await writeFile(outputFilePath, encrypted, { encoding: "utf8" }); 29 30 console.log("Done encryption!"); 31}; 32 33main();

2.2.3 解密功能

解密功能需要用戶提供:
  1. 已加密既檔案既路徑
  2. 輸出檔案既路徑
  3. 密碼
dec.js
1import { readFile, writeFile } from "fs/promises"; 2import { decrypt } from "./utils.js"; 3 4const main = async () => { 5 const args = process.argv.slice(2); // 一定係 2 6 7 if (args.length < 3) { 8 console.log( 9 [ 10 "Required 3 args:", 11 "", 12 " dec <I> <O> <P>", 13 "", 14 "I: Input file path", 15 "O: Output file path", 16 "P: Password", 17 ].join("\n") 18 ); 19 return; 20 } 21 22 const inputFilePath = args.shift(); 23 const outputFilePath = args.shift(); 24 const secret = args.shift(); 25 26 const data = await readFile(inputFilePath, { encoding: "utf8" }); 27 const decrypted = decrypt(data.trim(), secret); 28 await writeFile(outputFilePath, decrypted, { encoding: "base64" }); 29 30 console.log("Done decryption!"); 31}; 32 33main();

3 測試

3.1 測試加密功能

node enc "/path/to/raw.file" "/path/to/encrypted.file" "password"

3.2 測試解密功能

node dec "/path/to/encrypted.file" "/path/to/raw.file" "password"

4 筆記

4.1 用 import 而非 require 引用 modules

require 係 CommonJS 既寫法,可以喺 runtime 根據條件即時 load module;而 import 就係 ECMAScript 既寫法,喺 compile 既時候就會 load,亦冇得根據條件 load 或唔 load module。
NodeJS 默認係用 CommonJS,除非喺 package.json 度配置:
{ "type": "module" }

4.2 process.argv

2 個 values 係 NodeJS 個 binary 既 file path 以及我地個 JavaScript 程式既 file path,所以要去除頭 2 個 values:
const args = process.argv.slice(2); // 一定係 2
參考資料:

4.3 fs/promises

NodeJS 自帶既 fs module 提供左讀寫檔案既功能,不過啲 API 就係 async callback 既寫法黎既。
如果我地想得到 Promise,然後用 await 去令佢返回結果,可以用 NodeJS 自帶既 fs/promises module。
參考資料:

4.4 將 config 放喺 .env

如果我地想用一個檔案黎 config password,我地可以喺 project root folder 建立一個 .env 檔,然後將 config 寫落去。不過我地需要安裝 NPM library dotenv 先可以將 .env 檔裡面既 key-value pairs 讀入 process.env,然後喺 JavaScript code 裡面使用。
.env
password=1234
為 project 添加 dependency:
npm i dotenv
喺 JavaScript code 度讀取 .env 既 config:
import dotenv from "dotenv"; dotenv.config(); console.log(process.env.password); // 1234
參考資料: