[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) | |
---|---|---|
輸入(純文字) | plaintext | message |
加密過程 | cryptographic algorithm | hash function |
加密後稱作 | ciphertext | 文摘(digest)、hash codes、hash value 、hashes,長度通常固定 |
解密 | 可透過 key 解密 | 不可解密(但可能被破解) |
用途 | 需要還原回原本的內容時 | 確保資料內容沒被竄改 |
常見演算法 | AES , PGP | MD5 , 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)