主题
Crypto API
简介
Crypto API 是 Web Cryptography API 的一部分,提供了在浏览器中执行基本加密操作的能力。它通过 window.crypto 对象暴露,主要用于生成安全的随机数和执行各种加密操作。
主要特点:
- 提供密码学安全的随机数生成
- 支持多种加密算法(AES、RSA、HMAC等)
- 基于 Promise 的异步 API
- 在 HTTPS 环境下完全可用
crypto.getRandomValues()
生成密码学安全的伪随机数,用于替代 Math.random()。
语法
javascript
crypto.getRandomValues(typedArray)参数
typedArray: 整数类型的 TypedArray(Int8Array、Uint8Array、Int16Array 等)
返回值
返回传入的 TypedArray,其内容已被随机值填充。
示例
javascript
// 生成随机字节数组
const array = new Uint8Array(16)
crypto.getRandomValues(array)
console.log(array)
// 生成随机 ID
function generateRandomId() {
const array = new Uint32Array(1)
crypto.getRandomValues(array)
return array[0].toString(16)
}
// 生成 UUID v4
function generateUUID() {
const array = new Uint8Array(16)
crypto.getRandomValues(array)
// 设置版本(4)和变体位
array[6] = (array[6] & 0x0f) | 0x40
array[8] = (array[8] & 0x3f) | 0x80
const hex = Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('')
return `${hex.substr(0, 8)}-${hex.substr(8, 4)}-${hex.substr(12, 4)}-${hex.substr(16, 4)}-${hex.substr(20)}`
}
// 生成随机密码
function generatePassword(length = 16) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*'
const array = new Uint8Array(length)
crypto.getRandomValues(array)
return Array.from(array, (byte) => chars[byte % chars.length]).join('')
}注意事项
- 不支持 Float32Array 和 Float64Array
- 数组长度不能超过 65536 字节
- 比 Math.random() 更安全,适合生成密码、令牌等
crypto.randomUUID()
直接生成符合 RFC 4122 标准的 UUID v4。
语法
javascript
crypto.randomUUID()返回值
返回一个字符串形式的 UUID。
示例
javascript
const uuid = crypto.randomUUID()
console.log(uuid) // "36b8f84d-df4e-4d49-b662-bcde71a8764f"
// 批量生成 UUID
function generateUUIDs(count) {
return Array.from({ length: count }, () => crypto.randomUUID())
}
const ids = generateUUIDs(5)浏览器支持
- Chrome 92+
- Firefox 95+
- Safari 15.4+
SubtleCrypto API
通过 crypto.subtle 访问,提供更底层的加密功能。
主要方法
encrypt()/decrypt()- 加密/解密数据sign()/verify()- 签名/验证digest()- 生成哈希摘要generateKey()- 生成密钥importKey()/exportKey()- 导入/导出密钥deriveBits()/deriveKey()- 派生密钥
生成哈希摘要
javascript
// SHA-256 哈希
async function sha256(message) {
const msgBuffer = new TextEncoder().encode(message)
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer)
const hashArray = Array.from(new Uint8Array(hashBuffer))
return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
}
// 使用示例
sha256('Hello, World!').then((hash) => {
console.log(hash) // "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"
})
// 支持的算法
async function hash(algorithm, message) {
const msgBuffer = new TextEncoder().encode(message)
const hashBuffer = await crypto.subtle.digest(algorithm, msgBuffer)
const hashArray = Array.from(new Uint8Array(hashBuffer))
return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
}
// SHA-1
await hash('SHA-1', 'test')
// SHA-256
await hash('SHA-256', 'test')
// SHA-384
await hash('SHA-384', 'test')
// SHA-512
await hash('SHA-512', 'test')对称加密(AES-GCM)
javascript
// 生成密钥
async function generateAESKey() {
return await crypto.subtle.generateKey(
{
name: 'AES-GCM',
length: 256,
},
true, // 是否可导出
['encrypt', 'decrypt'],
)
}
// 加密
async function encryptData(key, data) {
const iv = crypto.getRandomValues(new Uint8Array(12)) // 初始化向量
const encodedData = new TextEncoder().encode(data)
const encryptedData = await crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv: iv,
},
key,
encodedData,
)
return {
iv: Array.from(iv),
data: Array.from(new Uint8Array(encryptedData)),
}
}
// 解密
async function decryptData(key, encryptedObj) {
const decryptedData = await crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv: new Uint8Array(encryptedObj.iv),
},
key,
new Uint8Array(encryptedObj.data),
)
return new TextDecoder().decode(decryptedData)
}
// 使用示例
;(async () => {
const key = await generateAESKey()
const encrypted = await encryptData(key, 'Secret message')
console.log('Encrypted:', encrypted)
const decrypted = await decryptData(key, encrypted)
console.log('Decrypted:', decrypted) // "Secret message"
})()非对称加密(RSA-OAEP)
javascript
// 生成 RSA 密钥对
async function generateRSAKeyPair() {
return await crypto.subtle.generateKey(
{
name: 'RSA-OAEP',
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256',
},
true,
['encrypt', 'decrypt'],
)
}
// 使用公钥加密
async function encryptWithPublicKey(publicKey, data) {
const encodedData = new TextEncoder().encode(data)
const encrypted = await crypto.subtle.encrypt(
{
name: 'RSA-OAEP',
},
publicKey,
encodedData,
)
return new Uint8Array(encrypted)
}
// 使用私钥解密
async function decryptWithPrivateKey(privateKey, encryptedData) {
const decrypted = await crypto.subtle.decrypt(
{
name: 'RSA-OAEP',
},
privateKey,
encryptedData,
)
return new TextDecoder().decode(decrypted)
}
// 使用示例
;(async () => {
const { publicKey, privateKey } = await generateRSAKeyPair()
const encrypted = await encryptWithPublicKey(publicKey, 'Secret')
const decrypted = await decryptWithPrivateKey(privateKey, encrypted)
console.log(decrypted) // "Secret"
})()HMAC 签名与验证
javascript
// 生成 HMAC 密钥
async function generateHMACKey() {
return await crypto.subtle.generateKey(
{
name: 'HMAC',
hash: 'SHA-256',
},
true,
['sign', 'verify'],
)
}
// 生成签名
async function signData(key, data) {
const encodedData = new TextEncoder().encode(data)
const signature = await crypto.subtle.sign('HMAC', key, encodedData)
return new Uint8Array(signature)
}
// 验证签名
async function verifySignature(key, signature, data) {
const encodedData = new TextEncoder().encode(data)
return await crypto.subtle.verify('HMAC', key, signature, encodedData)
}
// 使用示例
;(async () => {
const key = await generateHMACKey()
const data = 'Important message'
const signature = await signData(key, data)
const isValid = await verifySignature(key, signature, data)
console.log('Signature valid:', isValid) // true
const isTampered = await verifySignature(key, signature, 'Modified message')
console.log('Tampered valid:', isTampered) // false
})()密钥导入导出
javascript
// 导出密钥为 JWK 格式
async function exportKey(key) {
return await crypto.subtle.exportKey('jwk', key)
}
// 从 JWK 导入密钥
async function importKey(jwk, algorithm, usages) {
return await crypto.subtle.importKey('jwk', jwk, algorithm, true, usages)
}
// 使用示例
;(async () => {
const key = await generateAESKey()
const exported = await exportKey(key)
console.log('Exported key:', exported)
const imported = await importKey(exported, { name: 'AES-GCM', length: 256 }, ['encrypt', 'decrypt'])
})()
// 导出为 raw 格式
async function exportRawKey(key) {
const rawKey = await crypto.subtle.exportKey('raw', key)
return new Uint8Array(rawKey)
}
// 从 raw 导入
async function importRawKey(rawKey) {
return await crypto.subtle.importKey('raw', rawKey, { name: 'AES-GCM' }, true, ['encrypt', 'decrypt'])
}密钥派生(PBKDF2)
javascript
// 从密码派生密钥
async function deriveKeyFromPassword(password, salt, iterations = 100000) {
const passwordBuffer = new TextEncoder().encode(password)
// 导入密码作为基础密钥
const baseKey = await crypto.subtle.importKey('raw', passwordBuffer, 'PBKDF2', false, ['deriveBits', 'deriveKey'])
// 派生 AES 密钥
return await crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: salt,
iterations: iterations,
hash: 'SHA-256',
},
baseKey,
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt'],
)
}
// 使用示例
;(async () => {
const password = 'userPassword123'
const salt = crypto.getRandomValues(new Uint8Array(16))
const derivedKey = await deriveKeyFromPassword(password, salt)
console.log('Derived key:', derivedKey)
// 可以使用该密钥进行加密
const encrypted = await encryptData(derivedKey, 'Sensitive data')
})()实用工具函数
ArrayBuffer 与字符串转换
javascript
// ArrayBuffer 转 Base64
function arrayBufferToBase64(buffer) {
const bytes = new Uint8Array(buffer)
let binary = ''
for (let i = 0; i < bytes.length; i++) {
binary += String.fromCharCode(bytes[i])
}
return btoa(binary)
}
// Base64 转 ArrayBuffer
function base64ToArrayBuffer(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 转十六进制字符串
function arrayBufferToHex(buffer) {
return Array.from(new Uint8Array(buffer))
.map((b) => b.toString(16).padStart(2, '0'))
.join('')
}
// 十六进制字符串转 ArrayBuffer
function hexToArrayBuffer(hex) {
const bytes = new Uint8Array(hex.length / 2)
for (let i = 0; i < hex.length; i += 2) {
bytes[i / 2] = parseInt(hex.substr(i, 2), 16)
}
return bytes.buffer
}常见应用场景
文件加密
javascript
async function encryptFile(file, password) {
const salt = crypto.getRandomValues(new Uint8Array(16))
const key = await deriveKeyFromPassword(password, salt)
const fileData = await file.arrayBuffer()
const iv = crypto.getRandomValues(new Uint8Array(12))
const encryptedData = await crypto.subtle.encrypt({ name: 'AES-GCM', iv: iv }, key, fileData)
// 将 salt、iv 和加密数据组合
const result = new Uint8Array(salt.length + iv.length + encryptedData.byteLength)
result.set(salt, 0)
result.set(iv, salt.length)
result.set(new Uint8Array(encryptedData), salt.length + iv.length)
return new Blob([result])
}
async function decryptFile(encryptedBlob, password) {
const data = new Uint8Array(await encryptedBlob.arrayBuffer())
const salt = data.slice(0, 16)
const iv = data.slice(16, 28)
const encryptedData = data.slice(28)
const key = await deriveKeyFromPassword(password, salt)
const decryptedData = await crypto.subtle.decrypt({ name: 'AES-GCM', iv: iv }, key, encryptedData)
return new Blob([decryptedData])
}数字签名验证
javascript
// 生成签名密钥对
async function generateSigningKeyPair() {
return await crypto.subtle.generateKey(
{
name: 'RSASSA-PKCS1-v1_5',
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256',
},
true,
['sign', 'verify'],
)
}
// 对数据签名
async function signMessage(privateKey, message) {
const encodedMessage = new TextEncoder().encode(message)
const signature = await crypto.subtle.sign('RSASSA-PKCS1-v1_5', privateKey, encodedMessage)
return arrayBufferToBase64(signature)
}
// 验证签名
async function verifyMessage(publicKey, message, signatureBase64) {
const encodedMessage = new TextEncoder().encode(message)
const signature = base64ToArrayBuffer(signatureBase64)
return await crypto.subtle.verify('RSASSA-PKCS1-v1_5', publicKey, signature, encodedMessage)
}安全令牌生成
javascript
// 生成安全的访问令牌
function generateSecureToken(length = 32) {
const array = new Uint8Array(length)
crypto.getRandomValues(array)
return arrayBufferToBase64(array.buffer).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
}
// 生成 CSRF 令牌
function generateCSRFToken() {
return generateSecureToken(32)
}
// 生成 API 密钥
function generateAPIKey() {
const timestamp = Date.now().toString(36)
const random = generateSecureToken(24)
return `${timestamp}-${random}`
}兼容性与注意事项
浏览器兼容性
| API | Chrome | Firefox | Safari | Edge |
|---|---|---|---|---|
| crypto.getRandomValues() | 11+ | 21+ | 6.1+ | 12+ |
| crypto.randomUUID() | 92+ | 95+ | 15.4+ | 92+ |
| crypto.subtle | 37+ | 34+ | 11+ | 79+ |
HTTPS 要求
- SubtleCrypto API 只能在安全上下文(HTTPS)中使用
- localhost 被视为安全上下文,可用于开发测试
- 在非安全上下文中,
crypto.subtle为undefined
性能考虑
javascript
// 批量操作时使用 Promise.all
async function hashMultipleMessages(messages) {
return await Promise.all(messages.map((msg) => sha256(msg)))
}
// 大文件分块处理
async function hashLargeFile(file) {
const chunkSize = 1024 * 1024 // 1MB
const chunks = Math.ceil(file.size / chunkSize)
for (let i = 0; i < chunks; i++) {
const chunk = file.slice(i * chunkSize, (i + 1) * chunkSize)
// 处理每个块
}
}安全最佳实践
密钥管理
- 永远不要在代码中硬编码密钥
- 使用密钥派生函数(PBKDF2)从密码生成密钥
- 定期轮换密钥
随机数生成
- 始终使用
crypto.getRandomValues()而非Math.random() - 为每次加密操作生成新的 IV(初始化向量)
- 始终使用
算法选择
- 对称加密推荐 AES-GCM(256位)
- 非对称加密推荐 RSA-OAEP(2048位或更高)
- 哈希推荐 SHA-256 或更高
错误处理
javascript
async function safeDecrypt(key, data) {
try {
return await decryptData(key, data)
} catch (error) {
console.error('Decryption failed:', error)
// 不要泄露具体错误信息给用户
throw new Error('Invalid data or key')
}
}