之前写过 NodeJS 加解密之 crypto 模块,前端一般很少用这个功能,就我见过的业务来讲基本上没人用,了解了下前端浏览器支持的 Crypto 和 Node.js 模块支持的 Crypto API 还不太一样。
前端的 Crypto API 挂在浏览器的当前上下文,直接通过 crypto 就能访问。
包含一个属性两个方法:
graph LR;
A[Crypto]-->|方法|B[RandomSource];
A[Crypto]-->|属性|C[subtle];
B-->D[Crypto.getRandomValues];
B-->E[randomUUID];
randomUUID 生成 v4 的 UUID 是很有用的 API,关于加解密的全部都在 subtle 属性上,使用 subtle 一定要注意下面提示的两点。
此功能:
加密方式
前端浏览器的 Crypto 技术提供了以下常用加密方式:
1. 对称加密和解密
对称加密是一种加密方式,使用同一个密钥进行加密和解密。Web Crypto API 提供了多种对称加密算法,如 AES、DES 和 3DES 等,可以通过这些算法来加密和解密数据。
实现过程中用到的 Web API 参考:
- SubtleCrypto
- SubtleCrypto.generateKey()
- SubtleCrypto.encrypt()
- SubtleCrypto.decrypt()
- Crypto.getRandomValues()
- TextEncoder
- TextDecoder
所有 API 中最重要的是 SubtleCrypto.generateKey() API,SubtleCrypto.generateKey()
是 Web Cryptography API 中的一个方法,用于生成密钥。
该方法有三个参数:
-
第一个参数是一个包含算法名称和密钥长度等信息的对象。
- 对于 RSASSA-PKCS1-v1_5、RSA-PSS 或 RSA-OAEP 算法:传递
RsaHashedKeyGenParams
(en-US) 对象。 - 对于 ECDSA 或 ECDH 算法:传递
EcKeyGenParams
(en-US) 对象。 - 对于 HMAC 算法:传递
HmacKeyGenParams
(en-US) 对象。 - 对于 AES-CTR、AES-CBC、AES-GCM 或 AES-KW (en-US) 算法:传递
AesKeyGenParams
(en-US) 对象。
- 对于 RSASSA-PKCS1-v1_5、RSA-PSS 或 RSA-OAEP 算法:传递
第二个参数是一个布尔值,表示生成的密钥是否可被
SubtleCrypto.exportKey()
和SubtleCrypto.wrapKey()
(en-US) 方法导出。-
第三个参数是一个数组,表示密钥的用途,数组元素可能的值有:
-
encrypt
:密钥可用于加密消息。 -
decrypt
:密钥可用于解密消息。 -
sign
:密钥可用于对消息进行签名。 -
verify
:密钥可用于验证 (en-US)签名。 -
deriveKey
:密钥可用于派生新的密钥。 -
deriveBits
:密钥可用于派生比特序列。 -
wrapKey
:密钥可被包装 (en-US)。 -
unwrapKey
:密钥可被解包装 (en-US)。
-
下面演示对称加密和解密的过程:
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();
以下是代码的解析:
-
async function encryptSymmetricKey(key, iv, data)
:这是一个异步函数,用于加密传入的数据。它使用传入的对称密钥和初始化向量 iv,以及 AES-GCM 加密算法对数据进行加密,并返回加密后的结果 ciphertext。 -
async function decryptSymmetricKey(key, iv, ciphertext)
:这是一个异步函数,用于解密传入的密文。它使用传入的对称密钥和初始化向量 iv,以及 AES-GCM 加密算法对密文进行解密,并将解密后的结果以字符串形式返回。 -
async function symmetricEncryptionDecryption()
:这是一个异步函数,它执行了整个加密和解密的过程。它首先生成一个随机的 AES-GCM 对称密钥,并将要加密的文本编码为 ArrayBuffer。然后,它生成一个随机的 12 字节初始化向量 iv,并使用 encryptSymmetricKey 函数对文本进行加密。最后,它使用 decryptSymmetricKey 函数对密文进行解密,并输出原始文本、加密后的文本和解密后的文本。 -
window.crypto.subtle
:这是 Web Crypto API 提供的加密和解密函数的命名空间。它提供了一组对称和非对称加密算法,包括 AES-GCM、RSA-OAEP 等。 -
TextEncoder
和TextDecoder
:这两个对象用于将文本字符串编码为 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
生成的公钥可以通过以下步骤发送给后端:
- 从生成的密钥对中获取公钥,使用
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
);
- 将导出的
ArrayBuffer
转换为 Base64 编码的字符串格式,以便可以通过 HTTP 请求发送给后端。
const publicKeyBase64 = btoa(String.fromCharCode.apply(null, new Uint8Array(publicKey)));
- 将 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 技术,并在必要时使用它来保证应用程序的安全性。