JavaScript内核与高级编程之:`JavaScript`的`Web`加密:`SubtleCrypto API` 在浏览器中的应用。

各位观众老爷,早上好!今天咱们聊点刺激的,啊不,是严肃的——JavaScript的Web加密,特别是那个听起来很高大上的SubtleCrypto API。别害怕,保证用大白话给你讲明白,让你觉得加密这玩意儿也没那么神秘。

一、加密:你以为的安全感,可能只是心理安慰

想象一下,你给女神/男神发了条消息:“我喜欢你很久了!”,结果TA回了句:“你谁啊?”。为了防止这种尴尬情况发生,你决定把消息加密一下,这样就算被别人看到了,也不知道你发了啥。

这就是加密的基本作用:保护你的数据不被未经授权的人看到或篡改。 在Web开发中,加密更是至关重要,毕竟谁也不想自己的密码、银行卡号啥的在网络上裸奔。

二、SubtleCrypto API:浏览器自带的加密工具箱

SubtleCrypto API 是浏览器提供的用于执行加密操作的一组接口。它允许你在客户端(也就是用户的浏览器)进行各种加密、解密、签名、验证等操作,而无需依赖服务器。

简单来说,SubtleCrypto API 就像一个加密工具箱,里面装满了各种加密算法,你可以根据需要选择合适的工具来保护你的数据。

三、SubtleCrypto API 的基本用法:Promise 套娃

SubtleCrypto API 的方法都是异步的,并且返回 Promise 对象。这意味着你需要使用 async/await 或者 .then() 来处理异步操作的结果。

先来个简单的例子,生成一个随机数:

async function generateRandomNumber(length) {
  try {
    const array = new Uint8Array(length); // 创建一个指定长度的Uint8Array
    window.crypto.getRandomValues(array); // 使用getRandomValues填充随机值
    return array; // 返回包含随机数的Uint8Array
  } catch (error) {
    console.error("生成随机数出错:", error);
    return null;
  }
}

// 使用示例
async function main() {
  const randomNumber = await generateRandomNumber(16); // 生成16字节的随机数
  if (randomNumber) {
    console.log("生成的随机数:", Array.from(randomNumber)); // 将Uint8Array转换为数组并打印
  }
}

main();

这个例子虽然简单,但展示了 SubtleCrypto API 的基本使用方式:

  1. window.crypto: SubtleCrypto API 的入口点。
  2. getRandomValues(): 用于生成加密安全的随机数。
  3. Uint8Array: 一种用于存储字节的类型化数组。

四、加密算法:工具箱里的十八般武艺

SubtleCrypto API 支持多种加密算法,包括对称加密、非对称加密、哈希算法等等。

  • 对称加密 (Symmetric Encryption):加密和解密使用相同的密钥。速度快,适合加密大量数据。常用的算法有:AES、DES、Triple DES。
  • 非对称加密 (Asymmetric Encryption):加密和解密使用不同的密钥(公钥和私钥)。安全性高,但速度慢。常用的算法有:RSA、ECDSA、ECDH。
  • 哈希算法 (Hashing):将任意长度的数据转换为固定长度的哈希值(也称为摘要)。用于验证数据的完整性,不能用于解密。常用的算法有:SHA-1、SHA-256、SHA-384、SHA-512。

五、对称加密:AES 搞起来!

AES (Advanced Encryption Standard) 是一种广泛使用的对称加密算法。咱们来看一个使用 AES-CBC 模式进行加密和解密的例子:

async function generateKey(algorithm) {
  return await window.crypto.subtle.generateKey(
    algorithm,
    true, // 是否可提取密钥
    ["encrypt", "decrypt"] // 密钥用途
  );
}

async function encrypt(key, iv, data) {
  const encoded = new TextEncoder().encode(data); // 将字符串转换为ArrayBuffer
  return await window.crypto.subtle.encrypt(
    {
      name: "AES-CBC",
      iv: iv // 初始向量
    },
    key,
    encoded
  );
}

