JS `SubtleCrypto` (Web Crypto API) 的算法选择与安全实践

各位观众老爷,晚上好! 今天咱们聊点刺激的——Web Crypto API,特别是其中那个神秘又强大的SubtleCrypto。 这玩意儿可不是你家楼下卖煎饼果子的大妈用的,它是浏览器提供的加密解密工具箱,能让你在前端玩出各种花样,当然,前提是你得玩得明白,不然就变成给自己挖坑了。

第一章:SubtleCrypto,你是谁?从入门到放弃(误)

SubtleCrypto是Web Crypto API的核心,它提供了一系列加密算法,让你能够在浏览器端执行诸如哈希、签名、加密解密等操作。 简单来说,它就是个加密算法的百宝箱,但是,打开这个百宝箱之前,你得先知道里面都有些什么,以及怎么用才不会炸。

先看个最简单的例子,计算一段文字的SHA-256哈希值:

async function calculateSHA256(message) {
  const encoder = new TextEncoder();
  const data = encoder.encode(message);

  const hashBuffer = await crypto.subtle.digest('SHA-256', data);

  // 将ArrayBuffer转换为十六进制字符串
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  const hashHex = hashArray
    .map(b => b.toString(16).padStart(2, '0'))
    .join('');

  return hashHex;
}

calculateSHA256("Hello, SubtleCrypto!")
  .then(hash => console.log("SHA-256 Hash:", hash));

这段代码做了什么?

  1. 编码: TextEncoder将字符串转换为Uint8Array,这是Web Crypto API要求的输入格式。
  2. 哈希: crypto.subtle.digest('SHA-256', data)执行SHA-256哈希算法,返回一个ArrayBuffer
  3. 转换:ArrayBuffer转换为十六进制字符串,方便显示和使用。

是不是感觉还挺简单的? 别高兴太早,这只是冰山一角。 SubtleCrypto的强大之处在于它支持各种各样的算法,而真正的挑战在于选择合适的算法,并正确地使用它们。

第二章:算法选择的艺术:选对了上天堂,选错了…你懂的

SubtleCrypto支持的算法主要分为以下几类:

  • 哈希算法: SHA-1, SHA-256, SHA-384, SHA-512
  • 对称加密算法: AES-CBC, AES-CTR, AES-GCM, AES-KW
  • 非对称加密算法: RSA-OAEP, RSA-PSS, ECDSA, ECDH
  • HMAC: HMAC

这么多算法,到底该选哪个? 这取决于你的具体需求。

2.1 哈希算法:用于数据完整性校验

哈希算法主要用于验证数据的完整性,确保数据在传输过程中没有被篡改。 常见的哈希算法包括SHA-256、SHA-384和SHA-512。 SHA-1已经被认为是不安全的,不建议使用。

算法 安全性 性能 适用场景
SHA-256 较安全 较高 大部分场景,例如密码存储、文件校验等
SHA-384 安全 中等 对安全性要求更高的场景
SHA-512 安全 较低 极少数对安全性有极致要求的场景

选择建议: 优先选择SHA-256,除非你有特殊的需求需要更高的安全性。

2.2 对称加密算法:用于加密敏感数据

对称加密算法使用相同的密钥进行加密和解密,速度快,适合加密大量数据。 常见的对称加密算法包括AES-CBC、AES-CTR和AES-GCM。

  • AES-CBC (Cipher Block Chaining): 需要初始化向量(IV),容易受到Padding Oracle攻击,不推荐新手使用。
  • AES-CTR (Counter): 也需要初始化向量(IV),但不需要填充,性能比AES-CBC好,是比较推荐的选择。
  • AES-GCM (Galois/Counter Mode): 集成了认证功能,可以同时保证数据的机密性和完整性,是目前最推荐的对称加密算法。
  • AES-KW (Key Wrap): 用于密钥包装,保护其他密钥的安全。
算法 安全性 性能 是否需要IV 是否需要填充 适用场景
AES-CBC 较低 较高 遗留系统,不推荐新项目使用
AES-CTR 较高 较高 大部分需要对称加密的场景
AES-GCM 最高 中等 对安全性要求最高的场景,例如加密存储等
AES-KW 较高 包装其他密钥,例如加密存储密钥、传输密钥等

代码示例 (AES-GCM):

async function encryptAESGCM(plaintext, key) {
  const encoder = new TextEncoder();
  const data = encoder.encode(plaintext);

  const iv = crypto.getRandomValues(new Uint8Array(12)); // GCM需要12字节的IV

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

  // 将IV和密文合并,方便存储和传输
  const combined = new Uint8Array(iv.length + ciphertext.byteLength);
  combined.set(iv, 0);
  combined.set(new Uint8Array(ciphertext), iv.length);

  return combined;
}

