Created
October 14, 2025 09:59
-
-
Save nuintun/3251794f1f44b20ed1e087b63cad5f4a to your computer and use it in GitHub Desktop.
对称加密函数实现
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @module crypto | |
*/ | |
/** | |
* @function safeAtob | |
* @description 安全模式的 atob | |
* @param text 要 Base64 解码的文本 | |
*/ | |
function safeAtob(text: string): string { | |
try { | |
return globalThis.atob(text); | |
} catch { | |
return ''; | |
} | |
} | |
/** | |
* @function toUint16 | |
* @param value 需要转换的数值 | |
*/ | |
function toUint16(value: number): number { | |
return value & 0xffff; | |
} | |
/** | |
* @function encodeUint16 | |
* @param value 需要编码的16位数值 | |
*/ | |
function encodeUint16(value: number): string { | |
return String.fromCharCode(value & 0xff, (value & 0xff00) >> 8); | |
} | |
/** | |
* @function readUint16 | |
* @param text 原始文本 | |
* @param index 位置索引 | |
*/ | |
function readUint16(text: string, index: number): number { | |
return text.charCodeAt(index) + (text.charCodeAt(++index) << 8); | |
} | |
/** | |
* @function encrypt | |
* @description 加密字符串 | |
* @param plainText 要加密的字符串 | |
*/ | |
export function encrypt(plainText: string): string { | |
const { length } = plainText; | |
const seed = new Uint16Array(1); | |
const [key] = crypto.getRandomValues(seed); | |
let checksum = 0; | |
let currentKey = key; | |
checksum += currentKey; | |
checksum = toUint16(checksum); | |
const encryptedChunks: string[] = []; | |
for (let i = 0; i < length; i++) { | |
const code = plainText.charCodeAt(i); | |
const encryptedCode = code ^ currentKey; | |
checksum += code; | |
checksum = toUint16(checksum); | |
currentKey ^= i ^ code; | |
encryptedChunks[i] = encodeUint16(encryptedCode); | |
} | |
checksum = toUint16(~checksum + 1); | |
return globalThis.btoa( | |
// 解密密钥 | |
encodeUint16(key ^ (length * 2)) + | |
// 校验值 | |
encodeUint16(checksum) + | |
// 加密数据 | |
encryptedChunks.join('') | |
); | |
} | |
/** | |
* @function decrypt | |
* @description 解密字符串 | |
* @param cipherText 要解密的字符串 | |
*/ | |
export function decrypt(cipherText: string): string { | |
const atobText = safeAtob(cipherText); | |
const { length } = atobText; | |
const offset = 4; | |
if (length <= offset || (length & 1) !== 0) { | |
return ''; | |
} | |
const key = readUint16(atobText, 0); | |
let checksum = readUint16(atobText, 2); | |
let currentKey = key ^ (length - offset); | |
checksum += currentKey; | |
checksum = toUint16(checksum); | |
const decryptedChunks: string[] = []; | |
for (let index = 0, i = offset; i < length; i += 2) { | |
const code = readUint16(atobText, i); | |
const decryptedCode = code ^ currentKey; | |
checksum += decryptedCode; | |
checksum = toUint16(checksum); | |
currentKey ^= index ^ decryptedCode; | |
decryptedChunks[index++] = String.fromCharCode(decryptedCode); | |
} | |
return checksum !== 0 ? '' : decryptedChunks.join(''); | |
} |
/**
* @module crypto
*/
// 数据包信息
const enum Packet {
HEAD_SIZE = 0x08,
KEY_OFFSET = 0x04,
CHECKSUM_OFFSET = 0x00,
XOR_KEY_SEED = 0x76efd8ba,
SECURITY_KEY_SEED = 0xf0578e23
}
/**
* @function seedToRandom
* @description 基于 LCG 算法通过种子生成随机数
* @param seed 随机数种子
* @returns {number}
*/
function seedToRandom(seed: number): number {
return (seed * 0x343fd + 0x269ec3) & 0xffff;
}
/**
* @function deriveXorKey
* @description 派生 XOR 密钥
* @param {number} seed XOR 密钥种子
* @returns {number}
*/
function deriveXorKey(seed: number): number {
const high = seedToRandom(seed >>> 16);
const low = seedToRandom(seed & 0xffff);
return (((high << 16) | low) ^ Packet.XOR_KEY_SEED) >>> 0;
}
// 安全密钥
const SECURITY_KEY = deriveXorKey(Packet.SECURITY_KEY_SEED);
// CRC32C 查找表,用于快速计算 CRC32C 校验值
const CRC32C_TABLE = new Uint32Array(256);
for (let i = 0; i < 256; i++) {
let crc = i;
for (let j = 0; j < 8; j++) {
crc = (crc >>> 1) ^ (-(crc & 1) & 0x82f63b78);
}
CRC32C_TABLE[i] = crc >>> 0;
}
/**
* @function crc32c
* @description 计算数据的 CRC32C 校验值
* @param {Uint8Array} bytes 输入数据
* @param {number} offset 起始偏移量
* @param {number} end 结束偏移量
* @returns {number} CRC32C 校验值
*/
function crc32c(bytes: Uint8Array, offset: number, end: number): number {
let crc = 0xffffffff;
for (let i = offset; i < end; i++) {
crc = CRC32C_TABLE[(crc ^ bytes[i]) & 0xff] ^ (crc >>> 8);
}
return (crc ^ 0xffffffff) >>> 0;
}
/**
* @function encrypt
* @description 对数据进行加密处理
* @param {Uint8Array} buffer 需要加密的数据
* @param {boolean} [littleEndian] 是否使用小端字节序
* @returns {Uint8Array} 加密后的数据包
*/
export function encrypt(buffer: Uint8Array, littleEndian?: boolean): Uint8Array {
// 获取原始数据大小
const size = buffer.length;
// 计算需要加密的数据块数量(每块 4 字节)
const encryptBlocks = Math.ceil(size / 4);
// 获取对齐后的数据大小
const alignedSize = encryptBlocks * 4;
// 创建数据包
const packet = new Uint8Array(Packet.HEAD_SIZE + alignedSize);
// 生成初始密钥
const [key] = crypto.getRandomValues(new Uint32Array(1));
// 创建数据包视图
const packetView = new DataView(packet.buffer);
// 写入加密后的初始密钥到包头
packetView.setUint32(Packet.KEY_OFFSET, key ^ SECURITY_KEY, littleEndian);
// 拷贝原始数据到包中
packet.set(buffer, Packet.HEAD_SIZE);
// 创建 XOR 密钥
let xorKey = deriveXorKey(key);
// 循环加密数据,每块 4 字节
for (let i = 0; i < encryptBlocks; i++) {
const blockOffset = Packet.HEAD_SIZE + i * 4;
// 读取4字节数据并与密钥进行 XOR 运算实现加密
const value = (packetView.getUint32(blockOffset, littleEndian) ^ xorKey) >>> 0;
// 写入加密后的数据
packetView.setUint32(blockOffset, value, littleEndian);
// 根据加密后的数据派生新的密钥,实现密钥流加密
xorKey = deriveXorKey(value);
}
// 实际使用的数据长度
const length = Packet.HEAD_SIZE + size;
// 计算校验码,确保数据完整性
const checksum = crc32c(packet, Packet.KEY_OFFSET, length);
// 写入加密后的校验码到包头
packetView.setUint32(Packet.CHECKSUM_OFFSET, checksum ^ key, littleEndian);
// 返回实际使用的数据长度(去除填充)
return packet.subarray(0, length);
}
/**
* @function decrypt
* @description 对数据包进行解密处理
* @param {Uint8Array} packet 需要解密的数据包
* @param {boolean} [littleEndian] 是否使用小端字节序
* @returns {Uint8Array} 解密后的原始数据
*/
export function decrypt(packet: Uint8Array, littleEndian?: boolean): Uint8Array {
// 获取原始数据大小
const length = packet.length;
// 计算需要解密的数据大小(总大小减去包头信息大小)
const size = length - Packet.HEAD_SIZE;
// 检查数据包大小是否有效
if (size < 0) {
throw new Error('invalid packet size');
}
// 创建数据包视图
const packetView = new DataView(packet.buffer);
// 从包头读取初始密钥
const key = (packetView.getUint32(Packet.KEY_OFFSET, littleEndian) ^ SECURITY_KEY) >>> 0;
// 从包头读取校验码
const checksum = (packetView.getUint32(Packet.CHECKSUM_OFFSET, littleEndian) ^ key) >>> 0;
// 校验和必须为0表示数据完整
if (checksum !== crc32c(packet, Packet.KEY_OFFSET, length)) {
throw new Error('checksum verification failed');
}
// 计算解密块数量(每块 4 字节)
const decryptBlocks = Math.ceil(size / 4);
// 获取对齐后的数据大小
const alignedSize = decryptBlocks * 4;
// 创建缓冲区用于存储对齐后的数据
const buffer = new Uint8Array(alignedSize);
// 拷贝加密数据部分到缓冲区
buffer.set(packet.subarray(Packet.HEAD_SIZE));
// 计算补齐的字节数(保证 4 字节对齐)
const paddingSize = alignedSize - size;
// 计算最大块偏移
const maxBlockOffset = decryptBlocks - 1;
// 创建 XOR 密钥
let xorKey = deriveXorKey(key);
// 创建缓冲区视图
const bufferView = new DataView(buffer.buffer);
// 循环解密数据,每块 4 字节
for (let i = 0; i < decryptBlocks; i++) {
// 处理补齐字节,确保数据完整性
if (paddingSize > 0 && i >= maxBlockOffset) {
// 创建补齐缓冲区
const padding = new Uint8Array(4);
const paddingView = new DataView(padding.buffer);
// 写入当前密钥作为补齐数据
paddingView.setUint32(0, xorKey, littleEndian);
// 将补齐数据写入缓冲区末尾
buffer.set(padding.subarray(4 - paddingSize), size);
}
// 计算当前块偏移位置
const blockOffset = i * 4;
// 读取当前块数据
const value = bufferView.getUint32(blockOffset, littleEndian);
// 进行 XOR 解密并写入解密后的数据
bufferView.setUint32(blockOffset, value ^ xorKey, littleEndian);
// 更新解密密钥,实现密钥流解密
xorKey = deriveXorKey(value);
}
// 提取实际数据部分(去除填充)
return buffer.subarray(0, size);
}
/**
* @module crypto
*/
// 数据包信息
const enum Packet {
HEAD_SIZE = 0x08,
KEY_OFFSET = 0x04,
CHECKSUM_OFFSET = 0x00,
XOR_KEY_SEED = 0x76efd8ba,
SECURITY_KEY_SEED = 0xf0578e23
}
/**
* @function seedToRandom
* @description 基于 LCG 算法通过种子生成随机数
* @param seed 随机数种子
* @returns {number}
*/
function seedToRandom(seed: number): number {
return (seed * 0x343fd + 0x269ec3) & 0xffff;
}
/**
* @function deriveXorKey
* @description 派生 XOR 密钥
* @param {number} seed XOR 密钥种子
* @returns {number}
*/
function deriveXorKey(seed: number): number {
const high = seedToRandom(seed >>> 16);
const low = seedToRandom(seed & 0xffff);
return (((high << 16) | low) ^ Packet.XOR_KEY_SEED) >>> 0;
}
// 安全密钥
const SECURITY_KEY = deriveXorKey(Packet.SECURITY_KEY_SEED);
// CRC32C 查找表,用于快速计算 CRC32C 校验值
const CRC32C_TABLE = new Uint32Array(256);
for (let i = 0; i < 256; i++) {
let crc = i;
for (let j = 0; j < 8; j++) {
crc = (crc >>> 1) ^ (-(crc & 1) & 0x82f63b78);
}
CRC32C_TABLE[i] = crc >>> 0;
}
/**
* @function crc32c
* @description 计算数据的 CRC32C 校验值
* @param {Uint8Array} bytes 输入数据
* @param {number} offset 起始偏移量
* @param {number} end 结束偏移量
* @returns {number} CRC32C 校验值
*/
function crc32c(bytes: Uint8Array, offset: number, end: number): number {
let crc = 0xffffffff;
for (let i = offset; i < end; i++) {
crc = CRC32C_TABLE[(crc ^ bytes[i]) & 0xff] ^ (crc >>> 8);
}
return (crc ^ 0xffffffff) >>> 0;
}
/**
* @function encryptBlock
* @description 加密单个数据块并派生下一个密钥
* @param {DataView} view 数据视图
* @param {number} offset 数据块偏移量(以 4 字节为单位)
* @param {number} xorKey XOR 加密密钥
* @param {boolean} [littleEndian] 是否使用小端字节序
* @returns {number} 根据加密数据派生的新密钥
*/
function encryptBlock(view: DataView, offset: number, xorKey: number, littleEndian?: boolean): number {
// 计算当前块偏移位置
const blockOffset = Packet.HEAD_SIZE + offset * 4;
// 读取4字节数据并与密钥进行 XOR 运算实现加密
const value = (view.getUint32(blockOffset, littleEndian) ^ xorKey) >>> 0;
// 写入加密后的数据
view.setUint32(blockOffset, value, littleEndian);
// 根据加密后的数据派生新的密钥,实现密钥流加密
return deriveXorKey(value);
}
/**
* @function decryptBlock
* @description 解密单个数据块并派生下一个密钥
* @param {DataView} view 数据视图
* @param {number} offset 数据块偏移量(以 4 字节为单位)
* @param {number} xorKey XOR 解密密钥
* @param {boolean} [littleEndian] 是否使用小端字节序
* @returns {number} 根据解密数据派生的新密钥
*/
function decryptBlock(view: DataView, offset: number, xorKey: number, littleEndian?: boolean): number {
// 计算当前块偏移位置
const blockOffset = offset * 4;
// 读取当前块数据
const value = view.getUint32(blockOffset, littleEndian);
// 进行 XOR 解密并写入解密后的数据
view.setUint32(blockOffset, value ^ xorKey, littleEndian);
// 更新解密密钥,实现密钥流解密
return deriveXorKey(value);
}
/**
* @function encrypt
* @description 对数据进行加密处理
* @param {Uint8Array} buffer 需要加密的数据
* @param {boolean} [littleEndian] 是否使用小端字节序
* @returns {Uint8Array} 加密后的数据包
*/
export function encrypt(buffer: Uint8Array, littleEndian?: boolean): Uint8Array {
// 获取原始数据大小
const size = buffer.length;
// 计算需要加密的数据块数量(每块 4 字节)
const encryptBlocks = Math.ceil(size / 4);
// 获取对齐后的数据大小
const alignedSize = encryptBlocks * 4;
// 创建数据包
const packet = new Uint8Array(Packet.HEAD_SIZE + alignedSize);
// 生成初始密钥
const [key] = crypto.getRandomValues(new Uint32Array(1));
// 创建数据包视图
const packetView = new DataView(packet.buffer);
// 写入加密后的初始密钥到包头
packetView.setUint32(Packet.KEY_OFFSET, key ^ SECURITY_KEY, littleEndian);
// 拷贝原始数据到包中
packet.set(buffer, Packet.HEAD_SIZE);
// 创建 XOR 密钥
let xorKey = deriveXorKey(key);
// 循环加密数据,每块 4 字节
for (let offset = 0; offset < encryptBlocks; offset++) {
// 加密数据块并更新密钥
xorKey = encryptBlock(packetView, offset, xorKey, littleEndian);
}
// 实际使用的数据长度
const length = Packet.HEAD_SIZE + size;
// 计算校验码,确保数据完整性
const checksum = crc32c(packet, Packet.KEY_OFFSET, length);
// 写入加密后的校验码到包头
packetView.setUint32(Packet.CHECKSUM_OFFSET, checksum ^ key, littleEndian);
// 返回实际使用的数据长度(去除填充)
return packet.subarray(0, length);
}
/**
* @function decrypt
* @description 对数据包进行解密处理
* @param {Uint8Array} packet 需要解密的数据包
* @param {boolean} [littleEndian] 是否使用小端字节序
* @returns {Uint8Array} 解密后的原始数据
*/
export function decrypt(packet: Uint8Array, littleEndian?: boolean): Uint8Array {
// 获取原始数据大小
const length = packet.length;
// 计算需要解密的数据大小(总大小减去包头信息大小)
const size = length - Packet.HEAD_SIZE;
// 检查数据包大小是否有效
if (size < 0) {
throw new Error('invalid packet size');
}
// 创建数据包视图
const packetView = new DataView(packet.buffer);
// 从包头读取初始密钥
const key = (packetView.getUint32(Packet.KEY_OFFSET, littleEndian) ^ SECURITY_KEY) >>> 0;
// 从包头读取校验码
const checksum = (packetView.getUint32(Packet.CHECKSUM_OFFSET, littleEndian) ^ key) >>> 0;
// 校验和必须为0表示数据完整
if (checksum !== crc32c(packet, Packet.KEY_OFFSET, length)) {
throw new Error('checksum verification failed');
}
// 计算解密块数量(每块 4 字节)
const decryptBlocks = Math.ceil(size / 4);
// 获取对齐后的数据大小
const alignedSize = decryptBlocks * 4;
// 创建缓冲区用于存储对齐后的数据
const buffer = new Uint8Array(alignedSize);
// 拷贝加密数据部分到缓冲区
buffer.set(packet.subarray(Packet.HEAD_SIZE));
// 计算补齐的字节数(保证 4 字节对齐)
const paddingSize = alignedSize - size;
// 计算最大块偏移
const maxBlockOffset = decryptBlocks - 1;
// 创建 XOR 密钥
let xorKey = deriveXorKey(key);
// 创建缓冲区视图
const bufferView = new DataView(buffer.buffer);
// 循环解密数据,每块 4 字节,忽略最后一块,放后面以便补位处理
for (let offset = 0; offset < maxBlockOffset; offset++) {
// 解密数据块并更新密钥
xorKey = decryptBlock(bufferView, offset, xorKey, littleEndian);
}
// 处理补齐字节,确保数据完整性
if (paddingSize > 0) {
// 创建补齐缓冲区
const padding = new Uint8Array(4);
const paddingView = new DataView(padding.buffer);
// 写入当前密钥作为补齐数据
paddingView.setUint32(0, xorKey, littleEndian);
// 将补齐数据写入缓冲区末尾
buffer.set(padding.subarray(4 - paddingSize), size);
}
// 解密最后一块数据块
xorKey = decryptBlock(bufferView, maxBlockOffset, xorKey, littleEndian);
// 提取实际数据部分(去除填充)
return buffer.subarray(0, size);
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
更高性能更安全的实现