2026-02-14 14:53:41 +08:00

84 lines
2.2 KiB
TypeScript

import { webcrypto } from 'node:crypto'
/**
* Get key material from password
*
* 从密码获取密钥材料
*
* @see https://developer.mozilla.org/zh-CN/docs/Web/API/SubtleCrypto/deriveKey#pbkdf2_2
* @param password - Password string / 密码字符串
* @returns CryptoKey / 加密密钥
*/
function getKeyMaterial(password: string) {
const enc = new TextEncoder()
return webcrypto.subtle.importKey(
'raw',
enc.encode(password),
'PBKDF2',
false,
['deriveBits', 'deriveKey'],
)
}
/**
* Get derived key from key material
*
* 从密钥材料获取派生密钥
*
* @param keyMaterial - Key material / 密钥材料
* @param salt - Salt for key derivation / 密钥派生盐值
* @returns Derived CryptoKey / 派生加密密钥
*/
function getCryptoDeriveKey(keyMaterial: CryptoKey | webcrypto.CryptoKey, salt: Uint8Array) {
return webcrypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: salt as unknown as ArrayBuffer,
iterations: 100000,
hash: 'SHA-256',
},
keyMaterial,
{
name: 'AES-CBC',
length: 256,
},
true,
['encrypt', 'decrypt'],
)
}
/**
* Encrypt content using AES-CBC
*
* 使用 AES-CBC 加密内容
*
* @see https://github.com/mdn/dom-examples/blob/main/web-crypto/encrypt-decrypt/aes-cbc.js
* @param content - Content to encrypt / 要加密的内容
* @param options - Encryption options / 加密选项
* @param options.password - Password for encryption / 加密密码
* @param options.iv - Initialization vector / 初始化向量
* @param options.salt - Salt for key derivation / 密钥派生盐值
* @returns Encrypted content / 加密后的内容
*/
export async function encryptContent(content: string, options: {
password: string
iv: Uint8Array
salt: Uint8Array
}) {
const { password, iv, salt } = options
const keyMaterial = await getKeyMaterial(password)
const key = await getCryptoDeriveKey(keyMaterial, salt)
const enc = new TextEncoder()
const cipherTextData = await webcrypto.subtle.encrypt(
{
name: 'AES-CBC',
iv: iv as unknown as ArrayBuffer,
},
key,
enc.encode(content),
)
return String.fromCharCode(...new Uint8Array(cipherTextData))
}