|
|
@@ -1,24 +1,115 @@
|
|
|
+import CryptoJS from 'crypto-js/core';
|
|
|
+import 'crypto-js/lib-typedarrays';
|
|
|
+import 'crypto-js/cipher-core';
|
|
|
import AES from 'crypto-js/aes';
|
|
|
import encBase64 from 'crypto-js/enc-base64';
|
|
|
import encUtf8 from 'crypto-js/enc-utf8';
|
|
|
+import modeCtr from 'crypto-js/mode-ctr';
|
|
|
+import modeEcb from 'crypto-js/mode-ecb';
|
|
|
import MD5 from 'crypto-js/md5';
|
|
|
import Rabbit from 'crypto-js/rabbit';
|
|
|
import SHA1 from 'crypto-js/sha1';
|
|
|
import SHA256 from 'crypto-js/sha256';
|
|
|
|
|
|
-export function md5(str: string, lowerCase?: boolean): string {
|
|
|
+import { bigEndianToLittleEndian, littleEndianToBigEndian } from '@/utils/bytesUtils';
|
|
|
+
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
+// 常量
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
+
|
|
|
+const WordArray = CryptoJS.lib.WordArray as typeof CryptoJS.lib.WordArray & {
|
|
|
+ create(words?: number[] | Uint8Array, sigBytes?: number): CryptoJS.lib.WordArray;
|
|
|
+};
|
|
|
+const CipherParams = CryptoJS.lib.CipherParams as {
|
|
|
+ create(cfg: {
|
|
|
+ ciphertext: CryptoJS.lib.WordArray;
|
|
|
+ iv?: CryptoJS.lib.WordArray;
|
|
|
+ }): CryptoJS.lib.CipherParams;
|
|
|
+};
|
|
|
+
|
|
|
+const KEY_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
|
+const KEY_CHARS_LEN = KEY_CHARS.length;
|
|
|
+const MAX_ALLOWED = 256 - (256 % KEY_CHARS_LEN);
|
|
|
+
|
|
|
+const AES_KEY_BYTES = 32;
|
|
|
+const AES_IV_BYTES = 16;
|
|
|
+const RABBIT_KEY_BYTES = 16;
|
|
|
+const RABBIT_IV_BYTES = 8;
|
|
|
+
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
+// 内部工具函数
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
+
|
|
|
+function wordsToBytes(words: number[], sigBytes: number, littleEndian?: boolean): Uint8Array {
|
|
|
+ const out = new Uint8Array(sigBytes);
|
|
|
+ for (let i = 0; i < sigBytes; i++) {
|
|
|
+ out[i] = (words[i >>> 2]! >>> (24 - (i % 4) * 8)) & 0xff;
|
|
|
+ }
|
|
|
+ return littleEndian ? bigEndianToLittleEndian(out) : out;
|
|
|
+}
|
|
|
+
|
|
|
+function bytesToWordArray(bytes: Uint8Array, littleEndian?: boolean): CryptoJS.lib.WordArray {
|
|
|
+ const input = littleEndian ? littleEndianToBigEndian(bytes) : bytes;
|
|
|
+ return WordArray.create(input);
|
|
|
+}
|
|
|
+
|
|
|
+function wordArrayToBytes(wa: CryptoJS.lib.WordArray, littleEndian?: boolean): Uint8Array {
|
|
|
+ return wordsToBytes(wa.words, wa.sigBytes, littleEndian);
|
|
|
+}
|
|
|
+
|
|
|
+function padBytes(buf: Uint8Array, len: number): Uint8Array {
|
|
|
+ if (buf.length >= len) return buf.subarray(0, len);
|
|
|
+ const out = new Uint8Array(len);
|
|
|
+ out.set(buf);
|
|
|
+ return out;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 计算字符串的 MD5
|
|
|
+ */
|
|
|
+export function stringMd5(str: string, lowerCase?: boolean): string {
|
|
|
return lowerCase ? String(MD5(str)).toLowerCase() : String(MD5(str)).toUpperCase();
|
|
|
}
|
|
|
|
|
|
-export function sha1(str: string, lowerCase?: boolean): string {
|
|
|
+/**
|
|
|
+ * 计算二进制数据的 MD5,返回 16 字节
|
|
|
+ */
|
|
|
+export function bytesMd5(bytes: Uint8Array): Uint8Array {
|
|
|
+ return wordArrayToBytes(MD5(bytesToWordArray(bytes)) as CryptoJS.lib.WordArray);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 计算字符串的 SHA-1
|
|
|
+ */
|
|
|
+export function stringSha1(str: string, lowerCase?: boolean): string {
|
|
|
return lowerCase ? String(SHA1(str)).toLowerCase() : String(SHA1(str)).toUpperCase();
|
|
|
}
|
|
|
|
|
|
-export function sha256(str: string, lowerCase?: boolean): string {
|
|
|
+/**
|
|
|
+ * 计算二进制数据的 SHA-1,返回 20 字节
|
|
|
+ */
|
|
|
+export function bytesSha1(bytes: Uint8Array): Uint8Array {
|
|
|
+ return wordArrayToBytes(SHA1(bytesToWordArray(bytes)) as CryptoJS.lib.WordArray);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 计算字符串的 SHA-256
|
|
|
+ */
|
|
|
+export function stringSha256(str: string, lowerCase?: boolean): string {
|
|
|
return lowerCase ? String(SHA256(str)).toLowerCase() : String(SHA256(str)).toUpperCase();
|
|
|
}
|
|
|
|
|
|
-export function base64encode(raw: string): string {
|
|
|
+/**
|
|
|
+ * 计算二进制数据的 SHA-256,返回 32 字节
|
|
|
+ */
|
|
|
+export function bytesSha256(bytes: Uint8Array): Uint8Array {
|
|
|
+ return wordArrayToBytes(SHA256(bytesToWordArray(bytes)) as CryptoJS.lib.WordArray);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 对字符串进行 Base64 编码
|
|
|
+ */
|
|
|
+export function stringBase64encode(raw: string): string {
|
|
|
try {
|
|
|
return encBase64.stringify(encUtf8.parse(raw));
|
|
|
} catch {
|
|
|
@@ -26,7 +117,21 @@ export function base64encode(raw: string): string {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-export function base64decode(str: string): string {
|
|
|
+/**
|
|
|
+ * 对二进制数据进行 Base64 编码
|
|
|
+ */
|
|
|
+export function bytesBase64encode(bytes: Uint8Array): string {
|
|
|
+ try {
|
|
|
+ return encBase64.stringify(bytesToWordArray(bytes));
|
|
|
+ } catch {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 对字符串进行 Base64 解码
|
|
|
+ */
|
|
|
+export function stringBase64decode(str: string): string {
|
|
|
try {
|
|
|
return encBase64.parse(str).toString(encUtf8);
|
|
|
} catch {
|
|
|
@@ -35,41 +140,62 @@ export function base64decode(str: string): string {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 生成随机密钥
|
|
|
+ * 对 Base64 字符串解码为二进制数据
|
|
|
+ */
|
|
|
+export function bytesBase64decode(str: string): Uint8Array {
|
|
|
+ try {
|
|
|
+ return wordArrayToBytes(encBase64.parse(str));
|
|
|
+ } catch {
|
|
|
+ return new Uint8Array(0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 生成随机密钥 (字符串)
|
|
|
* @param length 密钥长度
|
|
|
* @returns 随机密钥
|
|
|
*/
|
|
|
-export const generateKey = (length: number = 32): string => {
|
|
|
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
|
- let result = '';
|
|
|
- for (let i = 0; i < length; i++) {
|
|
|
- result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
|
+export const generateKeyString = (length: number = 32): string => {
|
|
|
+ const result: string[] = [];
|
|
|
+ const bufferSize = Math.ceil((length * 256) / MAX_ALLOWED) + 16;
|
|
|
+ const buffer = new Uint8Array(bufferSize);
|
|
|
+ let pos = 0;
|
|
|
+ while (result.length < length) {
|
|
|
+ if (pos >= buffer.length) {
|
|
|
+ crypto.getRandomValues(buffer);
|
|
|
+ pos = 0;
|
|
|
+ }
|
|
|
+ const byte = buffer[pos++];
|
|
|
+ if (byte < MAX_ALLOWED) {
|
|
|
+ result.push(KEY_CHARS[byte % KEY_CHARS_LEN]);
|
|
|
+ }
|
|
|
}
|
|
|
- return result;
|
|
|
+ return result.join('');
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
- * 生成随机 IV
|
|
|
- * @param length 初始化向量长度
|
|
|
- * @returns 16 字节的随机 IV
|
|
|
+ * 生成随机密钥 (二进制)
|
|
|
+ * @param length 密钥长度
|
|
|
+ * @returns 随机密钥
|
|
|
*/
|
|
|
-const generateIV = (length: number = 16): string => {
|
|
|
- return generateKey(length);
|
|
|
+export const generateKeyBytes = (length: number): Uint8Array => {
|
|
|
+ const out = new Uint8Array(length);
|
|
|
+ crypto.getRandomValues(out);
|
|
|
+ return out;
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
- * 加密数据
|
|
|
- * @param data 要加密的字符串数据
|
|
|
- * @param key 加密密钥
|
|
|
- * @param iv 可选的初始化向量,如果不提供则随机生成
|
|
|
- * @returns 加密后的字符串(如果未提供 IV,则包含 IV)
|
|
|
+ * AES-CBC 加密(字符串)
|
|
|
+ *
|
|
|
+ * 注意:
|
|
|
+ * - 如果如果 iv 为空,则返回的加密结果的头部会包含 iv,否则不包含 iv。
|
|
|
+ * - 如果 key 的长度不足 32 位,则会在末尾补齐 字符'0'。
|
|
|
+ * - 如果 iv 的长度不足 16 位,则会在末尾补齐 字符'0'。
|
|
|
*/
|
|
|
-export const aesEncrypt = (data: string, key: string, iv?: string): string => {
|
|
|
+export const aesCbcEncryptString = (data: string, key: string, iv?: string): string => {
|
|
|
if (!data || !key) return '';
|
|
|
- // aes算法要求密钥长度为 16、24 或 32 字节,因此我们这里保证密钥长度为 32 个字符
|
|
|
const paddedKey = key.padEnd(32, '0').slice(0, 32);
|
|
|
- // aes算法要求 IV 长度为 16 字节,因此我们这里保证 IV 长度为 16 个字符
|
|
|
- const ivValue = iv ? iv.padEnd(16, '0').slice(0, 16) : generateIV(16);
|
|
|
+ const ivValue = iv ? iv.padEnd(16, '0').slice(0, 16) : generateKeyString(16);
|
|
|
const keyHex = encUtf8.parse(paddedKey);
|
|
|
const ivHex = encUtf8.parse(ivValue);
|
|
|
const encrypted = AES.encrypt(data, keyHex, { iv: ivHex });
|
|
|
@@ -77,18 +203,35 @@ export const aesEncrypt = (data: string, key: string, iv?: string): string => {
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
- * 解密数据
|
|
|
- * @param encryptedData 加密的字符串(如果加密时未提供 IV,则包含 IV)
|
|
|
- * @param key 解密密钥
|
|
|
- * @param iv 可选的初始化向量,如果不提供则从加密数据中提取
|
|
|
- * @returns 解密后的字符串
|
|
|
+ * AES-CBC 加密(二进制)
|
|
|
+ *
|
|
|
+ * 注意:
|
|
|
+ * - 如果如果 iv 为空,则返回的加密结果的头部会包含 iv,否则不包含 iv。
|
|
|
+ * - 如果 key 的长度不足 32 位,则会在末尾补齐 数字0。
|
|
|
+ * - 如果 iv 的长度不足 16 位,则会在末尾补齐 数字0。
|
|
|
*/
|
|
|
-export const aesDecrypt = (encryptedData: string, key: string, iv?: string): string => {
|
|
|
+export function aesCbcEncryptBytes(data: Uint8Array, key: Uint8Array, iv?: Uint8Array): Uint8Array {
|
|
|
+ if (!data.length || !key.length) return new Uint8Array(0);
|
|
|
+ const keyWa = bytesToWordArray(padBytes(key, AES_KEY_BYTES));
|
|
|
+ const ivWa = iv
|
|
|
+ ? bytesToWordArray(padBytes(iv, AES_IV_BYTES))
|
|
|
+ : bytesToWordArray(generateKeyBytes(AES_IV_BYTES));
|
|
|
+ const encrypted = AES.encrypt(bytesToWordArray(data), keyWa, { iv: ivWa });
|
|
|
+ const ct = wordArrayToBytes(encrypted.ciphertext);
|
|
|
+ if (iv) return ct;
|
|
|
+ const out = new Uint8Array(AES_IV_BYTES + ct.length);
|
|
|
+ out.set(wordArrayToBytes(ivWa), 0);
|
|
|
+ out.set(ct, AES_IV_BYTES);
|
|
|
+ return out;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * AES-CBC 解密(字符串)
|
|
|
+ */
|
|
|
+export const aesCbcDecryptString = (encryptedData: string, key: string, iv?: string): string => {
|
|
|
if (!encryptedData || !key) return '';
|
|
|
try {
|
|
|
- // aes算法要求密钥长度为 16、24 或 32 字节,因此我们这里保证密钥长度为 32 个字符
|
|
|
const paddedKey = key.padEnd(32, '0').slice(0, 32);
|
|
|
- // aes算法要求 IV 长度为 16 字节,因此我们这里保证 IV 长度为 16 个字符
|
|
|
const ivValue = iv ? iv.padEnd(16, '0').slice(0, 16) : encryptedData.slice(0, 16);
|
|
|
const data = iv ? encryptedData : encryptedData.slice(16);
|
|
|
const keyHex = encUtf8.parse(paddedKey);
|
|
|
@@ -102,18 +245,188 @@ export const aesDecrypt = (encryptedData: string, key: string, iv?: string): str
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
- * Rabbit 加密
|
|
|
- * @param data 要加密的数据
|
|
|
- * @param key 加密密钥
|
|
|
- * @param iv 可选的初始化向量,如果不提供则随机生成
|
|
|
- * @returns 加密后的数据(如果未提供 IV,则包含 IV)
|
|
|
+ * AES-CBC 解密(二进制)
|
|
|
+ */
|
|
|
+export function aesCbcDecryptBytes(
|
|
|
+ encryptedData: Uint8Array,
|
|
|
+ key: Uint8Array,
|
|
|
+ iv?: Uint8Array
|
|
|
+): Uint8Array {
|
|
|
+ if (!encryptedData.length || !key.length) return new Uint8Array(0);
|
|
|
+ try {
|
|
|
+ const ivBytes = iv ? padBytes(iv, AES_IV_BYTES) : encryptedData.subarray(0, AES_IV_BYTES);
|
|
|
+ const ctBytes = iv ? encryptedData : encryptedData.subarray(AES_IV_BYTES);
|
|
|
+ const keyWa = bytesToWordArray(padBytes(key, AES_KEY_BYTES));
|
|
|
+ const ivWa = bytesToWordArray(ivBytes);
|
|
|
+ const cp = CipherParams.create({
|
|
|
+ ciphertext: bytesToWordArray(ctBytes),
|
|
|
+ iv: ivWa,
|
|
|
+ });
|
|
|
+ const decrypted = AES.decrypt(cp, keyWa, { iv: ivWa });
|
|
|
+ return wordArrayToBytes(decrypted);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('AES-CBC decrypt bytes error:', error);
|
|
|
+ return new Uint8Array(0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * AES-CTR 加密(字符串)
|
|
|
+ *
|
|
|
+ * 注意:
|
|
|
+ * - 如果如果 iv 为空,则返回的加密结果的头部会包含 iv,否则不包含 iv。
|
|
|
+ * - 如果 key 的长度不足 32 位,则会在末尾补齐 字符'0'。
|
|
|
+ * - 如果 iv 的长度不足 16 位,则会在末尾补齐 字符'0'。
|
|
|
*/
|
|
|
-export const rabbitEncrypt = (data: string, key: string, iv?: string): string => {
|
|
|
+export const aesCtrEncryptString = (data: string, key: string, iv?: string): string => {
|
|
|
+ if (!data || !key) return '';
|
|
|
+ const paddedKey = key.padEnd(32, '0').slice(0, 32);
|
|
|
+ const ivValue = iv ? iv.padEnd(16, '0').slice(0, 16) : generateKeyString(16);
|
|
|
+ const keyHex = encUtf8.parse(paddedKey);
|
|
|
+ const ivHex = encUtf8.parse(ivValue);
|
|
|
+ const encrypted = AES.encrypt(data, keyHex, { iv: ivHex, mode: modeCtr });
|
|
|
+ return iv ? encrypted.toString() : ivValue + encrypted.toString();
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * AES-CTR 加密(二进制)
|
|
|
+ *
|
|
|
+ * 注意:
|
|
|
+ * - 如果如果 iv 为空,则返回的加密结果的头部会包含 iv,否则不包含 iv。
|
|
|
+ * - 如果 key 的长度不足 32 位,则会在末尾补齐 数字0。
|
|
|
+ * - 如果 iv 的长度不足 16 位,则会在末尾补齐 数字0。
|
|
|
+ */
|
|
|
+export function aesCtrEncryptBytes(data: Uint8Array, key: Uint8Array, iv?: Uint8Array): Uint8Array {
|
|
|
+ if (!data.length || !key.length) return new Uint8Array(0);
|
|
|
+ const keyWa = bytesToWordArray(padBytes(key, AES_KEY_BYTES));
|
|
|
+ const ivWa = iv
|
|
|
+ ? bytesToWordArray(padBytes(iv, AES_IV_BYTES))
|
|
|
+ : bytesToWordArray(generateKeyBytes(AES_IV_BYTES));
|
|
|
+ const encrypted = AES.encrypt(bytesToWordArray(data), keyWa, { iv: ivWa, mode: modeCtr });
|
|
|
+ const ct = wordArrayToBytes(encrypted.ciphertext);
|
|
|
+ if (iv) return ct;
|
|
|
+ const out = new Uint8Array(AES_IV_BYTES + ct.length);
|
|
|
+ out.set(wordArrayToBytes(ivWa), 0);
|
|
|
+ out.set(ct, AES_IV_BYTES);
|
|
|
+ return out;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * AES-CTR 解密(字符串)
|
|
|
+ */
|
|
|
+export const aesCtrDecryptString = (encryptedData: string, key: string, iv?: string): string => {
|
|
|
+ if (!encryptedData || !key) return '';
|
|
|
+ try {
|
|
|
+ const paddedKey = key.padEnd(32, '0').slice(0, 32);
|
|
|
+ const ivValue = iv ? iv.padEnd(16, '0').slice(0, 16) : encryptedData.slice(0, 16);
|
|
|
+ const data = iv ? encryptedData : encryptedData.slice(16);
|
|
|
+ const keyHex = encUtf8.parse(paddedKey);
|
|
|
+ const ivHex = encUtf8.parse(ivValue);
|
|
|
+ const decrypted = AES.decrypt(data, keyHex, { iv: ivHex, mode: modeCtr });
|
|
|
+ return decrypted.toString(encUtf8);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('AES-CTR decrypt error:', error);
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * AES-CTR 解密(二进制)
|
|
|
+ */
|
|
|
+export function aesCtrDecryptBytes(
|
|
|
+ encryptedData: Uint8Array,
|
|
|
+ key: Uint8Array,
|
|
|
+ iv?: Uint8Array
|
|
|
+): Uint8Array {
|
|
|
+ if (!encryptedData.length || !key.length) return new Uint8Array(0);
|
|
|
+ try {
|
|
|
+ const ivBytes = iv ? padBytes(iv, AES_IV_BYTES) : encryptedData.subarray(0, AES_IV_BYTES);
|
|
|
+ const ctBytes = iv ? encryptedData : encryptedData.subarray(AES_IV_BYTES);
|
|
|
+ const keyWa = bytesToWordArray(padBytes(key, AES_KEY_BYTES));
|
|
|
+ const ivWa = bytesToWordArray(ivBytes);
|
|
|
+ const cp = CipherParams.create({
|
|
|
+ ciphertext: bytesToWordArray(ctBytes),
|
|
|
+ iv: ivWa,
|
|
|
+ });
|
|
|
+ const decrypted = AES.decrypt(cp, keyWa, { iv: ivWa, mode: modeCtr });
|
|
|
+ return wordArrayToBytes(decrypted);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('AES-CTR decrypt bytes error:', error);
|
|
|
+ return new Uint8Array(0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * AES-ECB 加密(字符串,无 IV,不推荐用于敏感数据)
|
|
|
+ *
|
|
|
+ * 注意:
|
|
|
+ * - 如果 key 的长度不足32位,则会在末尾补齐 字符'0'。
|
|
|
+ */
|
|
|
+export const aesEcbEncryptString = (data: string, key: string): string => {
|
|
|
+ if (!data || !key) return '';
|
|
|
+ const paddedKey = key.padEnd(32, '0').slice(0, 32);
|
|
|
+ const keyHex = encUtf8.parse(paddedKey);
|
|
|
+ const encrypted = AES.encrypt(data, keyHex, { mode: modeEcb });
|
|
|
+ return encrypted.toString();
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * AES-ECB 加密(二进制,无 IV)
|
|
|
+ *
|
|
|
+ * 注意:
|
|
|
+ * - 如果 key 的长度不足 32 位,则会在末尾补齐 数字0。
|
|
|
+ */
|
|
|
+export function aesEcbEncryptBytes(data: Uint8Array, key: Uint8Array): Uint8Array {
|
|
|
+ if (!data.length || !key.length) return new Uint8Array(0);
|
|
|
+ const keyWa = bytesToWordArray(padBytes(key, AES_KEY_BYTES));
|
|
|
+ const encrypted = AES.encrypt(bytesToWordArray(data), keyWa, { mode: modeEcb });
|
|
|
+ return wordArrayToBytes(encrypted.ciphertext);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * AES-ECB 解密(字符串)
|
|
|
+ */
|
|
|
+export const aesEcbDecryptString = (encryptedData: string, key: string): string => {
|
|
|
+ if (!encryptedData || !key) return '';
|
|
|
+ try {
|
|
|
+ const paddedKey = key.padEnd(32, '0').slice(0, 32);
|
|
|
+ const keyHex = encUtf8.parse(paddedKey);
|
|
|
+ const decrypted = AES.decrypt(encryptedData, keyHex, { mode: modeEcb });
|
|
|
+ return decrypted.toString(encUtf8);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('AES-ECB decrypt error:', error);
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * AES-ECB 解密(二进制)
|
|
|
+ */
|
|
|
+export function aesEcbDecryptBytes(encryptedData: Uint8Array, key: Uint8Array): Uint8Array {
|
|
|
+ if (!encryptedData.length || !key.length) return new Uint8Array(0);
|
|
|
+ try {
|
|
|
+ const keyWa = bytesToWordArray(padBytes(key, AES_KEY_BYTES));
|
|
|
+ const cp = CipherParams.create({ ciphertext: bytesToWordArray(encryptedData) });
|
|
|
+ const decrypted = AES.decrypt(cp, keyWa, { mode: modeEcb });
|
|
|
+ return wordArrayToBytes(decrypted);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('AES-ECB decrypt bytes error:', error);
|
|
|
+ return new Uint8Array(0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Rabbit 加密(字符串)
|
|
|
+ *
|
|
|
+ * 注意:
|
|
|
+ * - 如果如果 iv 为空,则返回的加密结果的头部会包含 iv,否则不包含 iv。
|
|
|
+ * - 如果 key 的长度不足 32 位,则会在末尾补齐 字符'0'。
|
|
|
+ * - 如果 iv 的长度不足 16 位,则会在末尾补齐 字符'0'。
|
|
|
+ */
|
|
|
+export const rabbitEncryptString = (data: string, key: string, iv?: string): string => {
|
|
|
if (!data || !key) return '';
|
|
|
- // Rabbit 算法要求密钥长度为 16 字节,因此我们这里保证密钥长度为 16 个字符
|
|
|
const paddedKey = key.padEnd(16, '0').slice(0, 16);
|
|
|
- // Rabbit 算法要求 IV 长度为 8 字节,因此我们这里保证 IV 长度为 8 个字符
|
|
|
- const ivValue = iv ? iv.padEnd(8, '0').slice(0, 8) : generateIV(8);
|
|
|
+ const ivValue = iv ? iv.padEnd(8, '0').slice(0, 8) : generateKeyString(8);
|
|
|
const keyHex = encUtf8.parse(paddedKey);
|
|
|
const ivHex = encUtf8.parse(ivValue);
|
|
|
const encrypted = Rabbit.encrypt(data, keyHex, { iv: ivHex });
|
|
|
@@ -121,18 +434,35 @@ export const rabbitEncrypt = (data: string, key: string, iv?: string): string =>
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
- * Rabbit 解密
|
|
|
- * @param encryptedData 加密的数据(如果加密时未提供 IV,则包含 IV)
|
|
|
- * @param key 解密密钥
|
|
|
- * @param iv 可选的初始化向量,如果不提供则从加密数据中提取
|
|
|
- * @returns 解密后的数据
|
|
|
+ * Rabbit 加密(二进制)
|
|
|
+ *
|
|
|
+ * 注意:
|
|
|
+ * - 如果如果 iv 为空,则返回的加密结果的头部会包含 iv,否则不包含 iv。
|
|
|
+ * - 如果 key 的长度不足 16 位,则会在末尾补齐 数字0。
|
|
|
+ * - 如果 iv 的长度不足 8 位,则会在末尾补齐 数字0。
|
|
|
*/
|
|
|
-export const rabbitDecrypt = (encryptedData: string, key: string, iv?: string): string => {
|
|
|
+export function rabbitEncryptBytes(data: Uint8Array, key: Uint8Array, iv?: Uint8Array): Uint8Array {
|
|
|
+ if (!data.length || !key.length) return new Uint8Array(0);
|
|
|
+ const keyWa = bytesToWordArray(padBytes(key, RABBIT_KEY_BYTES));
|
|
|
+ const ivWa = iv
|
|
|
+ ? bytesToWordArray(padBytes(iv, RABBIT_IV_BYTES))
|
|
|
+ : bytesToWordArray(generateKeyBytes(RABBIT_IV_BYTES));
|
|
|
+ const encrypted = Rabbit.encrypt(bytesToWordArray(data), keyWa, { iv: ivWa });
|
|
|
+ const ct = wordArrayToBytes(encrypted.ciphertext);
|
|
|
+ if (iv) return ct;
|
|
|
+ const out = new Uint8Array(RABBIT_IV_BYTES + ct.length);
|
|
|
+ out.set(wordArrayToBytes(ivWa), 0);
|
|
|
+ out.set(ct, RABBIT_IV_BYTES);
|
|
|
+ return out;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Rabbit 解密(字符串)
|
|
|
+ */
|
|
|
+export const rabbitDecryptString = (encryptedData: string, key: string, iv?: string): string => {
|
|
|
if (!encryptedData || !key) return '';
|
|
|
try {
|
|
|
- // Rabbit 算法要求密钥长度为 16 字节,因此我们这里保证密钥长度为 16 个字符
|
|
|
const paddedKey = key.padEnd(16, '0').slice(0, 16);
|
|
|
- // Rabbit 算法要求 IV 长度为 8 字节,因此我们这里保证 IV 长度为 8 个字符
|
|
|
const ivValue = iv ? iv.padEnd(8, '0').slice(0, 8) : encryptedData.slice(0, 8);
|
|
|
const data = iv ? encryptedData : encryptedData.slice(8);
|
|
|
const keyHex = encUtf8.parse(paddedKey);
|
|
|
@@ -144,3 +474,31 @@ export const rabbitDecrypt = (encryptedData: string, key: string, iv?: string):
|
|
|
return '';
|
|
|
}
|
|
|
};
|
|
|
+
|
|
|
+/**
|
|
|
+ * Rabbit 解密(二进制)
|
|
|
+ */
|
|
|
+export function rabbitDecryptBytes(
|
|
|
+ encryptedData: Uint8Array,
|
|
|
+ key: Uint8Array,
|
|
|
+ iv?: Uint8Array
|
|
|
+): Uint8Array {
|
|
|
+ if (!encryptedData.length || !key.length) return new Uint8Array(0);
|
|
|
+ try {
|
|
|
+ const ivBytes = iv
|
|
|
+ ? padBytes(iv, RABBIT_IV_BYTES)
|
|
|
+ : encryptedData.subarray(0, RABBIT_IV_BYTES);
|
|
|
+ const ctBytes = iv ? encryptedData : encryptedData.subarray(RABBIT_IV_BYTES);
|
|
|
+ const keyWa = bytesToWordArray(padBytes(key, RABBIT_KEY_BYTES));
|
|
|
+ const ivWa = bytesToWordArray(ivBytes);
|
|
|
+ const cp = CipherParams.create({
|
|
|
+ ciphertext: bytesToWordArray(ctBytes),
|
|
|
+ iv: ivWa,
|
|
|
+ });
|
|
|
+ const decrypted = Rabbit.decrypt(cp, keyWa, { iv: ivWa });
|
|
|
+ return wordArrayToBytes(decrypted);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Rabbit decrypt bytes error:', error);
|
|
|
+ return new Uint8Array(0);
|
|
|
+ }
|
|
|
+}
|