iT邦幫忙

2024 iThome 鐵人賽

DAY 30
1
JavaScript

可愛又迷人的 Web API系列 第 30

Day30. 使用 Web Cryptography API 提高網頁應用的安全性

  • 分享至 

  • xImage
  •  

現在的網路應用服務愈來愈多,每個服務幾乎都要加入會員以及取得各種個人資料,也因此數據的安全性也愈發重要。我們可以使用 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 密鑰。我們要設定演算法名稱和密鑰的長度,以及密鑰的可匯出性和用途。這組生成的密鑰可以用於加密和解密操作。

https://mukiwu.github.io/web-api-demo/img/29-1.png

如果要產生一組 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 或其他非對稱加密算法,以下是產生出來的公鑰與私鑰範例。

https://mukiwu.github.io/web-api-demo/img/29-2.png

使用 AES-GCM 加密與解密資料

加密資料

有了密鑰後,我們就能進行加密和解密囉,這邊就用剛剛的 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,就能讓同樣的明文在加密後都不一樣:

https://mukiwu.github.io/web-api-demo/img/29-3.png

解密資料

該如何使用 AES 解密呢?解密前要再提一次 IV 的重要性,我們在加密和解密過程中都必須使用 IV,因為解密時,必須使用與加密時相同的 IV,否則無法正確地解密資料

因此,我們在加密後要將 IV 一併儲存或傳輸給解密方,以便能夠正確解密資料。常見的儲存方式有兩種:

  1. 與密文一起儲存或傳輸:這是最常見的方式。通常,IV 可以直接附加在密文的前面或後面,在需要解密時分割出 IV 和密文。
  2. 單獨儲存或傳輸:將 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 加密與解密資料

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 不僅在資料傳輸過程中確保了數據的機密性和完整性,有效防止了資料被未經授權的第三方竊取或篡改,更進一步加強了使用者的隱私保護和數據安全性。最後有任何問題,都歡迎留言討論唷。


上一篇
Day29. 使用 Screen Capture API 取得你的螢幕畫面
下一篇
寫在最後:完賽撒花!
系列文
可愛又迷人的 Web API31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言