前端加解密之 Crypto

2,935 阅读8分钟

image.png

之前写过 NodeJS 加解密之 crypto 模块,前端一般很少用这个功能,就我见过的业务来讲基本上没人用,了解了下前端浏览器支持的 Crypto 和 Node.js 模块支持的 Crypto API 还不太一样。

前端的 Crypto API 挂在浏览器的当前上下文,直接通过 crypto 就能访问。

image.png

包含一个属性两个方法:

graph LR;
A[Crypto]-->|方法|B[RandomSource];
A[Crypto]-->|属性|C[subtle];
B-->D[Crypto.getRandomValues];
B-->E[randomUUID];

randomUUID 生成 v4 的 UUID 是很有用的 API,关于加解密的全部都在 subtle 属性上,使用 subtle 一定要注意下面提示的两点。

此功能:

  1. 仅在安全上下文(HTTPS)中可用
  2. 在某些或所有支持的浏览器中可用。

加密方式

前端浏览器的 Crypto 技术提供了以下常用加密方式:

1. 对称加密和解密

对称加密是一种加密方式,使用同一个密钥进行加密和解密。Web Crypto API 提供了多种对称加密算法,如 AES、DES 和 3DES 等,可以通过这些算法来加密和解密数据。

实现过程中用到的 Web API 参考:

所有 API 中最重要的是 SubtleCrypto.generateKey() API,SubtleCrypto.generateKey() 是 Web Cryptography API 中的一个方法,用于生成密钥。

该方法有三个参数:

下面演示对称加密和解密的过程:

async function encryptSymmetricKey(key, iv, data) {
  const ciphertext = await window.crypto.subtle.encrypt(
    {
      name: "AES-GCM",
      iv,
    },
    key,
    data
  );
  return ciphertext;
}

async function decryptSymmetricKey(key, iv, ciphertext) {
  const plaintext = await window.crypto.subtle.decrypt(
    {
      name: "AES-GCM",
      iv,
    },
    key,
    ciphertext
  );
  const decoder = new TextDecoder();
  return decoder.decode(plaintext);
}

async function symmetricEncryptionDecryption() {
  const text = "Shavahn";

  // 生成一个随机的对称加密密钥
  const key = await window.crypto.subtle.generateKey(
    {
      name: "AES-GCM",
      length: 256,
    },
    true,
    ["encrypt", "decrypt"]
  );

  // 将文本编码为 ArrayBuffer
  const encoder = new TextEncoder();
  const data = encoder.encode(text);

  // 加密文本
  const iv = window.crypto.getRandomValues(new Uint8Array(12));
  const ciphertext = await encryptSymmetricKey(key, iv, data);

  // 将加密后的文本解密
  const decryptedText = await decryptSymmetricKey(key, iv, ciphertext);

  console.log("原始文本:", text);
  console.log("加密后的文本:", new Uint8Array(ciphertext));
  console.log("解密后的文本:", decryptedText);
}

symmetricEncryptionDecryption();

以下是代码的解析:

  1. async function encryptSymmetricKey(key, iv, data):这是一个异步函数,用于加密传入的数据。它使用传入的对称密钥和初始化向量 iv,以及 AES-GCM 加密算法对数据进行加密,并返回加密后的结果 ciphertext。
  2. async function decryptSymmetricKey(key, iv, ciphertext):这是一个异步函数,用于解密传入的密文。它使用传入的对称密钥和初始化向量 iv,以及 AES-GCM 加密算法对密文进行解密,并将解密后的结果以字符串形式返回。
  3. async function symmetricEncryptionDecryption():这是一个异步函数,它执行了整个加密和解密的过程。它首先生成一个随机的 AES-GCM 对称密钥,并将要加密的文本编码为 ArrayBuffer。然后,它生成一个随机的 12 字节初始化向量 iv,并使用 encryptSymmetricKey 函数对文本进行加密。最后,它使用 decryptSymmetricKey 函数对密文进行解密,并输出原始文本、加密后的文本和解密后的文本。
  4. window.crypto.subtle:这是 Web Crypto API 提供的加密和解密函数的命名空间。它提供了一组对称和非对称加密算法,包括 AES-GCM、RSA-OAEP 等。
  5. TextEncoderTextDecoder:这两个对象用于将文本字符串编码为 ArrayBuffer 和将 ArrayBuffer 解码为文本字符串。在这个例子中,我们使用它们将文本字符串编码为 ArrayBuffer,以便进行加密,并将解密后的 ArrayBuffer 解码为文本字符串。