async function decrypt(key, iv, encryptedData) {
  const decrypted = await window.crypto.subtle.decrypt(
    {
      name: "AES-CBC",
      iv: iv
    },
    key,
    encryptedData
  );
  return new TextDecoder().decode(decrypted); // 将ArrayBuffer转换为字符串
}

// 使用示例
async function main() {
  try {
    const keyAlgorithm = {
      name: "AES-CBC",
      length: 256 // 密钥长度,可以是128、192或256
    };

    const key = await generateKey(keyAlgorithm);

    const iv = window.crypto.getRandomValues(new Uint8Array(16)); // 生成16字节的初始向量

    const data = "我喜欢你很久了!";
    const encryptedData = await encrypt(key, iv, data);

    const decryptedData = await decrypt(key, iv, encryptedData);

    console.log("原始数据:", data);
    console.log("加密后的数据:", encryptedData);
    console.log("解密后的数据:", decryptedData);

    // 验证是否解密成功
    if (data === decryptedData) {
      console.log("解密成功!");
    } else {
      console.log("解密失败!");
    }

  } catch (error) {
    console.error("发生错误:", error);
  }
}

main();

代码解释:

  1. generateKey(): 生成一个 AES 密钥。algorithm 参数指定了密钥的算法和长度。extractable 参数指定了密钥是否可以被提取(通常设置为 true)。keyUsages 参数指定了密钥的用途(例如,"encrypt""decrypt")。
  2. encrypt(): 使用 AES-CBC 模式加密数据。iv 是初始向量,用于增加加密的随机性。data 是要加密的数据。
  3. decrypt(): 使用 AES-CBC 模式解密数据。
  4. TextEncoderTextDecoder: 用于在字符串和 ArrayBuffer 之间进行转换。

六、非对称加密:RSA 保护你的秘密

RSA 是一种常用的非对称加密算法。咱们来看一个使用 RSA-OAEP 模式进行加密和解密的例子:

async function generateKeyPair() {
  return await window.crypto.subtle.generateKey(
    {
      name: "RSA-OAEP",
      modulusLength: 2048, // 密钥长度,通常是2048或4096
      publicExponent: new Uint8Array([0x01, 0x00, 0x01]), // 65537
      hash: "SHA-256" // 哈希算法
    },
    true, // 是否可提取密钥
    ["encrypt", "decrypt"] // 密钥用途
  );
}

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

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

// 使用示例
async function main() {
  try {
    const keyPair = await generateKeyPair();

    const data = "我喜欢你很久了!";
    const encryptedData = await encrypt(keyPair.publicKey, data);

    const decryptedData = await decrypt(keyPair.privateKey, encryptedData);

    console.log("原始数据:", data);
    console.log("加密后的数据:", encryptedData);
    console.log("解密后的数据:", decryptedData);

    // 验证是否解密成功
    if (data === decryptedData) {
      console.log("解密成功!");
    } else {
      console.log("解密失败!");
    }

  } catch (error) {
    console.error("发生错误:", error);
  }
}

main();

代码解释:

  1. generateKeyPair(): 生成一个 RSA 密钥对(公钥和私钥)。modulusLength 参数指定了密钥的长度(通常是 2048 或 4096)。publicExponent 参数指定了公钥指数(通常是 65537)。hash 参数指定了哈希算法(例如,"SHA-256")。
  2. encrypt(): 使用 RSA-OAEP 模式加密数据。publicKey 是公钥。
  3. decrypt(): 使用 RSA-OAEP 模式解密数据。privateKey 是私钥。

七、哈希算法:验证数据的完整性

哈希算法用于生成数据的摘要,可以用来验证数据的完整性。咱们来看一个使用 SHA-256 算法生成哈希值的例子:

async function hash(data) {
  const encoded = new TextEncoder().encode(data);
  const digest = await window.crypto.subtle.digest("SHA-256", encoded);
  return digest;
}

function toHexString(buffer) {
  return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join('');
}

// 使用示例
async function main() {
  try {
    const data = "我喜欢你很久了!";
    const digest = await hash(data);

    console.log("原始数据:", data);
    console.log("哈希值:", toHexString(digest));

  } catch (error) {
    console.error("发生错误:", error);
  }
}

