/* 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('无法识别的字节数据'); } /* ================= 版本 11:Blowfish 自定义链式(无填充) ================= */ 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)); } /* ================= 版本 12:AES-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; }); } /* 解析 .ncx(Navicat 导出的 XML 连接文件),返回连接数组 */ function parseNcx(xmlText) { var text = String(xmlText == null ? '' : xmlText); if (!/\S/.test(text)) throw new Error('文件内容为空'); var connRe = /]*?\/?>/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('未找到 节点,请确认是 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) */ 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);