- 首頁
- 加密文件
加密文件
瞭解我們的 AES-256-GCM 加密如何工作。包含實現客戶端加密的程式碼示例的完整文件。
工作原理
金鑰派生
您的密碼用於使用 PBKDF2、SHA-256 和 600,000 次迭代派生 256 位 AES 金鑰。這使得暴力破解在計算上非常昂貴。
隨機值
每次加密都會使用加密安全的隨機數生成器生成隨機的 128 位鹽和 96 位 IV(初始化向量)。
AES-256-GCM 加密
資料使用 AES-256-GCM(伽羅瓦/計數器模式)加密,該模式同時提供機密性和真實性驗證。
輸出格式
輸出格式為 v1:salt:iv:ciphertext,其中所有元件都經過 base64 編碼。
加密函式
派生金鑰
從密碼派生 AES 金鑰
使用 PBKDF2、SHA-256 和 600,000 次迭代從密碼派生 256 位 AES-GCM 金鑰。
async function deriveAesGcmKey(password, saltBytes) {
const te = new TextEncoder();
const baseKey = await crypto.subtle.importKey(
'raw',
te.encode(password),
{ name: 'PBKDF2' },
false,
['deriveKey']
);
return crypto.subtle.deriveKey(
{
name: 'PBKDF2',
hash: 'SHA-256',
salt: saltBytes,
iterations: 600000, // OWASP 2025 recommended minimum
},
baseKey,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
}
輔助函式
輔助函式
用於位元組陣列的 Base64 編碼和解碼工具。
function bytesToBase64(bytes) {
let binary = '';
const len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
function base64ToBytes(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;
}
加密
加密資料
使用隨機鹽和 IV 的 AES-256-GCM 加密明文。返回格式化的有效載荷。
/**
* Build combined key from password and salt key
* Format: "password:saltKey"
*/
function buildCombinedKey(password, saltKey) {
if (saltKey) {
return password + ':' + saltKey;
}
return password;
}
async function encrypt(plainText, password, saltKey = '') {
const te = new TextEncoder();
const salt = crypto.getRandomValues(new Uint8Array(16));
const iv = crypto.getRandomValues(new Uint8Array(12));
const combinedKey = buildCombinedKey(password, saltKey);
const key = await deriveAesGcmKey(combinedKey, salt);
const cipherBuf = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
te.encode(plainText)
);
return [
'v1',
bytesToBase64(salt),
bytesToBase64(iv),
bytesToBase64(new Uint8Array(cipherBuf)),
].join(':');
}
解密
解密資料
使用原始密碼將 v1 有效載荷解密回明文。
/**
* Build combined key from password and salt key
* Format: "password:saltKey"
*/
function buildCombinedKey(password, saltKey) {
if (saltKey) {
return password + ':' + saltKey;
}
return password;
}
async function decrypt(payload, password, saltKey = '') {
const td = new TextDecoder();
const [version, saltB64, ivB64, cipherB64] = payload.split(':');
if (version !== 'v1') {
throw new Error('Unsupported payload version');
}
const salt = base64ToBytes(saltB64);
const iv = base64ToBytes(ivB64);
const ciphertext = base64ToBytes(cipherB64);
const combinedKey = buildCombinedKey(password, saltKey);
const key = await deriveAesGcmKey(combinedKey, salt);
const plainBuf = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv },
key,
ciphertext
);
return td.decode(plainBuf);
}
完整示例
即用程式碼
將此完整示例複製並貼上到您的瀏覽器控制檯或 JavaScript 檔案中。
/**
* AES-256-GCM Encryption/Decryption Example
* Encrypts "Hello World" and decrypts it back
*/
// Salt key (RK) - used to strengthen password-based encryption
const SALT_KEY = '9x=1KO2tUFw#G:ARZd>Ff)s(^H+DWY4MpgJ:Cp_pCUU|og$>6a.bS.;ij9Wnw';
// Helper functions
function bytesToBase64(bytes) {
let binary = '';
const len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
function base64ToBytes(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;
}
/**
* Build combined key from password and salt key
* Format: "password:saltKey"
*/
function buildCombinedKey(password, saltKey) {
if (saltKey) {
return password + ':' + saltKey;
}
return password;
}
/**
* Version configuration for encryption payloads
* v1: 150,000 iterations (legacy)
* v2: 600,000 iterations (current, OWASP 2025 recommended)
*/
const VERSION_CONFIG = {
v1: { iterations: 150000 },
v2: { iterations: 600000 },
};
const CURRENT_VERSION = 'v2';
// Derive AES-256 key from password using PBKDF2
async function deriveAesGcmKey(password, saltBytes, iterations) {
const te = new TextEncoder();
const baseKey = await crypto.subtle.importKey(
'raw',
te.encode(password),
{ name: 'PBKDF2' },
false,
['deriveKey']
);
return crypto.subtle.deriveKey(
{
name: 'PBKDF2',
hash: 'SHA-256',
salt: saltBytes,
iterations: iterations,
},
baseKey,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
}
// Encrypt function (uses v2 with 600,000 iterations)
async function encrypt(plainText, password, saltKey = '') {
const te = new TextEncoder();
const salt = crypto.getRandomValues(new Uint8Array(16));
const iv = crypto.getRandomValues(new Uint8Array(12));
const combinedKey = buildCombinedKey(password, saltKey);
const iterations = VERSION_CONFIG[CURRENT_VERSION].iterations;
const key = await deriveAesGcmKey(combinedKey, salt, iterations);
const cipherBuf = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
te.encode(plainText)
);
return [
CURRENT_VERSION,
bytesToBase64(salt),
bytesToBase64(iv),
bytesToBase64(new Uint8Array(cipherBuf)),
].join(':');
}
// Decrypt function (supports both v1 and v2)
async function decrypt(payload, password, saltKey = '') {
const td = new TextDecoder();
const [version, saltB64, ivB64, cipherB64] = payload.split(':');
const versionConfig = VERSION_CONFIG[version];
if (!versionConfig) {
throw new Error('Unsupported payload version: ' + version);
}
const salt = base64ToBytes(saltB64);
const iv = base64ToBytes(ivB64);
const ciphertext = base64ToBytes(cipherB64);
const combinedKey = buildCombinedKey(password, saltKey);
const key = await deriveAesGcmKey(combinedKey, salt, versionConfig.iterations);
const plainBuf = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv },
key,
ciphertext
);
return td.decode(plainBuf);
}
// Example usage
(async () => {
const message = 'Hello World';
const password = 'my-secret-password';
console.log('Original:', message);
// Encrypt with salt key (uses v2)
const encrypted = await encrypt(message, password, SALT_KEY);
console.log('Encrypted:', encrypted);
// Decrypt with same salt key (supports v1 and v2)
const decrypted = await decrypt(encrypted, password, SALT_KEY);
console.log('Decrypted:', decrypted);
})();
預期輸出
預期控制檯輸出
原始: Hello World 加密後: v1:aBcDeFgHiJkLmNoP...:qRsTuVwXyZ...:encrypted_data... 解密後: Hello World
注意: 由於隨機鹽和 IV 的生成,每次加密輸出都會不同,但解密總是返回原始訊息。