跳至主要内容

[WebAPIs] 資料加密 Crypto and Cipher

keywords: encryption, decryption, hash, ciphertext, bcrypt, cryptographic

專有名詞

加密(encryption)和摘要(digest)

簡單來說,加密(encryption)指的是將純文字(plain text)透過金鑰(key)加密演算法(encryption algorithm)轉換成加密後的密文(ciphertext, encrypted text)。加密後的內容可以透過相同的金鑰和演算法解密回原本的文字內容(original text)。

摘要(digest)則是將純文字(message)透過雜湊函式(hash function)產生成一段雜湊(hashes),通常雜湊的長度會是固定的,可以用來確保原本的文字內容沒有被他人竄改,但通常不能從雜湊解密回原本的文字內容。

加密(encrypt)雜湊(hashing)
輸入(純文字)plaintextmessage
加密過程cryptographic algorithmhash function
加密後稱作ciphertext文摘(digest)、hash codes、hash value、hashes,長度通常固定
解密可透過 key 解密不可解密(但可能被破解)
用途需要還原回原本的內容時確保資料內容沒被竄改
常見演算法AES, PGPMD5, SHA-1, SHA-2, SHA-256

雜湊(Hashing)通常可以用來儲存密碼,先家使用者輸入的密碼雜湊處理後,儲存到資料庫中,接著在使用者登入時,把輸入的密碼一樣透過雜湊處理,接著把它和儲存在資料庫中的雜湊比對,一樣的話即可登入。

**加鹽(salting)**指的是在進行雜湊前「將原本 message 的任意固定位置插入特定字串」,這麼做可以增加額外的安全性。一般來說,鹽不需要額外的加密,可以是隨機的文字。

實作:使用 Node 原生的 crypto

資料加密:crypto.subtle.encrypt

透過 crypto.subtle.encrypt(algorithm, key, data) 可以將原本的資料(plaintext)進行加密,會回傳 Promise,這個 Promise 又會回傳加密後的內容(ciphertext):

// const result = crypto.subtle.encrypt(algorithm, key, data);
const encryptText = async (plainText, password) => {
const ptUtf8 = new TextEncoder().encode(plainText);

// 把 password 透過雜湊加密
const pwUtf8 = new TextEncoder().encode(password);
const pwHash = await crypto.subtle.digest('SHA-256', pwUtf8);

// 加密要用的演算法
const iv = crypto.getRandomValues(new Uint8Array(12));
const alg = {
name: 'AES-GCM',
iv: iv,
};

// 把雜湊後 password 變成 key
const key = await crypto.subtle.importKey('raw', pwHash, alg, false, ['encrypt']);

return {
iv,
encBuffer: await crypto.subtle.encrypt(alg, key, ptUtf8),
};
};

const plainText = 'I see the secrete';
const password = 'key';

encryptText(plainText, password).then((ciphertext) => {
console.log(ciphertext);
});
  • password:拿這組 password 雜湊後變成加密用的 key
  • iv 指的是 initialization vector,可以想成是隨機種子向量,但解密的時候還需要用到它。

資料解密:crypto.subtle.encrypt

透過 crypto.subtle.encrypt(algorithm, key, ciphertext) 可以將資料進行解密。這個方法會回傳一個 Promise,而這個 Promise 會回傳解密後的 plaintext:

const decryptText = async (ctBuffer, iv, password) => {
// 把 password 拿進來進行雜湊處理
const pwUtf8 = new TextEncoder().encode(password);
const pwHash = await crypto.subtle.digest('SHA-256', pwUtf8);

const alg = {
name: 'AES-GCM',
iv: iv,
};

// 把雜湊後 password 變成 key
const key = await crypto.subtle.importKey('raw', pwHash, alg, false, ['decrypt']);

const ptBuffer = await crypto.subtle.decrypt(alg, key, ctBuffer);
const plaintext = new TextDecoder().decode(ptBuffer);

return plaintext;
};

const plainText = 'I see the secrete';
const password = 'key';

encryptText(plainText, password).then((ciphertext) => {
decryptText(ciphertext.encBuffer, ciphertext.iv, password).then((plainText) =>
console.log('plainText', plainText),
); // 解碼出原本的文字
});

實作:使用 bcryptjs

bcryptjs @ GitHub

const bcrypt = require('bcryptjs');
const encryptPassword = bcrypt.hashSync(password, bcrypt.genSaltSync(10), null),

// user.password if from database
const isSamePassword = bcrypt.compareSync(req.body.password, user.password)

參考