是 Ray 不是 Array

整理這些技術筆記真的很花時間,如果你願意 關閉 Adblock 支持我,我會把這份感謝轉換成更多「踩坑轉避坑」的內容給你!ヽ(・∀・)ノ

Advertisement
2026-03-10 JavaScript

前端加密不求人!Web Crypto API 完整教學:雜湊、AES 加解密、HMAC 簽章

Web Crypto API
Web Crypto API

前言

在開發過程中,我們常常會需要對資料進行加密、解密、雜湊等操作,過去我們可能會依賴 Node.js 的 crypto 模組或第三方套件(例如:crypto-js),但其實現代瀏覽器已經內建了 Web Crypto API,讓你不需要安裝任何套件就可以直接在瀏覽器中進行加解密操作,那麼這一篇就來介紹一下 Web Crypto API 的使用方式吧!

Crypto 是什麼?

許多時候我們在進行開發時,並不會直接將資料明文儲存,而是會將資料額外加密後再儲存,這樣的好處在於,當資料庫被入侵時,駭客也會因為沒有 Key 而無法解密資料庫中的資料,雖然可以透過暴力破解方式推測出 Key,但這樣的方式會需要花費非常長的時間,但其實這樣有助於當資安事件發生時,企業可以爭取足夠時間來處理資安事件,並且通知使用者(如:更改密碼、鎖定帳號等等)。

因此 Crypto 就是專門做這件事情的套件,舉凡:

  • 加密
  • 解密
  • 雜湊
  • HMAC
  • 簽章
  • 驗章

等等,都可以透過 Crypto 來實現,而在 Node.js 環境中,Crypto 是內建的模組,因此我們不需要額外安裝套件就可以直接使用,但在瀏覽器環境中,我們則是需要透過第三方套件來實現加密功能,但其實在瀏覽器環境中,我們也有一個內建的 API 可以直接使用,那就是 Web Crypto API。

Web Crypto API

Web Crypto API 是瀏覽器後來新加入的功能,讓你可以直接透過瀏覽器進行加解密的操作行為,這樣的好處在於,你不需要額外安裝套件就可以直接使用,而且它的 API 也非常簡單易懂,接下來我們就來介紹一下 Web Crypto API 的使用方式吧!

Web Crypto API 主要是透過 window.crypto.subtle 這個物件來操作,它提供了以下幾種常見的方法:

  • crypto.subtle.digest():雜湊(Hash)
  • crypto.subtle.encrypt() / crypto.subtle.decrypt():加密 / 解密
  • crypto.subtle.sign() / crypto.subtle.verify():簽章 / 驗章
  • crypto.subtle.generateKey():產生金鑰
  • crypto.subtle.importKey() / crypto.subtle.exportKey():匯入 / 匯出金鑰
  • crypto.subtle.deriveKey() / crypto.subtle.deriveBits():衍生金鑰 / 衍生位元

那麼接下來我們就來一一介紹這些方法的使用方式吧!

產生隨機 UUID

在介紹比較複雜的加解密之前,我們先來看一個最簡單也最實用的功能,那就是產生 UUID(Universally Unique Identifier),其實 crypto.randomUUID() 就是 Web Crypto API 所提供的方法之一,使用方式非常簡單:

1
2
const id = crypto.randomUUID();
console.log(id); // 類似 'cb729eee-87de-4aa8-b455-857bfff869e2'

是不是超級簡單?你不需要額外安裝 uuid 套件,瀏覽器本身就已經內建了這個功能,而且它產生的是 UUID v4,幾乎不可能會有重複的情況發生。

Note
如果你想要更深入了解 UUID 的使用方式,可以參考我之前寫的「不安裝 npm 套件也可以直接在瀏覽器上生成 UUID!」這篇文章。

產生隨機值

除了 UUID 之外,Web Crypto API 還提供了 crypto.getRandomValues() 方法,讓你可以產生加密安全的隨機值,這個方法比 Math.random() 更加安全,因為 Math.random() 產生的隨機數是可預測的,但 crypto.getRandomValues() 產生的隨機數是不可預測的:

1
2
3
4
// 產生 16 個隨機 bytes
const randomBytes = crypto.getRandomValues(new Uint8Array(16));
console.log(randomBytes);
// 類似 Uint8Array(16) [23, 187, 49, 200, 11, ...]

這個方法在產生加密金鑰的初始化向量(IV)、鹽值(Salt)等場景中非常實用,後面的加解密範例中你也會看到它的身影。

雜湊(Hash)

雜湊是一種單向的加密方式,也就是說你只能將資料加密,但無法將加密後的資料解密回原本的資料,這種方式通常用於密碼的儲存,因為我們不需要知道密碼的原文,只需要知道密碼是否正確就好。

常見的雜湊演算法有以下幾種:

  • SHA-1(已不建議使用)
  • SHA-256
  • SHA-384
  • SHA-512

