| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135 |
- import globalConfig from '@/config';
- import { CompressMethod } from '@/config/types';
- import {
- bytesToNumber,
- numberToBytesAt,
- Endian,
- bytesToString,
- stringToBytes,
- } from '@/utils/bytesUtils';
- import {
- aesCbcDecryptBytes,
- aesCbcEncryptBytes,
- bytesBase64decode,
- bytesBase64encode,
- } from '@/utils/crypto';
- import { compressBytes, decompressBytes, CompressFormat } from './compress';
- import { currentUnixTimestamp } from './timeUtils';
- export type JsonPrimitive = string | number | boolean | null;
- export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue | undefined };
- const TIMESTAMP_BYTES = 8;
- function buildTimestampBytes(timestamp: number): Uint8Array {
- const arr = new Uint8Array(TIMESTAMP_BYTES);
- numberToBytesAt(0, arr, 0, Endian.BE);
- numberToBytesAt(timestamp, arr, 4, Endian.BE);
- return arr;
- }
- function parseTimestampBytes(bytes: Uint8Array): number {
- const high = bytesToNumber(bytes, 0, Endian.BE);
- const low = bytesToNumber(bytes, 4, Endian.BE);
- return high * 0x100000000 + low;
- }
- function compressFormatFromMethod(method: CompressMethod): CompressFormat {
- return method === CompressMethod.BR ? CompressFormat.BROTLI : CompressFormat.GZIP;
- }
- /**
- * 请求体加密(与后端 GoDataEncrypt 对应)
- * 仅接收字符串:8字节时间戳(BE)+数据 → 压缩() → AES-CBC 加密 → IV + 密文
- * @param plaintext 明文字符串
- * @param key 密钥(由 crypto 内部补齐/截断为 32 字节)
- * @param compressMethod 压缩算法,空表示不压缩
- * @param timestamp 时间戳(秒级数字),内部转为 8 字节大端后拼在明文前
- * @returns 二进制密文(IV 16 字节 + 密文)
- */
- export async function encryptRequestPayload(
- dataBytes: Uint8Array,
- timestamp: number,
- key: Uint8Array,
- compressMethod: CompressMethod = CompressMethod.NO_ZIP
- ): Promise<Uint8Array> {
- const compressed =
- compressMethod && compressMethod !== CompressMethod.NO_ZIP
- ? await compressBytes(dataBytes, compressFormatFromMethod(compressMethod))
- : dataBytes;
- const timestampBytes = buildTimestampBytes(timestamp);
- const timeAndData = new Uint8Array(TIMESTAMP_BYTES + compressed.length);
- timeAndData.set(timestampBytes, 0);
- timeAndData.set(compressed, TIMESTAMP_BYTES);
- return aesCbcEncryptBytes(timeAndData, key);
- }
- /**
- * 响应体解密(与后端 GoDataDecrypt 对应)
- * 仅接收二进制:IV + 密文 → AES-CBC 解密 → 去掉 8 字节时间戳 → 可选解压
- * @param encryptedBytes 二进制密文(IV 16 字节 + 密文)
- * @param key 密钥(由 crypto 内部补齐/截断为 32 字节)
- * @param compressMethod 解压算法,空表示未压缩
- * @returns 去掉时间戳并解压后的数据
- */
- export async function decryptResponsePayload(
- encryptedBytes: Uint8Array,
- key: Uint8Array,
- compressMethod: CompressMethod = CompressMethod.NO_ZIP
- ): Promise<{ timestamp: number; data: Uint8Array }> {
- const decrypted = aesCbcDecryptBytes(encryptedBytes, key);
- if (decrypted.length < TIMESTAMP_BYTES) return { timestamp: 0, data: new Uint8Array(0) };
- const timestampBytes = decrypted.subarray(0, TIMESTAMP_BYTES);
- const timestamp = parseTimestampBytes(timestampBytes);
- const dataBytes = decrypted.subarray(TIMESTAMP_BYTES);
- const rawBytes =
- compressMethod && compressMethod !== CompressMethod.NO_ZIP
- ? await decompressBytes(dataBytes, compressFormatFromMethod(compressMethod))
- : dataBytes;
- return { timestamp, data: rawBytes };
- }
- /**
- * 加密 URL 参数
- * @param params 参数对象
- * @returns 加密后的 URL 参数
- */
- export async function encryptUrlParams(params: JsonValue): Promise<string> {
- const jsonString = typeof params === 'string' ? params : JSON.stringify(params);
- const key = stringToBytes(globalConfig.security.requestEncryptionKey);
- const dataBytes = stringToBytes(jsonString);
- const encrypted = await encryptRequestPayload(
- dataBytes,
- currentUnixTimestamp(),
- key,
- globalConfig.security.compressMethod
- );
- return bytesBase64encode(encrypted, true);
- }
- /**
- * 解密 URL 参数
- * @param params 加密后的 URL 参数
- * @returns 解密后的参数对象
- */
- export async function decryptUrlParams<T = any>(params: string): Promise<T | null> {
- // const key = bytesBase64decode(globalConfig.security.requestEncryptionKey);
- const key = stringToBytes(globalConfig.security.requestEncryptionKey);
- const dataBytes = bytesBase64decode(params, true);
- const compressMethod = globalConfig.security.compressMethod;
- try {
- const { data } = await decryptResponsePayload(dataBytes, key, compressMethod);
- const result = bytesToString(data);
- return JSON.parse(result) as T;
- } catch {
- return null;
- }
- }
|