async function decryptAESGCM(combined, key) {
  const iv = combined.slice(0, 12);
  const ciphertext = combined.slice(12);

  const plaintext = await crypto.subtle.decrypt(
    {
      name: "AES-GCM",
      iv: iv
    },
    key,
    ciphertext
  );

  const decoder = new TextDecoder();
  return decoder.decode(plaintext);
}

async function generateAESKey() {
  return await crypto.subtle.generateKey(
    {
      name: "AES-GCM",
      length: 256, // 密钥长度,可以是128、192或256
    },
    true, // 是否可导出
    ["encrypt", "decrypt"] // 允许的操作
  );
}

// 使用示例
async function example() {
  const key = await generateAESKey();
  const plaintext = "This is a secret message!";

  const encrypted = await encryptAESGCM(plaintext, key);
  console.log("Encrypted:", encrypted);

  const decrypted = await decryptAESGCM(encrypted, key);
  console.log("Decrypted:", decrypted);
}

example();

选择建议: 强烈推荐使用AES-GCM,它既安全又方便。 如果你对性能有极致要求,可以考虑AES-CTR。 尽量避免使用AES-CBC,除非你非常清楚自己在做什么。

2.3 非对称加密算法:用于密钥交换和数字签名

非对称加密算法使用一对密钥,公钥用于加密和验证签名,私钥用于解密和生成签名。 常见的非对称加密算法包括RSA-OAEP、RSA-PSS、ECDSA和ECDH。

  • RSA-OAEP (Optimal Asymmetric Encryption Padding): 用于加密数据,安全性较高,但性能相对较慢。
  • RSA-PSS (Probabilistic Signature Scheme): 用于数字签名,安全性较高,是RSA签名算法的推荐选择。
  • ECDSA (Elliptic Curve Digital Signature Algorithm): 基于椭圆曲线的数字签名算法,性能比RSA快,是移动端的常用选择。
  • ECDH (Elliptic Curve Diffie-Hellman): 基于椭圆曲线的密钥交换算法,用于安全地协商密钥。
算法 安全性 性能 适用场景
RSA-OAEP 较高 较低 加密少量数据,例如密钥交换
RSA-PSS 较高 较低 数字签名
ECDSA 较高 较高 数字签名,移动端常用
ECDH 较高 较高 密钥交换

代码示例 (ECDSA):

async function generateECDSAKey() {
  return await crypto.subtle.generateKey(
    {
      name: "ECDSA",
      namedCurve: "P-256", // 椭圆曲线,常用的有P-256、P-384和P-521
    },
    true, // 是否可导出
    ["sign", "verify"] // 允许的操作
  );
}

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

  const signature = await crypto.subtle.sign(
    {
      name: "ECDSA",
      hash: { name: "SHA-256" }, // 指定哈希算法
    },
    key.privateKey,
    encodedData
  );

  return new Uint8Array(signature);
}

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

  return await crypto.subtle.verify(
    {
      name: "ECDSA",
      hash: { name: "SHA-256" },
    },
    key.publicKey,
    signature,
    encodedData
  );
}

// 使用示例
async function example() {
  const keyPair = await generateECDSAKey();
  const data = "This is a message to be signed.";

  const signature = await signECDSA(data, keyPair);
  console.log("Signature:", signature);

  const isValid = await verifyECDSA(data, signature, keyPair);
  console.log("Signature is valid:", isValid);
}

example();

选择建议: 如果需要加密数据,选择RSA-OAEP。 如果需要数字签名,选择RSA-PSS或ECDSA。 如果需要在移动端进行数字签名,选择ECDSA。 如果需要密钥交换,选择ECDH。

2.4 HMAC:用于消息认证码

HMAC (Hash-based Message Authentication Code) 是一种使用密钥和哈希函数生成消息认证码的算法,用于验证消息的完整性和真实性。

算法 安全性 性能 适用场景
HMAC 较高 较高 消息认证,验证消息的完整性和真实性

代码示例 (HMAC):

async function generateHMACKey() {
  return await crypto.subtle.generateKey(
    {
      name: "HMAC",
      hash: { name: "SHA-256" }, // 指定哈希算法
      length: 256, // 密钥长度
    },
    true, // 是否可导出
    ["sign", "verify"] // 允许的操作
  );
}

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

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

  return new Uint8Array(signature);
}

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

  return await crypto.subtle.verify(
    {
      name: "HMAC",
      hash: { name: "SHA-256" },
    },
    key,
    signature,
    encodedData
  );
}