2. 非对称加密和解密

非对称加密是一种加密方式,使用一对公钥和私钥进行加密和解密。Web Crypto API 提供了多种非对称加密算法,如 RSA 和 ECDSA 等,可以通过这些算法来加密和解密数据。

async function encryptAsymmetricKey(publicKey, data) {
  const encodedData = new TextEncoder().encode(data);
  const encryptedData = await window.crypto.subtle.encrypt(
    {
      name: "RSA-OAEP",
    },
    publicKey,
    encodedData
  );
  return encryptedData;
}

async function decryptAsymmetricKey(privateKey, encryptedData) {
  const decryptedData = await window.crypto.subtle.decrypt(
    {
      name: "RSA-OAEP",
    },
    privateKey,
    encryptedData
  );
  return new TextDecoder().decode(decryptedData);
}

async function asymmetricEncryptionDecryption() {
  const text = "Shavahn";

  // 生成一对非对称加密密钥
  const keyPair = await window.crypto.subtle.generateKey(
    {
      name: "RSA-OAEP",
      modulusLength: 2048,
      publicExponent: new Uint8Array([1, 0, 1]),
      hash: { name: "SHA-256" },
    },
    true,
    ["encrypt", "decrypt"]
  );

  // 加密文本
  const encryptedData = await encryptAsymmetricKey(keyPair.publicKey, text);

  // 将加密后的文本解密
  const decryptedText = await decryptAsymmetricKey(
    keyPair.privateKey,
    encryptedData
  );

  console.log("原始文本:", text);
  console.log("加密后的文本:", new Uint8Array(encryptedData));
  console.log("解密后的文本:", decryptedText);
}

asymmetricEncryptionDecryption();

3. 数字签名和验证

数字签名是一种用于验证数据完整性和身份认证的技术,Web Crypto API 提供了多种数字签名算法,如 RSA 和 ECDSA 等,可以通过这些算法来对数据进行签名和验证。

async function signMessage(privateKey, message) {
  const encoder = new TextEncoder();
  const data = encoder.encode(message);

  const signature = await window.crypto.subtle.sign(
    {
      name: "ECDSA",
      hash: { name: "SHA-256" },
    },
    privateKey,
    data
  );
  return signature;
}

async function verifySignature(publicKey, message, signature) {
  const encoder = new TextEncoder();
  const data = encoder.encode(message);

  const isVerified = await window.crypto.subtle.verify(
    {
      name: "ECDSA",
      hash: { name: "SHA-256" },
    },
    publicKey,
    signature,
    data
  );
  return isVerified;
}

async function signatureAndVerification() {
  const message = "hello world";

  // 生成椭圆曲线密钥对
  const keyPair = await window.crypto.subtle.generateKey(
    {
      name: "ECDSA",
      namedCurve: "P-256",
    },
    true,
    ["sign", "verify"]
  );

  // 对消息进行数字签名
  const signature = await signMessage(keyPair.privateKey, message);

  // 验证数字签名
  const isVerified = await verifySignature(keyPair.publicKey, message, signature);

  console.log("原始消息:", message);
  console.log("数字签名:", new Uint8Array(signature));
  console.log("验证结果:", isVerified);
}

signatureAndVerification();

如何把密钥发给后端

前端通过 window.crypto.subtle.generateKey 生成的公钥可以通过以下步骤发送给后端:

  1. 从生成的密钥对中获取公钥,使用 exportKey 方法将其导出成 ArrayBuffer 格式。