那麼我們就來看看如何使用 Web Crypto API 來進行雜湊吧!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
async function hash(message) {
// 將字串轉換成 Uint8Array
const encoder = new TextEncoder();
const data = encoder.encode(message);

// 使用 SHA-256 進行雜湊
const hashBuffer = await crypto.subtle.digest('SHA-256', data);

// 將 ArrayBuffer 轉換成十六進位字串
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');

return hashHex;
}

hash('Hello World').then(console.log);
// 輸出:a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e

你可能會好奇,為什麼要先將字串轉換成 Uint8Array 呢?因為 Web Crypto API 的 digest 方法接受的參數是 BufferSource,也就是 ArrayBufferTypedArray,因此我們需要先將字串轉換成 Uint8Array

digest 方法回傳的也是一個 ArrayBuffer,因此我們需要再將它轉換成我們比較好閱讀的十六進位字串。

Note
如果你之前有使用過 Node.js 的 crypto 模組,你可能會發現它的 API 比較簡單,例如:crypto.createHash('sha256').update('Hello World').digest('hex'),但 Web Crypto API 的方式雖然稍微複雜一些,但它是瀏覽器原生支援的,不需要額外安裝套件。

加密與解密(AES-GCM)

接下來我們來介紹加密與解密的部分,這邊我們使用 AES-GCM 演算法來示範,AES-GCM 是一種對稱式加密演算法,也就是說加密與解密使用的是同一把金鑰。

產生金鑰

首先我們需要先產生一把金鑰:

1
2
3
4
5
6
7
8
9
10
async function generateKey() {
return await crypto.subtle.generateKey(
{
name: 'AES-GCM',
length: 256,
},
true, // 是否可匯出
['encrypt', 'decrypt'] // 金鑰用途
);
}

加密

接著我們就可以使用這把金鑰來進行加密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
async function encrypt(key, message) {
const encoder = new TextEncoder();
const data = encoder.encode(message);

// 產生一個隨機的初始化向量(IV)
const iv = crypto.getRandomValues(new Uint8Array(12));

const encryptedBuffer = await crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv: iv,
},
key,
data
);

return { iv, encryptedBuffer };
}

這邊有一個重要的概念,就是 iv(初始化向量),它的用途是讓同一段文字每次加密後的結果都不一樣,這樣可以增加安全性,而 iv 是不需要保密的,通常會跟加密後的資料一起儲存。

解密

最後我們就可以使用同一把金鑰來進行解密:

1
2
3
4
5
6
7
8
9
10
11
12
13
async function decrypt(key, iv, encryptedBuffer) {
const decryptedBuffer = await crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv: iv,
},
key,
encryptedBuffer
);

const decoder = new TextDecoder();
return decoder.decode(decryptedBuffer);
}

完整範例 AES-GCM

那麼我們就來看看完整的加密與解密範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
async function demo() {
// 產生金鑰
const key = await generateKey();

// 加密
const message = 'Hello Web Crypto API!';
const { iv, encryptedBuffer } = await encrypt(key, message);

console.log('加密後的資料:', new Uint8Array(encryptedBuffer));

// 解密
const decryptedMessage = await decrypt(key, iv, encryptedBuffer);
console.log('解密後的資料:', decryptedMessage);
// 輸出:Hello Web Crypto API!
}

demo();

是不是看起來非常簡單呢?透過 Web Crypto API,我們不需要安裝任何套件就可以直接在瀏覽器中進行加密與解密的操作。

簽章與驗章(HMAC)

HMAC(Hash-based Message Authentication Code)是一種用來驗證資料完整性與真實性的機制,它結合了雜湊函數與金鑰,讓你可以確認資料在傳輸過程中是否被篡改。

產生 HMAC 金鑰

1
2
3
4
5
6
7
8
9
10
async function generateHmacKey() {
return await crypto.subtle.generateKey(
{
name: 'HMAC',
hash: 'SHA-256',
},
true,
['sign', 'verify']
);
}

簽章

1
2
3
4
5
6
7
8
async function sign(key, message) {
const encoder = new TextEncoder();
const data = encoder.encode(message);

const signature = await crypto.subtle.sign('HMAC', key, data);

return signature;
}

驗章

1
2
3
4
5
6
7
8
async function verify(key, signature, message) {
const encoder = new TextEncoder();
const data = encoder.encode(message);

const isValid = await crypto.subtle.verify('HMAC', key, signature, data);

return isValid;
}

完整範例 HMAC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
async function hmacDemo() {
const key = await generateHmacKey();

const message = '這是一段需要驗證的資料';
const signature = await sign(key, message);

// 驗證原始資料
const isValid = await verify(key, signature, message);
console.log('驗證結果:', isValid); // true

// 驗證被篡改的資料
const isTampered = await verify(key, signature, '這是被篡改的資料');
console.log('篡改後驗證結果:', isTampered); // false
}

hmacDemo();

透過 HMAC,我們可以確認資料在傳輸過程中是否被篡改,這在 API 通訊中是非常常見的安全機制。

非對稱式加密(RSA-OAEP)

