Skip to content

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}`
}

兼容性与注意事项

浏览器兼容性

APIChromeFirefoxSafariEdge
crypto.getRandomValues()11+21+6.1+12+
crypto.randomUUID()92+95+15.4+92+
crypto.subtle37+34+11+79+

HTTPS 要求

  • SubtleCrypto API 只能在安全上下文(HTTPS)中使用
  • localhost 被视为安全上下文,可用于开发测试
  • 在非安全上下文中,crypto.subtleundefined

性能考虑

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)
    // 处理每个块
  }
}

安全最佳实践

  1. 密钥管理

    • 永远不要在代码中硬编码密钥
    • 使用密钥派生函数(PBKDF2)从密码生成密钥
    • 定期轮换密钥
  2. 随机数生成

    • 始终使用 crypto.getRandomValues() 而非 Math.random()
    • 为每次加密操作生成新的 IV(初始化向量)
  3. 算法选择

    • 对称加密推荐 AES-GCM(256位)
    • 非对称加密推荐 RSA-OAEP(2048位或更高)
    • 哈希推荐 SHA-256 或更高
  4. 错误处理

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')
  }
}

参考资源

基于 MIT 许可发布