Web 加密 API 实用指南

2025-06-07

Web 加密 API 实用指南

客户端加密是我一直以来想在Octo中实现的一个功能。当我终于要开始着手实现它时,却惊讶地发现关于这个主题的实际示例竟然如此稀少。MDN 上的文档很丰富,但实现它需要反复跳转到各个方法的 API。希望这篇文章能对那些寻求指导的人有所帮助。

注意:Web Cryptography API 是异步的,因此为了简洁起见,我在本文中使用了 async/await 语法。

SubtleCrypto

Web 加密 API 最初通过名为Crypto的非标准接口公开,但后来通过名为SubtleCrypto的新接口实现了标准化。本文将重点介绍公开于 的 SubtleCrypto 接口window.crypto.subtle

加密

就本文而言,我们将使用对称算法。公钥(非对称)策略根据密钥大小对可加密的数据量有严格的限制: 。对称加密使用相同的密钥来加密和解密数据,因此没有相同的限制。目前支持的算法(keyBits / 8) - padding有几种,但推荐的对称算法适用于其认证模式AES-GCM

生成密钥

首先,我们需要生成一个对称密钥。

// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/generateKey
const generateKey = async () => {
  return window.crypto.subtle.generateKey({
    name: 'AES-GCM',
    length: 256,
  }, true, ['encrypt', 'decrypt'])
}
Enter fullscreen mode Exit fullscreen mode

编码数据

在加密数据之前,我们必须先将其编码成字节流。我们可以用这个类轻松实现这一点TextEncoder。这个小工具稍后会在函数中使用encrypt

// https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder
const encode = (data) => {
  const encoder = new TextEncoder()

  return encoder.encode(data)
}
Enter fullscreen mode Exit fullscreen mode

生成初始化向量(IV)

简而言之,IV 为我们的加密策略引入了真正的随机性。当使用相同的密钥加密多组数据时,可以推导出加密数据块之间的关系,从而泄露部分或全部原始消息。IV 确保输入数据中重复的字符序列在生成的密码中产生不同的字节序列。将 IV 与加密消息一起以纯文本形式存储是完全安全的,稍后我们需要这样做来解密消息。

// https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues
const generateIv = () => {
  // https://developer.mozilla.org/en-US/docs/Web/API/AesGcmParams
  return window.crypto.getRandomValues(new Uint8Array(12))
}
Enter fullscreen mode Exit fullscreen mode

我们从不想对给定的密钥使用相同的 IV,因此最好将自动 IV 生成纳入我们的加密策略中,就像我们稍后会做的那样。

加密数据

现在我们已经准备好了所有实用程序,可以实现我们的encrypt功能了!如上所述,我们需要它返回密码初始向量 (IV),以便稍后解密密码。

// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/encrypt
const encrypt = async (data, key) => {
  const encoded = encode(data)
  const iv = generateIv()
  const cipher = await window.crypto.subtle.encrypt({
    name: 'AES-GCM',
    iv: iv,
  }, key, encoded)

  return {
    cipher,
    iv,
  }
}
Enter fullscreen mode Exit fullscreen mode

传输和存储

大多数实际的加密应用都涉及加密数据的传输或存储。使用 SubtleCrypto 加密数据时,生成的密码和初始向量 (IV) 会以原始二进制数据缓冲区的形式表示。这并非理想的传输或存储格式,因此我们接下来将讨论打包和解包。

包装数据

由于数据通常以 JSON 格式传输并存储在数据库中,因此将数据打包成可移植的格式是有意义的。我们将把二进制数据缓冲区转换为 base64 编码的字符串。根据您的用例,base64 编码完全是可选的,但我发现它有助于提高数据的可移植性,满足您的需求。

// https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
const pack = (buffer) => {
  return window.btoa(
    String.fromCharCode.apply(null, new Uint8Array(buffer))
  )
}
Enter fullscreen mode Exit fullscreen mode

解包数据

一旦我们的打包数据被传输、存储并随后检索,我们只需逆转该过程即可。我们将把 base64 编码的字符串转换回原始二进制缓冲区。

// https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
const unpack = (packed) => {
  const string = window.atob(packed)
  const buffer = new ArrayBuffer(string.length)
  const bufferView = new Uint8Array(buffer)

  for (let i = 0; i < string.length; i++) {
    bufferView[i] = string.charCodeAt(i)
  }

  return buffer
}
Enter fullscreen mode Exit fullscreen mode

解密

我们已经到了最后冲刺阶段!这个过程的最后一步是解密数据,看看那些甜蜜的秘密。和解包一样,我们只需要逆向加密过程即可。

解码数据

解密后,我们需要将得到的字节流解码回其原始形式。我们可以用TextDecoder类来实现这一点。这个实用程序稍后会被我们的函数使用decrypt

// https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder
const decode = (bytestream) => {
  const decoder = new TextDecoder()

  return decoder.decode(bytestream)
}
Enter fullscreen mode Exit fullscreen mode

解密数据

现在我们只需要实现该decrypt函数。如前所述,我们不仅需要提供密钥,还需要提供加密步骤中使用的IV。

// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/decrypt
const decrypt = async (cipher, key, iv) => {
  const encoded = await window.crypto.subtle.decrypt({
    name: 'AES-GCM',
    iv: iv,
  }, key, cipher)

  return decode(encoded)
}
Enter fullscreen mode Exit fullscreen mode

付诸实践

让我们编写一个应用程序!现在所有实用程序都已构建完毕,我们只需使用它们。我们将加密、打包并将数据传输到安全端点。然后,我们将检索、解包并解密原始消息。

const app = async () => {
  // encrypt message
  const first = 'Hello, World!'
  const key = await generateKey()
  const { cipher, iv } = await encrypt(first, key)

  // pack and transmit
  await fetch('/secure-api', {
    method: 'POST',
    body: JSON.stringify({
      cipher: pack(cipher),
      iv: pack(iv),
    }),
  })

  // retrieve
  const response = await fetch('/secure-api').then(res => res.json())

  // unpack and decrypt message
  const final = await decrypt(unpack(response.cipher), key, unpack(response.iv))
  console.log(final) // logs 'Hello, World!'
}
Enter fullscreen mode Exit fullscreen mode

就这样!我们成功实现了客户端加密。

最后,我想再次分享octo,一款面向开发者的写作应用。它是免费的,开源的,如果你试用一下,我会非常高兴。谢谢大家,祝你编程愉快!✌️

文章来源:https://dev.to/voracious/a-practical-guide-to-the-web-cryptography-api-4o8n
PREV
JavaScript 开发人员的 10 个面试问题
NEXT
Useful links for code newbies who want to study more than coding Contents Summary