現在的網路應用服務愈來愈多,每個服務幾乎都要加入會員以及取得各種個人資料,也因此數據的安全性也愈發重要。我們可以使用 Web Cryptography API 幫我們在瀏覽器中進行加密、解密、生成和驗證密鑰等動作。這個 API 可以提高網頁應用的安全性,不需要依賴外部套件,就能在客戶端進行各種加密操作,此外也支持多種加密演算法,例如常見的 RSA、AES、SHA-256 等,非常方便。
前面有提到,Web Cryptography API 支援多種加密演算法,我們就用它來生成一組對稱密鑰 (AES) 以及一組非對稱密鑰 (RSA)。
首先,先產生一個 256 位元的 AES 對稱密鑰:
async function generateAESKey() {
const key = await crypto.subtle.generateKey(
{
name: "AES-GCM",
length: 256, // 密鑰長度可以是 128, 192 或 256
},
true, // 是否允許密鑰被匯出
["encrypt", "decrypt"] // 密鑰的用途
);
console.log("Generated AES Key:", key);
return key;
}
generateAESKey();
我們使用 crypto.subtle.generateKey
方法生成了一個 AES-GCM 密鑰。我們要設定演算法名稱和密鑰的長度,以及密鑰的可匯出性和用途。這組生成的密鑰可以用於加密和解密操作。
如果要產生一組 RSA 的公鑰與私鑰,可以這樣寫:
async function generateRSAKey() {
const keyPair = await crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 2048, // 長度,可以是 1024, 2048, 或 4096
publicExponent: new Uint8Array([1, 0, 1]), // 常用的公鑰指數 (65537)
hash: { name: "SHA-256" }, // 使用的 hash 算法,可以是 "SHA-1", "SHA-256", "SHA-384", "SHA-512"
},
true, // 是否允許密鑰被匯出
["encrypt", "decrypt"] // 公鑰的用途
);
console.log("Generated RSA Key Pair:", keyPair);
return keyPair;
}
generateRSAKey();
如果需要分發密鑰,可以考慮 RSA 或其他非對稱加密算法,以下是產生出來的公鑰與私鑰範例。
有了密鑰後,我們就能進行加密和解密囉,這邊就用剛剛的 AES 密鑰來做示範,會使用前面做好的函式 generateAESKey()
來產生密鑰。
首先產生密鑰,然後加密:
function generateRandomValues() {
const array = new Uint8Array(12);
window.crypto.getRandomValues(array);
return array;
}
async function generateAESKey() {
const key = await crypto.subtle.generateKey(
{
name: "AES-GCM",
length: 256,
},
true,
["encrypt", "decrypt"]
);
return key;
}
async function encryptData(key, data) {
const iv = generateRandomValues();
const encrypted = await crypto.subtle.encrypt(
{
name: "AES-GCM",
iv: iv,
},
key,
data
);
console.log("加密後的資料:", new Uint8Array(encrypted));
return { encrypted, iv };
}
const data = new TextEncoder().encode("這是一段需要加密的文字。");
generateAESKey().then(key => encryptData(key, data));
加密前,我們先用 generateRandomValues()
產生一個 12 位元的隨機值 (IV),他可以確保相同的明文(要加密的文字)在每一次的加密時,都會產生不同的密文,以增加安全性。
接著使用 crypto.subtle.encrypt
方法進行加密,回傳的結果就是加密後的資料,而且使用了 IV,就能讓同樣的明文在加密後都不一樣:
該如何使用 AES 解密呢?解密前要再提一次 IV
的重要性,我們在加密和解密過程中都必須使用 IV
,因為解密時,必須使用與加密時相同的 IV
,否則無法正確地解密資料。
因此,我們在加密後要將 IV
一併儲存或傳輸給解密方,以便能夠正確解密資料。常見的儲存方式有兩種:
IV
可以直接附加在密文的前面或後面,在需要解密時分割出 IV
和密文。IV
儲存在一個獨立的變數或檔案中,並在解密時與密文一同使用。先假設我們將 IV
存在 localStorage,以下分享如何從 localStorage 取得 IV
並進行解密:
接著是解密操作:
async function decryptData(key, encryptedData) {
const iv = localStorage.getItem('iv');
const decrypted = await crypto.subtle.decrypt(
{
name: "AES-GCM",
iv,
},
key,
encryptedData
);
console.log("解密後的資料:", new TextDecoder().decode(decrypted));
return decrypted;
}
// 解密
generateAESKey().then(key => {
encryptData(key, data).then(({ encrypted }) => {
decryptData(key, encrypted);
});
});
使用 crypto.subtle.decrypt
方法進行解密即可。
你會發現解密和加密的動作非常類似,差別只在於我們要提供加密時使用的 IV
,才能解密資料。另外將 IV
存在 localStorage 不是一個好作法,我只是為了快速示範才這樣使用,請知悉。
RSA 是非對稱加密,他的特點是會產生一組密鑰:公鑰與私鑰,公鑰用來加密,私鑰用來解密。
以下是完整的範例程式碼,我有在程式碼裡面寫一些註解,讓大家更了解流程
// 產生密鑰
async function generateRSAKey() {
const keyPair = await crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: { name: "SHA-256" },
},
true,
["encrypt", "decrypt"]
);
return keyPair;
}
// 使用公鑰加密
async function encryptData(publicKey, data) {
const encryptedData = await crypto.subtle.encrypt(
{
name: "RSA-OAEP"
},
publicKey,
data // 要加密的資料,需轉換為 ArrayBuffer
);
console.log("加密後的資料:", new Uint8Array(encryptedData));
return encryptedData;
}
// 使用私鑰解密
async function decryptData(privateKey, encryptedData) {
const decryptedData = await crypto.subtle.decrypt(
{
name: "RSA-OAEP"
},
privateKey,
encryptedData
);
console.log("解密後的資料:", new TextDecoder().decode(decryptedData));
return decryptedData;
}
const { publicKey, privateKey } = await generateRSAKey();
const data = new TextEncoder().encode("這是一段需要加密的文字。");
// 使用公鑰加密資料
const encryptedData = await encryptData(publicKey, data);
// 使用私鑰解密資料
const decryptedData = await decryptData(privateKey, encryptedData);
透過以上的介紹,我們可以清楚地了解到 Web Cryptography API 的強大功能及其廣泛應用。這個 API 不僅在資料傳輸過程中確保了數據的機密性和完整性,有效防止了資料被未經授權的第三方竊取或篡改,更進一步加強了使用者的隱私保護和數據安全性。最後有任何問題,都歡迎留言討論唷。