除了對稱式加密之外,Web Crypto API 也支援非對稱式加密,也就是我們俗稱的公私鑰加密,這邊我們使用 RSA-OAEP 來示範。

非對稱式加密的概念是,你會有一對金鑰,分別是「公鑰」與「私鑰」,公鑰用來加密,私鑰用來解密,而且公鑰是可以公開的,私鑰則是要保密的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
async function rsaDemo() {
// 產生 RSA 金鑰對
const keyPair = await crypto.subtle.generateKey(
{
name: 'RSA-OAEP',
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256',
},
true,
['encrypt', 'decrypt']
);

const encoder = new TextEncoder();
const message = 'Hello RSA!';
const data = encoder.encode(message);

// 使用公鑰加密
const encrypted = await crypto.subtle.encrypt(
{ name: 'RSA-OAEP' },
keyPair.publicKey,
data
);

console.log('加密後:', new Uint8Array(encrypted));

// 使用私鑰解密
const decrypted = await crypto.subtle.decrypt(
{ name: 'RSA-OAEP' },
keyPair.privateKey,
encrypted
);

const decoder = new TextDecoder();
console.log('解密後:', decoder.decode(decrypted));
// 輸出:Hello RSA!
}

rsaDemo();

實用的輔助函式

在使用 Web Crypto API 時,你會發現經常需要做 ArrayBuffer 與字串之間的轉換,因此我們可以封裝一些常用的輔助函式,讓操作更加方便:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// ArrayBuffer 轉十六進位字串
function bufferToHex(buffer) {
return Array.from(new Uint8Array(buffer))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}

// 十六進位字串轉 ArrayBuffer
function hexToBuffer(hex) {
const bytes = hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16));
return new Uint8Array(bytes).buffer;
}

// ArrayBuffer 轉 Base64
function bufferToBase64(buffer) {
const bytes = new Uint8Array(buffer);
let binary = '';
bytes.forEach(b => binary += String.fromCharCode(b));
return btoa(binary);
}

// Base64 轉 ArrayBuffer
function base64ToBuffer(base64) {
const binary = atob(base64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return bytes.buffer;
}

這些輔助函式在實際開發中非常實用,因為我們在傳輸或儲存加密後的資料時,通常會將 ArrayBuffer 轉換成 Base64 或十六進位字串。

Web Crypto API 與 Node.js crypto 的差異

你可能會好奇,Web Crypto API 與 Node.js 的 crypto 模組有什麼差異呢?這邊我們簡單比較一下:

比較項目 Web Crypto API Node.js crypto
環境 瀏覽器 Node.js
API 風格 Promise(非同步) 同步 / Stream
安裝方式 內建,不需安裝 內建,不需安裝
支援的演算法 AES、RSA、ECDSA、HMAC 等 更多演算法支援
使用限制 需要 HTTPS(Secure Context) 無限制

有一個很重要的點要注意,Web Crypto API 只能在 Secure Context(安全環境)下使用,也就是說你必須在 HTTPSlocalhost 的環境下才能使用,如果你是在一般的 HTTP 環境下,是無法使用的哦!

Note
值得一提的是,Web Crypto API 的所有方法都是非同步的(回傳 Promise),這是因為加密操作通常比較耗時,使用非同步的方式可以避免阻塞主執行緒,讓使用者體驗更好。

瀏覽器支援度

那麼 Web Crypto API 的瀏覽器支援度如何呢?其實現在主流瀏覽器都已經支援了,包含 Chrome、Firefox、Safari、Edge 等等,基本上你可以放心的使用它。

而且不只瀏覽器,其實 Node.js(v15+)、Deno、Bun 等 Runtime 也都有支援 Web Crypto API,因此你的加密程式碼可以跨環境使用,這是一個非常大的優勢。

結語

Web Crypto API 是一個非常強大的瀏覽器內建 API,它讓我們不需要額外安裝套件就可以直接在瀏覽器中進行加解密的操作,雖然它的 API 相較於 Node.js 的 crypto 模組來說稍微複雜一些(主要是因為需要處理 ArrayBuffer 的轉換),但它的好處在於:

  • 不需要安裝任何套件,減少專案的依賴
  • 瀏覽器原生支援,效能更好
  • 跨環境支援(瀏覽器、Node.js、Deno、Bun)
  • 非同步設計,不會阻塞主執行緒

如果你的專案需要在前端進行加解密的操作,那麼 Web Crypto API 絕對是你的首選哩~

你的支持會直接轉換成更多技術筆記

如果我的筆記讓你少踩一個坑、節省 Debug 的時間,
也許你可以請我喝杯咖啡,讓我繼續當個不是 Array 的 Ray ☕

buymeacoffee | line | portaly
Terminal

整理這些技術筆記真的很花時間,如果你願意 關閉 Adblock 支持我,我會把這份感謝轉換成更多「踩坑轉避坑」的內容給你!ヽ(・∀・)ノ

Advertisement

分享這篇文章

留言

© 2026 Ray. All rights reserved.

Powered by Ray Theme