const keyPair = await window.crypto.subtle.generateKey(
  { name: "RSA-OAEP", modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" },
  true,
  ["encrypt", "decrypt"]
);

const publicKey = await window.crypto.subtle.exportKey(
  "spki",
  keyPair.publicKey
);
  1. 将导出的 ArrayBuffer 转换为 Base64 编码的字符串格式,以便可以通过 HTTP 请求发送给后端。
const publicKeyBase64 = btoa(String.fromCharCode.apply(null, new Uint8Array(publicKey)));
  1. 将 Base64 编码的字符串作为请求参数发送到后端,后端可以将其解码并使用相应的加密算法进行加密操作。
// 发送公钥给后端
fetch('/api/sendPublicKey', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ publicKey: publicKeyBase64 }),
})

上面就是简单的一种演示,只要用 exportKey 拿到 Key,你想怎么发送就怎么发送。

摘要算法

async function generateHMAC(key, data) {
  const encoder = new TextEncoder();
  const encodedKey = encoder.encode(key);
  const encodedData = encoder.encode(data);

  const cryptoKey = await window.crypto.subtle.importKey(
    "raw",
    encodedKey,
    {
      name: "HMAC",
      hash: "SHA-256"
    },
    false,
    ["sign"]
  );

  const signature = await window.crypto.subtle.sign(
    {
      name: "HMAC",
      hash: "SHA-256"
    },
    cryptoKey,
    encodedData
  );

  return signature;
}

async function verifyHMAC(key, data, signature) {
  const encoder = new TextEncoder();
  const encodedKey = encoder.encode(key);
  const encodedData = encoder.encode(data);

  const cryptoKey = await window.crypto.subtle.importKey(
    "raw",
    encodedKey,
    {
      name: "HMAC",
      hash: "SHA-256"
    },
    false,
    ["verify"]
  );

  const result = await window.crypto.subtle.verify(
    {
      name: "HMAC",
      hash: "SHA-256"
    },
    cryptoKey,
    signature,
    encodedData
  );

  return result;
}

async function hmacExample() {
  const key = "my secret key";
  const data = "Shavahn";
  
  const signature = await generateHMAC(key, data);
  console.log("HMAC 签名:", new Uint8Array(signature));

  const isValid = await verifyHMAC(key, data, signature);
  console.log("HMAC 签名是否有效:", isValid);
}

hmacExample();

主要用途

前端浏览器的 Crypto 技术可以用于以下主要用途:

1. 安全的数据传输

通过使用前端浏览器的 Crypto 技术,开发者可以对数据进行加密和解密,从而确保数据在传输过程中的安全性。例如,在使用 HTTPS 协议时,可以使用浏览器内置的加密算法对传输的数据进行加密,以保证数据的机密性和完整性。

2. 数字签名验证

通过使用前端浏览器的 Crypto 技术,可以对数据进行数字签名和验证,从而确保数据的完整性和真实性。例如,在电子商务中,可以使用数字签名技术对交易数据进行签名和验证,以保证交易数据的真实性和完整性。

3. 认证和授权

通过使用前端浏览器的 Crypto 技术,可以对用户进行认证和授权。例如,在使用 OAuth2 认证时,可以使用前端浏览器的 Crypto 技术生成安全的随机数,以保证授权码的安全性。

总结

前端浏览器的 Crypto 技术是实现安全的前端应用程序的关键技术之一。Web Crypto API 提供了多种密码学算法和函数,使得前端开发人员可以在浏览器中实现安全的数据传输、数字签名和验证等功能,同时确保数据的安全性和完整性。使用前端浏览器的 Crypto 技术可以使开发人员更轻松地实现密码学相关的功能,同时提高应用程序的安全性和保护用户数据的隐私。因此,开发人员应该了解和熟悉前端浏览器的 Crypto 技术,并在必要时使用它来保证应用程序的安全性。