Files
YMhut-box-C-/third_party/MoeKoeMusic/api/util/crypto.js
T

319 lines
8.9 KiB
JavaScript

const CryptoJS = require('crypto-js');
const forge = require('node-forge');
const { randomString } = require('./util');
const publicRasKey = `-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDIAG7QOELSYoIJvTFJhMpe1s/gbjDJX51HBNnEl5HXqTW6lQ7LC8jr9fWZTwusknp+sVGzwd40MwP6U5yDE27M/X1+UR4tvOGOqp94TJtQ1EPnWGWXngpeIW5GxoQGao1rmYWAu6oi1z9XkChrsUdC6DJE5E221wf/4WLFxwAtRQIDAQAB\n-----END PUBLIC KEY-----`;
const publicLiteRasKey = `-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDECi0Np2UR87scwrvTr72L6oO01rBbbBPriSDFPxr3Z5syug0O24QyQO8bg27+0+4kBzTBTBOZ/WWU0WryL1JSXRTXLgFVxtzIY41Pe7lPOgsfTCn5kZcvKhYKJesKnnJDNr5/abvTGf+rHG3YRwsCHcQ08/q6ifSioBszvb3QiwIDAQAB\n-----END PUBLIC KEY-----`;
const rsaKeyCache = new Map();
function encodeUtf8(str) {
if (typeof TextEncoder !== 'undefined') {
return new TextEncoder().encode(str);
}
if (typeof Buffer !== 'undefined') {
return new Uint8Array(Buffer.from(str, 'utf8'));
}
const codePoints = [];
for (let i = 0; i < str.length; i++) {
let code = str.charCodeAt(i);
if (code >= 0xd800 && code <= 0xdbff && i + 1 < str.length) {
const next = str.charCodeAt(i + 1);
if (next >= 0xdc00 && next <= 0xdfff) {
code = ((code - 0xd800) << 10) + (next - 0xdc00) + 0x10000;
i++;
}
}
codePoints.push(code);
}
const bytes = [];
for (const code of codePoints) {
if (code <= 0x7f) {
bytes.push(code);
} else if (code <= 0x7ff) {
bytes.push(
0xc0 | (code >> 6),
0x80 | (code & 0x3f),
);
} else if (code <= 0xffff) {
bytes.push(
0xe0 | (code >> 12),
0x80 | ((code >> 6) & 0x3f),
0x80 | (code & 0x3f),
);
} else {
bytes.push(
0xf0 | (code >> 18),
0x80 | ((code >> 12) & 0x3f),
0x80 | ((code >> 6) & 0x3f),
0x80 | (code & 0x3f),
);
}
}
return new Uint8Array(bytes);
}
function decodeUtf8(uint8) {
if (typeof TextDecoder !== 'undefined') {
return new TextDecoder().decode(uint8);
}
if (typeof Buffer !== 'undefined') {
return Buffer.from(uint8).toString('utf8');
}
let out = '';
let i = 0;
while (i < uint8.length) {
const byte1 = uint8[i++];
if (byte1 < 0x80) {
out += String.fromCharCode(byte1);
continue;
}
if (byte1 < 0xe0) {
const byte2 = uint8[i++] & 0x3f;
const codePoint = ((byte1 & 0x1f) << 6) | byte2;
out += String.fromCharCode(codePoint);
continue;
}
if (byte1 < 0xf0) {
const byte2 = uint8[i++] & 0x3f;
const byte3 = uint8[i++] & 0x3f;
const codePoint = ((byte1 & 0x0f) << 12) | (byte2 << 6) | byte3;
out += String.fromCharCode(codePoint);
continue;
}
const byte2 = uint8[i++] & 0x3f;
const byte3 = uint8[i++] & 0x3f;
const byte4 = uint8[i++] & 0x3f;
let codePoint = ((byte1 & 0x07) << 18) | (byte2 << 12) | (byte3 << 6) | byte4;
codePoint -= 0x10000;
out += String.fromCharCode(
(codePoint >> 10) + 0xd800,
(codePoint & 0x3ff) + 0xdc00,
);
}
return out;
}
function normalizeBuffer(data) {
if (data instanceof Uint8Array) return data;
const str = typeof data === 'string' ? data : JSON.stringify(data);
return encodeUtf8(str);
}
function wordArrayFromBuffer(uint8) {
const words = [];
for (let i = 0; i < uint8.length; i += 4) {
words.push(
((uint8[i] || 0) << 24) | ((uint8[i + 1] || 0) << 16) |
((uint8[i + 2] || 0) << 8) | (uint8[i + 3] || 0)
);
}
return CryptoJS.lib.WordArray.create(words, uint8.length);
}
function wordArrayToBuffer(wordArray) {
const { words, sigBytes } = wordArray;
const uint8 = new Uint8Array(sigBytes);
for (let i = 0; i < sigBytes; i++) {
uint8[i] = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
}
return uint8;
}
function uint8ArrayToHex(arr) {
return Array.from(arr).map(b => b.toString(16).padStart(2, '0')).join('');
}
function utf8WordArray(input) {
return typeof input === 'string' ? CryptoJS.enc.Utf8.parse(input) : wordArrayFromBuffer(input);
}
function getForgePublicKey(pem) {
if (!rsaKeyCache.has(pem)) {
rsaKeyCache.set(pem, forge.pki.publicKeyFromPem(pem));
}
return rsaKeyCache.get(pem);
}
function bufferToBinaryString(buffer) {
let out = '';
for (let i = 0; i < buffer.length; i++) out += String.fromCharCode(buffer[i]);
return out;
}
function rsaRawEncrypt(buffer, publicKey) {
const keyLength = Math.ceil(publicKey.n.bitLength() / 8);
const message = new forge.jsbn.BigInteger(uint8ArrayToHex(buffer), 16);
const encrypted = message.modPow(publicKey.e, publicKey.n);
return encrypted.toString(16).padStart(keyLength * 2, '0');
}
/**
* @typedef {{str: string, key: string}} AesEncrypt
*/
/**
* MD5 加密
* @param {BufferLike} data
* @returns {string}
*/
function cryptoMd5(data) {
const buffer = typeof data === 'object' ? JSON.stringify(data) : data;
return CryptoJS.MD5(buffer).toString(CryptoJS.enc.Hex);
}
/**
* Sha1 加密
* @param {BufferLike} data
* @returns { string }
*/
function cryptoSha1(data) {
const buffer = typeof data === 'object' ? JSON.stringify(data) : data;
return CryptoJS.SHA1(buffer).toString(CryptoJS.enc.Hex);
}
/**
* AES 加密
* @param {BufferLike} data 需要加密的数据
* @param {{ key?:string, iv?: string } | undefined} opt
* @returns {AesEncrypt | string}
*/
function cryptoAesEncrypt(data, opt) {
if (typeof data === 'object') data = JSON.stringify(data);
const buffer = normalizeBuffer(data);
let key;
let iv;
let tempKey = '';
if (opt?.key && opt?.iv) {
key = opt.key;
iv = opt.iv;
} else {
tempKey = opt?.key || randomString(16).toLowerCase();
key = cryptoMd5(tempKey).substring(0, 32);
iv = key.substring(key.length - 16);
}
const encrypted = CryptoJS.AES.encrypt(wordArrayFromBuffer(buffer), utf8WordArray(key), {
iv: utf8WordArray(iv),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
});
const hex = CryptoJS.enc.Hex.stringify(encrypted.ciphertext);
if (opt?.key && opt?.key) return hex;
return { str: hex, key: tempKey };
}
/**
* AES 解密
* @param {string} data
* @param {string} key
* @param {string?} iv
* @returns {string | Record<string, string>}
*/
function cryptoAesDecrypt(data, key, iv) {
if (!iv) key = cryptoMd5(key).substring(0, 32);
iv = iv || key.substring(key.length - 16);
const cipherParams = CryptoJS.lib.CipherParams.create({ ciphertext: CryptoJS.enc.Hex.parse(data) });
const decrypted = CryptoJS.AES.decrypt(cipherParams, utf8WordArray(key), {
iv: utf8WordArray(iv),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
});
const text = decodeUtf8(wordArrayToBuffer(decrypted));
try {
return JSON.parse(text);
} catch (e) {
return text;
}
}
/**
* RSA加密
* @param {BufferLike} data
* @param {string?} publicKey
* @returns {string} hex
*/
function cryptoRSAEncrypt(data, publicKey) {
const isLite = process.env.platform === 'lite';
const buffer = normalizeBuffer(data);
const pem = publicKey || (isLite ? publicLiteRasKey : publicRasKey);
const key = getForgePublicKey(pem);
const keyLength = Math.ceil(key.n.bitLength() / 8);
if (buffer.length > keyLength) throw new Error('Data length exceeds key size');
let padded = buffer;
if (buffer.length < keyLength) {
padded = new Uint8Array(keyLength);
padded.set(buffer);
}
return rsaRawEncrypt(padded, key);
}
function rsaEncrypt2(data) {
const isLite = process.env.platform === 'lite';
const buffer = normalizeBuffer(data);
const key = getForgePublicKey(isLite ? publicLiteRasKey : publicRasKey);
const encrypted = key.encrypt(bufferToBinaryString(buffer), 'RSAES-PKCS1-V1_5');
return forge.util.bytesToHex(encrypted);
}
function playlistAesEncrypt(data) {
const useData = typeof data === 'object' ? JSON.stringify(data) : data;
const key = randomString(6).toLowerCase();
const encryptKey = cryptoMd5(key).substring(0, 16);
const iv = cryptoMd5(key).substring(16, 32);
const encrypted = CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(useData), utf8WordArray(encryptKey), {
iv: utf8WordArray(iv),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
});
return { key, str: CryptoJS.enc.Base64.stringify(encrypted.ciphertext) };
}
function playlistAesDecrypt(data) {
const encryptKey = cryptoMd5(data.key).substring(0, 16);
const iv = cryptoMd5(data.key).substring(16, 32);
const cipherParams = CryptoJS.lib.CipherParams.create({ ciphertext: CryptoJS.enc.Base64.parse(data.str) });
const decrypted = CryptoJS.AES.decrypt(cipherParams, utf8WordArray(encryptKey), {
iv: utf8WordArray(iv),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
});
const text = decodeUtf8(wordArrayToBuffer(decrypted));
try {
return JSON.parse(text);
} catch (e) {
return text;
}
}
module.exports = {
cryptoAesDecrypt,
cryptoAesEncrypt,
cryptoMd5,
cryptoRSAEncrypt,
rsaEncrypt2,
cryptoSha1,
playlistAesEncrypt,
playlistAesDecrypt,
publicLiteRasKey,
publicRasKey,
wordArrayFromBuffer,
};