各位观众老爷,早上好!今天咱们聊点刺激的,啊不,是严肃的——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
的基本使用方式:
window.crypto
:SubtleCrypto API
的入口点。getRandomValues()
: 用于生成加密安全的随机数。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();
代码解释:
generateKey()
: 生成一个 AES 密钥。algorithm
参数指定了密钥的算法和长度。extractable
参数指定了密钥是否可以被提取(通常设置为true
)。keyUsages
参数指定了密钥的用途(例如,"encrypt"
、"decrypt"
)。encrypt()
: 使用 AES-CBC 模式加密数据。iv
是初始向量,用于增加加密的随机性。data
是要加密的数据。decrypt()
: 使用 AES-CBC 模式解密数据。TextEncoder
和TextDecoder
: 用于在字符串和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();
代码解释:
generateKeyPair()
: 生成一个 RSA 密钥对(公钥和私钥)。modulusLength
参数指定了密钥的长度(通常是 2048 或 4096)。publicExponent
参数指定了公钥指数(通常是 65537)。hash
参数指定了哈希算法(例如,"SHA-256"
)。encrypt()
: 使用 RSA-OAEP 模式加密数据。publicKey
是公钥。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();
代码解释:
hash()
: 使用 SHA-256 算法生成哈希值。toHexString()
: 将ArrayBuffer
转换为十六进制字符串。
八、数字签名:证明你是你
数字签名用于验证数据的来源和完整性。它结合了非对称加密和哈希算法。
- 签名过程: 发送者使用私钥对数据的哈希值进行加密,生成数字签名。
- 验证过程: 接收者使用发送者的公钥解密数字签名,得到哈希值。然后,接收者使用相同的哈希算法计算数据的哈希值。如果两个哈希值相同,则说明数据是完整的,并且来自发送者。
咱们来看一个使用 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();
代码解释:
generateKeyPair()
: 生成一个 ECDSA 密钥对(公钥和私钥)。namedCurve
参数指定了椭圆曲线(例如,"P-256"
)。sign()
: 使用 ECDSA 算法对数据进行签名。privateKey
是私钥。hash
参数指定了哈希算法(例如,"SHA-256"
)。verify()
: 使用 ECDSA 算法验证签名。publicKey
是公钥。signature
是签名。
九、一些注意事项:安全第一!
- 不要在客户端存储密钥: 密钥应该保存在安全的地方,例如服务器端或硬件安全模块 (HSM)。
- 使用加密安全的随机数: 不要使用
Math.random()
生成随机数,因为它不够安全。应该使用window.crypto.getRandomValues()
。 - 选择合适的加密算法和模式: 不同的加密算法和模式有不同的安全性和性能特点。应该根据实际需求选择合适的算法和模式。
- 了解浏览器的兼容性: 不同的浏览器对
SubtleCrypto API
的支持程度可能不同。应该测试你的代码在不同浏览器上的兼容性。 - 不要自己发明加密算法: 加密算法的设计非常复杂,容易出错。应该使用经过安全专家审查的成熟算法。
十、总结:加密,让你的数据更安全
SubtleCrypto API
是一个强大的 Web 加密工具,可以让你在客户端执行各种加密操作。通过合理使用 SubtleCrypto API
,你可以有效地保护你的数据,让你的 Web 应用更加安全。
虽然加密很强大,但它也不是万能的。安全是一个持续的过程,需要不断地学习和改进。希望今天的讲座能让你对 Web 加密有一个更深入的了解。
好了,今天的讲座就到这里,谢谢大家!如果有什么问题,欢迎提问。下次有机会再见!