This commit is contained in:
@ -1,25 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file compressor.js
|
|
||||||
* @description 解压缩工具,使用pako完成与GoBetterStudio相同的deflate/raw解码
|
|
||||||
*/
|
|
||||||
|
|
||||||
import pako from 'pako';
|
|
||||||
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
|
|
||||||
export const compressor = {
|
|
||||||
/** 压缩字符串或字节数据 */
|
|
||||||
compress(data: Uint8Array | string): Uint8Array {
|
|
||||||
const source = typeof data === 'string' ? new TextEncoder().encode(data) : data;
|
|
||||||
return pako.deflateRaw(source);
|
|
||||||
},
|
|
||||||
|
|
||||||
/** 解压缩字节数据并返回字符串 */
|
|
||||||
decompress(data: ArrayBuffer | Uint8Array): string {
|
|
||||||
const input = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
||||||
const uncompressed = pako.inflateRaw(input);
|
|
||||||
return decoder.decode(uncompressed);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default compressor;
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
import md5 from 'js-md5';
|
|
||||||
|
|
||||||
const KEY_SIZE = 256 / 32;
|
|
||||||
const ITERATIONS = 1000;
|
|
||||||
|
|
||||||
type EncryptedAsset = {
|
|
||||||
value: Uint8Array;
|
|
||||||
Timestamp: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Credential = {
|
|
||||||
token: string;
|
|
||||||
uid: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type CryptoMaterial = {
|
|
||||||
obj: Uint8Array;
|
|
||||||
iv: Uint8Array;
|
|
||||||
salt: Uint8Array;
|
|
||||||
key: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
function prepareCryptoMaterial(data: EncryptedAsset, info: Credential): CryptoMaterial {
|
|
||||||
const userIdPrefix = info.uid.slice(0, 16);
|
|
||||||
const iv = new TextEncoder().encode(userIdPrefix);
|
|
||||||
const obj = data.value.slice(7);
|
|
||||||
const saltHex = md5(`${info.token}${data.Timestamp}`);
|
|
||||||
const saltBytes = new Uint8Array((saltHex.match(/.{1,2}/g) ?? []).map(byte => parseInt(byte, 16)));
|
|
||||||
return { obj, iv, salt: saltBytes, key: info.token };
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 从服务器返回的数据中异步解密出原始GLTF内容 */
|
|
||||||
export async function decryptAsync(data: EncryptedAsset, info: Credential): Promise<ArrayBuffer | null> {
|
|
||||||
const { obj, iv, salt, key } = prepareCryptoMaterial(data, info);
|
|
||||||
const derivedKey = await generateAesKeyAsync(key, salt, KEY_SIZE, ITERATIONS);
|
|
||||||
return aesDecryptAsync(obj, derivedKey, iv);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function generateAesKeyAsync(secret: string, salt: Uint8Array, keySize: number, iterations: number): Promise<CryptoKey> {
|
|
||||||
const passwordBuffer = new TextEncoder().encode(secret);
|
|
||||||
const baseKey = await crypto.subtle.importKey(
|
|
||||||
'raw',
|
|
||||||
passwordBuffer,
|
|
||||||
{ name: 'PBKDF2' },
|
|
||||||
false,
|
|
||||||
['deriveBits', 'deriveKey']
|
|
||||||
);
|
|
||||||
return crypto.subtle.deriveKey(
|
|
||||||
{
|
|
||||||
name: 'PBKDF2',
|
|
||||||
salt,
|
|
||||||
iterations,
|
|
||||||
hash: 'SHA-1'
|
|
||||||
},
|
|
||||||
baseKey,
|
|
||||||
{ name: 'AES-CBC', length: keySize * 32 },
|
|
||||||
false,
|
|
||||||
['decrypt']
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function aesDecryptAsync(data: Uint8Array, key: CryptoKey, iv: Uint8Array): Promise<ArrayBuffer | null> {
|
|
||||||
try {
|
|
||||||
return await crypto.subtle.decrypt(
|
|
||||||
{ name: 'AES-CBC', iv },
|
|
||||||
key,
|
|
||||||
data
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('解密失败:', error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user