// 使用示例
async function example() {
  const key = await generateHMACKey();
  const data = "This is a message to be authenticated.";

  const signature = await signHMAC(data, key);
  console.log("Signature:", signature);

  const isValid = await verifyHMAC(data, signature, key);
  console.log("Signature is valid:", isValid);
}

example();

选择建议: 如果你需要验证消息的完整性和真实性,可以使用HMAC。

第三章:安全实践:玩火自焚还是浴火重生?

选择了合适的算法只是万里长征的第一步,如何安全地使用这些算法才是关键。 以下是一些安全实践建议:

  1. 密钥管理: 密钥是最重要的资产,必须妥善保管。 永远不要将密钥硬编码到代码中,也不要将其存储在客户端。 可以考虑使用专门的密钥管理服务,或者使用AES-KW对密钥进行包装。
  2. 初始化向量 (IV): 对于需要IV的算法(例如AES-CBC、AES-CTR和AES-GCM),必须使用随机生成的IV,并且每次加密都必须使用不同的IV。 永远不要使用固定的IV,否则会降低安全性。
  3. 密钥长度: 选择合适的密钥长度。 对于AES,建议使用256位的密钥。 对于RSA,建议使用2048位或更高的密钥。
  4. 错误处理: SubtleCrypto的API是基于Promise的,因此必须正确处理Promise的reject情况。 避免在catch块中直接忽略错误,应该将错误信息记录下来,并采取相应的措施。
  5. 权限控制: Web Crypto API的某些功能可能会受到浏览器的权限控制。 例如,某些浏览器可能不允许在非HTTPS环境下使用SubtleCrypto
  6. 更新算法: 随着密码学的发展,新的攻击方法不断涌现,因此需要定期更新使用的算法。 关注密码学领域的最新进展,及时替换不安全的算法。
  7. 不要自己发明轮子: 除非你是密码学专家,否则不要尝试自己实现加密算法。 使用经过广泛测试和验证的算法库,例如SubtleCrypto
  8. CSP (Content Security Policy): 正确配置CSP,限制可以执行JavaScript代码的来源,防止XSS攻击。
  9. 避免Side-channel攻击: 尽量避免根据加密解密时间来推断密钥的信息,例如 timing attack。

代码示例 (密钥导出和导入):

async function exportKey(key) {
  const exported = await crypto.subtle.exportKey(
    "jwk", // 导出格式,可以是jwk、raw或spki
    key
  );
  return exported;
}

async function importKey(keyData) {
  const key = await crypto.subtle.importKey(
    "jwk", // 导入格式,必须与导出格式一致
    keyData,
    {
      name: "AES-GCM",
      length: 256,
    },
    true,
    ["encrypt", "decrypt"]
  );
  return key;
}

// 使用示例
async function example() {
  const key = await generateAESKey();
  const exportedKey = await exportKey(key);
  console.log("Exported Key:", exportedKey);

  const importedKey = await importKey(exportedKey);
  console.log("Imported Key:", importedKey);
}

example();

第四章:SubtleCrypto的局限性:它不是万能的

虽然SubtleCrypto很强大,但它也有一些局限性:

  1. 浏览器兼容性: 不同的浏览器对Web Crypto API的支持程度可能不同。 在使用SubtleCrypto之前,应该检查浏览器的兼容性。
  2. 性能: SubtleCrypto的性能受到浏览器和硬件的限制。 对于需要高性能的场景,可以考虑使用WebAssembly或其他原生加密库。
  3. 安全性: SubtleCrypto的安全性取决于浏览器的实现。 如果浏览器存在安全漏洞,可能会导致SubtleCrypto的安全性受到威胁。
  4. 密钥存储: SubtleCrypto本身不提供密钥存储功能。 开发者需要自己实现密钥存储方案,并确保密钥的安全性。
  5. 无法完全防止恶意用户: 运行在客户端的 JavaScript 代码,无论加密多么强大,都存在被恶意用户破解的风险。 例如,用户可以篡改 JavaScript 代码,或者使用调试工具来获取密钥。因此,核心安全逻辑和敏感数据应该放在服务端处理。

第五章:总结:安全之路,任重道远

SubtleCrypto是一个强大的工具,但也需要谨慎使用。 选择合适的算法,遵循安全实践,并了解SubtleCrypto的局限性,才能真正发挥它的威力,保护你的数据安全。

记住,安全不是一蹴而就的,而是一个持续改进的过程。 保持学习,关注安全领域的最新动态,才能在安全之路上走得更远。

好了,今天的讲座就到这里。 谢谢大家! 下课!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注