NewHome/assets/js/navicat.js

348 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* navicat.js —— Navicat 数据库连接密码 本地解密 / 加密(纯前端,不上传服务器)
* 支持两种算法(依据 HyperSine《how-does-navicat-encrypt-password》实现
* 版本 11 及以前Blowfish/ECB/NoPadding + 自定义链式
* 密钥 = SHA1("3DC5CA39")IV = E(FFFFFFFFFFFFFFFF) = d9c7c3c8870d64bd无填充
* 版本 12 及以后AES-128-CBC/PKCS7
* 密钥 = "libcckeylibcckey"IV = "libcciv libcciv "
* 组件依赖(优先本地 assets/js/,缺失回退公共 CDN仅加载代码不含你的内容
* crypto-js 4.2.0 —— 版本 12 的 AES-128-CBC
* egoroof-blowfish 2.2.2 —— 版本 11 的 Blowfish/ECB 单块
*/
(function (global) {
'use strict';
var AES_KEY = 'libcckeylibcckey';
var AES_IV = 'libcciv libcciv ';
var BLOW_KEY_HEX = '42ceb271a5e458b74aea93947922354391873340'; // SHA1("3DC5CA39") 的 20 字节密钥
var BLOW_IV_HEX = 'd9c7c3c8870d64bd'; // Blowfish E(FFFFFFFFFFFFFFFF)
var LIBS = {
cryptojs: {
key: 'CryptoJS',
urls: [
'../assets/js/crypto-js.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js',
'https://cdn.jsdelivr.net/npm/crypto-js@4.2.0/crypto-js.min.js'
]
},
blowfish: {
key: 'Blowfish',
urls: [
'../assets/js/blowfish.js',
'https://cdn.jsdelivr.net/npm/egoroof-blowfish@2.2.2/dist/blowfish.js',
'https://unpkg.com/egoroof-blowfish@2.2.2/dist/blowfish.js'
]
}
};
var loadCache = {};
/* 懒加载指定组件(首个可用地址即可,返回 Promise<库对象|null> */
function loadLib(name) {
var spec = LIBS[name];
if (global[spec.key]) return Promise.resolve(global[spec.key]);
if (loadCache[name]) return loadCache[name];
loadCache[name] = new Promise(function (resolve) {
var go = function (idx) {
if (global[spec.key]) { resolve(global[spec.key]); return; }
if (idx >= spec.urls.length) { resolve(null); return; }
var s = document.createElement('script');
s.src = spec.urls[idx];
s.async = true;
s.onload = function () { resolve(global[spec.key] || null); };
s.onerror = function () { go(idx + 1); };
document.head.appendChild(s);
};
go(0);
});
return loadCache[name];
}
/* ================= 字节 / 编码工具 ================= */
function cleanHex(s) { return String(s == null ? '' : s).replace(/[^0-9a-fA-F]/g, ''); }
function hexToBytes(hex) {
var h = cleanHex(hex);
if (h.length % 2 !== 0) throw new Error('十六进制长度必须为偶数');
var out = new Uint8Array(h.length / 2);
for (var i = 0; i < out.length; i++) out[i] = parseInt(h.substr(i * 2, 2), 16);
return out;
}
function bytesToHex(bytes) {
var s = '';
for (var i = 0; i < bytes.length; i++) {
var b = bytes[i] & 0xff;
s += (b < 16 ? '0' : '') + b.toString(16);
}
return s;
}
function utf8Bytes(str) {
str = String(str);
if (typeof TextEncoder !== 'undefined') return new TextEncoder().encode(str);
var out = [], i, c;
for (i = 0; i < str.length; i++) {
c = str.charCodeAt(i);
if (c < 0x80) out.push(c);
else if (c < 0x800) out.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f));
else if (c < 0xd800 || c >= 0xe000) out.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f));
else {
var ch = (c - 0xd800) * 0x400 + (str.charCodeAt(++i) - 0xdc00) + 0x10000;
out.push(0xf0 | (ch >> 18), 0x80 | ((ch >> 12) & 0x3f), 0x80 | ((ch >> 6) & 0x3f), 0x80 | (ch & 0x3f));
}
}
return new Uint8Array(out);
}
function utf8Str(bytes) {
if (typeof TextDecoder !== 'undefined') return new TextDecoder('utf-8').decode(bytes);
var out = '', i = 0, b1, b2, b3, b4, c;
while (i < bytes.length) {
b1 = bytes[i++];
if (b1 < 0x80) out += String.fromCharCode(b1);
else if (b1 < 0xe0) { b2 = bytes[i++]; out += String.fromCharCode(((b1 & 0x1f) << 6) | (b2 & 0x3f)); }
else if (b1 < 0xf0) {
b2 = bytes[i++]; b3 = bytes[i++];
out += String.fromCharCode(((b1 & 0x0f) << 12) | ((b2 & 0x3f) << 6) | (b3 & 0x3f));
} else {
b2 = bytes[i++]; b3 = bytes[i++]; b4 = bytes[i++];
c = (((b1 & 0x07) << 18) | ((b2 & 0x3f) << 12) | ((b3 & 0x3f) << 6) | (b4 & 0x3f)) - 0x10000;
out += String.fromCharCode(0xd800 + (c >> 10), 0xdc00 + (c & 0x3ff));
}
}
return out;
}
function xorBytes(a, b) {
var n = Math.min(a.length, b.length), out = new Uint8Array(n);
for (var i = 0; i < n; i++) out[i] = a[i] ^ b[i];
return out;
}
function concatBytes(list) {
var len = 0, i;
for (i = 0; i < list.length; i++) len += list[i].length;
var out = new Uint8Array(len), off = 0;
for (i = 0; i < list.length; i++) { out.set(list[i], off); off += list[i].length; }
return out;
}
function ab(u8) { return u8.buffer.slice(u8.byteOffset, u8.byteOffset + u8.byteLength); }
function asU8(x) {
if (x instanceof Uint8Array) return x;
if (typeof ArrayBuffer !== 'undefined' && x instanceof ArrayBuffer) return new Uint8Array(x);
if (Array.isArray(x)) return new Uint8Array(x);
if (x && typeof x.length === 'number') return new Uint8Array(x);
throw new Error('无法识别的字节数据');
}
/* ================= 版本 11Blowfish 自定义链式(无填充) ================= */
function newBlowfish(Blowfish) {
return new Blowfish(ab(hexToBytes(BLOW_KEY_HEX)), Blowfish.MODE.ECB, Blowfish.PADDING.NULL);
}
function bfEnc(bf, block) { return asU8(bf.encode(ab(block))).slice(0, 8); }
function bfDec(bf, block, Blowfish) { return asU8(bf.decode(ab(block), Blowfish.TYPE.UINT8_ARRAY)).slice(0, 8); }
function encrypt11(Blowfish, text) {
var bf = newBlowfish(Blowfish);
var v = hexToBytes(BLOW_IV_HEX);
var data = utf8Bytes(text);
var rounds = Math.floor(data.length / 8);
var left = data.length % 8;
var out = '';
for (var i = 0; i < rounds; i++) {
var block = data.slice(i * 8, i * 8 + 8);
var temp = bfEnc(bf, xorBytes(block, v));
v = xorBytes(v, temp);
out += bytesToHex(temp).toUpperCase();
}
if (left > 0) {
v = bfEnc(bf, v);
out += bytesToHex(xorBytes(data.slice(rounds * 8), v)).toUpperCase();
}
return out;
}
function decrypt11(Blowfish, hex) {
var bf = newBlowfish(Blowfish);
var v = hexToBytes(BLOW_IV_HEX);
var data = hexToBytes(hex);
var rounds = Math.floor(data.length / 8);
var left = data.length % 8;
var parts = [];
for (var i = 0; i < rounds; i++) {
var block = data.slice(i * 8, i * 8 + 8);
parts.push(xorBytes(bfDec(bf, block, Blowfish), v));
v = xorBytes(v, block);
}
if (left > 0) {
v = bfEnc(bf, v);
parts.push(xorBytes(data.slice(rounds * 8), v));
}
return utf8Str(concatBytes(parts));
}
/* ================= 版本 12AES-128-CBC / PKCS7 ================= */
function pkcs7Pad(u8, block) {
var pad = block - (u8.length % block);
var out = new Uint8Array(u8.length + pad);
out.set(u8);
for (var i = 0; i < pad; i++) out[u8.length + i] = pad;
return out;
}
function pkcs7Unpad(u8, block) {
if (!u8.length || u8.length % block) return u8;
var pad = u8[u8.length - 1];
if (pad < 1 || pad > block || pad > u8.length) return u8; // 非合法填充:按原文返回
for (var i = u8.length - pad; i < u8.length; i++) {
if (u8[i] !== pad) return u8;
}
return u8.slice(0, u8.length - pad);
}
function u8ToWa(CryptoJS, u8) {
var words = [];
for (var i = 0; i < u8.length; i++) words[i >>> 2] = (words[i >>> 2] || 0) | (u8[i] << (24 - (i % 4) * 8));
return CryptoJS.lib.WordArray.create(words, u8.length);
}
function waToU8(wa) {
var words = wa.words, n = wa.sigBytes, out = new Uint8Array(n);
for (var i = 0; i < n; i++) out[i] = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
return out;
}
function encrypt12(CryptoJS, text) {
var key = CryptoJS.enc.Utf8.parse(AES_KEY);
var iv = CryptoJS.enc.Utf8.parse(AES_IV);
var data = u8ToWa(CryptoJS, pkcs7Pad(utf8Bytes(text), 16));
var enc = CryptoJS.AES.encrypt(data, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.NoPadding });
return enc.ciphertext.toString(CryptoJS.enc.Hex).toUpperCase();
}
function decrypt12(CryptoJS, hex) {
var key = CryptoJS.enc.Utf8.parse(AES_KEY);
var iv = CryptoJS.enc.Utf8.parse(AES_IV);
var ct = CryptoJS.enc.Hex.parse(cleanHex(hex).toLowerCase());
var dec = CryptoJS.AES.decrypt({ ciphertext: ct }, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.NoPadding });
return utf8Str(pkcs7Unpad(waToU8(dec), 16));
}
/* ================= .ncx 连接文件解析 ================= */
function decodeEntities(s) {
return String(s).replace(/&(amp|lt|gt|quot|apos|#\d+|#x[0-9a-f]+);/gi, function (m, g) {
var k = g.toLowerCase();
if (k === 'amp') return '&';
if (k === 'lt') return '<';
if (k === 'gt') return '>';
if (k === 'quot') return '"';
if (k === 'apos') return "'";
if (g.charAt(0) === '#') {
var code = g.charAt(1).toLowerCase() === 'x' ? parseInt(g.substr(2), 16) : parseInt(g.substr(1), 10);
return isNaN(code) ? m : String.fromCharCode(code);
}
return m;
});
}
/* 解析 .ncxNavicat 导出的 XML 连接文件),返回连接数组 */
function parseNcx(xmlText) {
var text = String(xmlText == null ? '' : xmlText);
if (!/\S/.test(text)) throw new Error('文件内容为空');
var connRe = /<Connection\b[^>]*?\/?>/gi;
var attrRe = /([A-Za-z_][\w:.-]*)\s*=\s*"([^"]*)"/g;
var out = [], m;
while ((m = connRe.exec(text))) {
var attrs = {}, a;
attrRe.lastIndex = 0;
while ((a = attrRe.exec(m[0]))) attrs[a[1]] = decodeEntities(a[2]);
out.push({
name: attrs.ConnectionName || '',
type: attrs.ConnType || '',
host: attrs.Host || '',
port: attrs.Port || '',
user: attrs.UserName || '',
password: attrs.Password || attrs.Pwd || '',
attrs: attrs
});
}
if (!out.length) throw new Error('未找到 <Connection> 节点,请确认是 Navicat 导出的 .ncx 文件');
return out;
}
function formatReport(rows, version) {
var lines = ['共解析 ' + rows.length + ' 个连接(密码按 Navicat ' + version + ' 解密)'];
for (var i = 0; i < rows.length; i++) {
var r = rows[i];
lines.push('');
lines.push('【' + (i + 1) + '】' + (r.name || '(未命名)') + (r.type ? '' + r.type + '' : ''));
lines.push(' 主机:' + (r.host || '(空)'));
lines.push(' 端口:' + (r.port || '(空)'));
lines.push(' 用户名:' + (r.user || '(空)'));
lines.push(' 密码:' + (r.plain ? r.plain : '(空)') + (r.plainNote || ''));
if (r.password && !/^null$/i.test(r.password)) lines.push(' 密码密文:' + r.password);
}
return lines.join('\n') + '\n';
}
/* 解析文件并解密每个连接密码返回规范格式文本Promise<string> */
function buildConnectionReport(xmlText, version) {
var ver = normalizeVersion(version);
var rows;
try { rows = parseNcx(xmlText); } catch (e) { return Promise.reject(e); }
return Promise.all(rows.map(function (c) {
var pwd = c.password;
var make = function (plain, note) {
return {
name: c.name, type: c.type, host: c.host, port: c.port, user: c.user,
password: pwd, plain: plain, plainNote: note || ''
};
};
if (!pwd || /^null$/i.test(pwd)) return Promise.resolve(make('', ''));
if (!/^[0-9a-fA-F]+$/.test(pwd) || pwd.length % 2 !== 0) {
return Promise.resolve(make(pwd, '(非十六进制密文,按原文显示)'));
}
return decrypt(pwd, ver).then(function (t) {
return make(t, t.indexOf('\uFFFD') >= 0 ? '(解密结果含乱码,请核对版本)' : '');
}).catch(function (e) {
return make(pwd, '(解密失败:' + (e && e.message ? e.message : e) + '');
});
})).then(function (list) {
return formatReport(list, ver);
});
}
/* ================= 对外接口 ================= */
function normalizeVersion(v) { return String(v) === '11' ? 11 : 12; }
function decrypt(cipher, version) {
var ver = normalizeVersion(version);
if (!cleanHex(cipher).length) return Promise.reject(new Error('请输入十六进制密文'));
return loadLib(ver === 11 ? 'blowfish' : 'cryptojs').then(function (lib) {
if (!lib) throw new Error('加密组件加载失败,请检查网络后重试');
return ver === 11 ? decrypt11(lib, cipher) : decrypt12(lib, cipher);
});
}
function encrypt(text, version) {
var ver = normalizeVersion(version);
if (text === '' || text == null) return Promise.reject(new Error('请输入明文'));
return loadLib(ver === 11 ? 'blowfish' : 'cryptojs').then(function (lib) {
if (!lib) throw new Error('加密组件加载失败,请检查网络后重试');
return ver === 11 ? encrypt11(lib, text) : encrypt12(lib, text);
});
}
global.NavicatCipher = {
decrypt: decrypt,
encrypt: encrypt,
buildConnectionReport: buildConnectionReport,
_core: {
encrypt11: encrypt11, decrypt11: decrypt11, encrypt12: encrypt12, decrypt12: decrypt12,
parseNcx: parseNcx, formatReport: formatReport,
hexToBytes: hexToBytes, bytesToHex: bytesToHex, utf8Bytes: utf8Bytes, utf8Str: utf8Str,
pkcs7Pad: pkcs7Pad, pkcs7Unpad: pkcs7Unpad
}
};
})(typeof window !== 'undefined' ? window : globalThis);