main();

代码解释:

  1. hash(): 使用 SHA-256 算法生成哈希值。
  2. toHexString(): 将 ArrayBuffer 转换为十六进制字符串。

八、数字签名:证明你是你

数字签名用于验证数据的来源和完整性。它结合了非对称加密和哈希算法。

  1. 签名过程: 发送者使用私钥对数据的哈希值进行加密,生成数字签名。
  2. 验证过程: 接收者使用发送者的公钥解密数字签名,得到哈希值。然后,接收者使用相同的哈希算法计算数据的哈希值。如果两个哈希值相同,则说明数据是完整的,并且来自发送者。

咱们来看一个使用 ECDSA 算法进行签名和验证的例子:

async function generateKeyPair() {
  return await window.crypto.subtle.generateKey(
    {
      name: "ECDSA",
      namedCurve: "P-256" // 椭圆曲线,可以是P-256、P-384或P-521
    },
    true, // 是否可提取密钥
    ["sign", "verify"] // 密钥用途
  );
}

async function sign(privateKey, data) {
  const encoded = new TextEncoder().encode(data);
  return await window.crypto.subtle.sign(
    {
      name: "ECDSA",
      hash: { name: "SHA-256" } // 哈希算法
    },
    privateKey,
    encoded
  );
}

async function verify(publicKey, signature, data) {
  const encoded = new TextEncoder().encode(data);
  return await window.crypto.subtle.verify(
    {
      name: "ECDSA",
      hash: { name: "SHA-256" }
    },
    publicKey,
    signature,
    encoded
  );
}

// 使用示例
async function main() {
  try {
    const keyPair = await generateKeyPair();

    const data = "我喜欢你很久了!";
    const signature = await sign(keyPair.privateKey, data);

    const isValid = await verify(keyPair.publicKey, signature, data);

    console.log("原始数据:", data);
    console.log("签名:", signature);
    console.log("签名是否有效:", isValid);

    if (isValid) {
      console.log("签名验证成功!");
    } else {
      console.log("签名验证失败!");
    }

  } catch (error) {
    console.error("发生错误:", error);
  }
}

main();

代码解释:

  1. generateKeyPair(): 生成一个 ECDSA 密钥对(公钥和私钥)。namedCurve 参数指定了椭圆曲线(例如,"P-256")。
  2. sign(): 使用 ECDSA 算法对数据进行签名。privateKey 是私钥。hash 参数指定了哈希算法(例如,"SHA-256")。
  3. verify(): 使用 ECDSA 算法验证签名。publicKey 是公钥。signature 是签名。

九、一些注意事项:安全第一!

  • 不要在客户端存储密钥: 密钥应该保存在安全的地方,例如服务器端或硬件安全模块 (HSM)。
  • 使用加密安全的随机数: 不要使用 Math.random() 生成随机数,因为它不够安全。应该使用 window.crypto.getRandomValues()
  • 选择合适的加密算法和模式: 不同的加密算法和模式有不同的安全性和性能特点。应该根据实际需求选择合适的算法和模式。
  • 了解浏览器的兼容性: 不同的浏览器对 SubtleCrypto API 的支持程度可能不同。应该测试你的代码在不同浏览器上的兼容性。
  • 不要自己发明加密算法: 加密算法的设计非常复杂,容易出错。应该使用经过安全专家审查的成熟算法。

十、总结:加密,让你的数据更安全

SubtleCrypto API 是一个强大的 Web 加密工具,可以让你在客户端执行各种加密操作。通过合理使用 SubtleCrypto API,你可以有效地保护你的数据,让你的 Web 应用更加安全。

虽然加密很强大,但它也不是万能的。安全是一个持续的过程,需要不断地学习和改进。希望今天的讲座能让你对 Web 加密有一个更深入的了解。

好了,今天的讲座就到这里,谢谢大家!如果有什么问题,欢迎提问。下次有机会再见!

发表回复

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