一、简介
SubtleCrypto API 是 Web Crypto API 的核心组成部分,它为网页应用提供了一套完整的加密功能接口。通过这个 API,开发者可以在浏览器中实现各种密码学操作,包括加密、解密、签名、验证等功能。
为什么叫SubtleCrypto
MDN解释如下:
有些浏览器实现了叫作 Crypto 的接口,但是它缺乏良好的定义,或在密码学上是不健全的。为了避免混乱,这个接口的方法和属性已经被实现 Web Crypto API 的浏览器所移除,并且所有的 Web Crypto API 方法都可以在新的接口中使用:SubtleCrypto。Crypto.subtle 属性可以获取到一个实现了新接口的对象。
简单来说就是有些浏览器已经实现了自己的Crypto
,和规范冲突,所以为了避免混乱,而使用SubtleCrypto
,可以用crypto.subtle
获取到该实例
二、SubtleCrypto API 的核心功能
SubtleCrypto API 主要包含下面这些功能:
密钥管理:
- generateKey(): 生成新的对称密钥或非对称密钥对。
- importKey(): 从各种格式(例如 JWK、PKCS#8)导入密钥。
- exportKey(): 将密钥导出为各种格式。
- wrapKey(): 使用包装密钥加密敏感密钥,以便在不安全环境中传输和存储。
- unwrapKey(): 解密并导入已包装的密钥。
加密和解密:
- encrypt(): 使用指定的算法和密钥加密数据。
- decrypt(): 使用指定的算法和密钥解密数据。
签名和验证:
- sign(): 使用指定的算法和密钥对数据进行签名。
- verify(): 验证数据的签名。
摘要:
- digest(): 生成数据的哈希值(摘要)。
密钥派生:
- deriveKey(): 从主密钥派生新的密钥。
- deriveBits(): 从主密钥派生伪随机位。
三、常用方法示例
1、计算摘要
使用SubtleCrypto
的digest
方法计算数据的摘要(即哈希值)
参数:
algorithm
可以是一个字符串或一个仅有 name 字符串属性的对象。该字符串为使用的哈希函数的名称。支持的算法以及对比如下
算法 | 输出长度(比特) | 块大小(比特) |
---|---|---|
SHA-1 | 160 | 512 |
SHA-256 | 256 | 512 |
SHA-384 | 384 | 1024 |
SHA-512 | 512 | 1024 |
警告:现在,SHA-1 被认为是易受攻击的,不应将其用于加密应用程序。
data
一个包含将计算摘要的数据的对象,支持的类型如下。
ArrayBuffer
TypedArray
DataView
返回值:一个 Promise
,会兑现一个包含摘要值的 ArrayBuffer
。
示例:
/**
* 支持的哈希算法类型
*/
type HashAlgorithm = 'SHA-1' | 'SHA-256' | 'SHA-384' | 'SHA-512'
/**
* 计算给定数据的哈希值
* @param data - 要计算哈希的数据,支持 ArrayBuffer、string、Blob
* @param algorithm - 使用的哈希算法,支持 SHA-1、SHA-256、SHA-384、SHA-512,默认为 SHA-256
* @returns 返回十六进制格式的哈希字符串
* @example
* // 计算字符串的 MD5
* const hash = await calculateHash('hello') // 返回 "5d41402abc4b2a76b9719d911017c592"
*
* // 计算二进制数据的 SHA-256
* const buffer = new ArrayBuffer(8)
* const hash = await calculateHash(buffer, 'SHA-256')
*
* // 计算Blob的哈希
* const blob = new Blob(['hello'])
* const hash = await calculateHash(blob)
*/
export async function calculateHash(
data: ArrayBuffer | string | Blob,
algorithm: HashAlgorithm = 'SHA-256'
): Promise<string> {
const cryptoImpl = await getCrypto()
let buffer: ArrayBuffer
if (typeof data === 'string') {
buffer = new TextEncoder().encode(data).buffer
} else if (data instanceof Blob) {
buffer = await data.arrayBuffer()
} else {
buffer = data
}
const hashBuffer = await cryptoImpl.subtle.digest(algorithm, buffer)
const hashArray = Array.from(new Uint8Array(hashBuffer))
return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
}
2、数据加密
使用SubtleCrypto
的encrypt
方法加密数据
参数:
algorithm
一个对象,用于指定使用的算法,以及需要的任何额外的参数:
- 使用
RSA-OAEP
,则传入RsaOaepParams
对象。 - 使用
AES-CTR
,则传入AesCtrParams
对象。 - 使用
AES-CBC
,则传入AesCbcParams
对象。 - 使用
AES-GCM
,则传入AesGcmParams
对象。
建议优先用
GCM
模式,它可以检测密文是否已被攻击者篡改。
key
一个包含了密钥的、用于加密的 CryptoKey
对象。
data
一个包含了待加密的数据(也称为明文)的 ArrayBuffer
、TypedArray
或 DataView
对象。
返回值
一个 Promise
,会兑现一个包含密文的 ArrayBuffer
。
示例:
/**
* 使用AES-GCM算法加密数据
* @param data - 要加密的字符串数据
* @param keyString - 加密密钥字符串
* @returns 返回加密后的ArrayBuffer数据
* @example
* const encrypted = await encrypt("hello", "secretkey")
*/
export async function encrypt(data: string, keyString: string) {
const cryptoImpl = await getCrypto()
const encoder = new TextEncoder()
// 首先通过 SHA-256 生成固定长度的密钥材料
const keyHash = await calculateHash(keyString, 'SHA-256')
const keyBytes = new Uint8Array(32) // 使用 256 位密钥
for (let i = 0; i < 32; i++) {
keyBytes[i] = parseInt(keyHash.slice(i * 2, i * 2 + 2), 16)
}
const keyMaterial = await cryptoImpl.subtle.importKey('raw', keyBytes, 'AES-GCM', false, [
'encrypt'
])
const encodedData = encoder.encode(data)
// 这里的 iv 可以自定义为随机数组,但解密时要保持一致
const encryptedData = await cryptoImpl.subtle.encrypt(
{ name: 'AES-GCM', iv: new Uint8Array(12) },
keyMaterial,
encodedData
)
return encryptedData
}
3、数据解密
使用SubtleCrypto
的decrypt
方法加密数据
参数:
algorithm
一个对象,用于指定使用的算法,以及需要的任何额外的参数:
- 使用
RSA-OAEP
,则传入RsaOaepParams
对象。 - 使用
AES-CTR
,则传入AesCtrParams
对象。 - 使用
AES-CBC
,则传入AesCbcParams
对象。 - 使用
AES-GCM
,则传入AesGcmParams
对象。
建议优先用
GCM
模式,它可以检测密文是否已被攻击者篡改。
key
一个包含了密钥的 CryptoKey
对象,用于解密。
data
一个包含了待解密的数据(也称为密文)的 ArrayBuffer
、TypedArray
或 DataView
对象。
返回值
一个 Promise
,会兑现一个包含明文的 ArrayBuffer
。
示例:
/**
* 使用AES-GCM算法解密数据
* @param encryptedData - 加密后的ArrayBuffer数据
* @param keyString - 解密密钥字符串
* @returns 返回解密后的字符串
* @example
* const decrypted = await decrypt(encryptedData, "secretkey")
*/
export async function decrypt(encryptedData: ArrayBuffer, keyString: string) {
const cryptoImpl = await getCrypto()
const decoder = new TextDecoder()
// 使用相同的方法生成密钥
const keyHash = await calculateHash(keyString, 'SHA-256')
const keyBytes = new Uint8Array(32)
for (let i = 0; i < 32; i++) {
keyBytes[i] = parseInt(keyHash.slice(i * 2, i * 2 + 2), 16)
}
const keyMaterial = await cryptoImpl.subtle.importKey('raw', keyBytes, 'AES-GCM', false, [
'decrypt'
])
// 这里的 iv 和加密用的 iv 保持一致
const decryptedData = await cryptoImpl.subtle.decrypt(
{ name: 'AES-GCM', iv: new Uint8Array(12) },
keyMaterial,
encryptedData
)
return decoder.decode(decryptedData)
}
四、附上我封装的crypto工具
该工具提供了哈希计算,数据加解密等操作,以及Arraybuffer
和Base64
互转方法
同时兼容nodejs
和浏览器
环境
点击下载:crypto.ts
参考: