|
|
|
|
|
|
| class ApiKeyEncryption {
|
| constructor() {
|
|
|
| this.salt = this.generateDeviceSalt();
|
| this.additionalSalt = 'Luna-OCR-2025-Security-Salt';
|
| }
|
|
|
|
|
| generateDeviceSalt() {
|
| const canvas = document.createElement('canvas');
|
| const ctx = canvas.getContext('2d');
|
| ctx.textBaseline = 'top';
|
| ctx.font = '14px Arial';
|
| ctx.fillText('Luna OCR Device Salt', 2, 2);
|
|
|
| const fingerprint = [
|
| navigator.userAgent,
|
| navigator.language,
|
| window.screen.width + 'x' + window.screen.height,
|
| new Date().getTimezoneOffset(),
|
| canvas.toDataURL()
|
| ].join('|');
|
|
|
| return this.simpleHash(fingerprint);
|
| }
|
|
|
|
|
| simpleHash(str) {
|
| let hash = 0;
|
| for (let i = 0; i < str.length; i++) {
|
| const char = str.charCodeAt(i);
|
| hash = ((hash << 5) - hash) + char;
|
| hash = hash & hash;
|
| }
|
| return Math.abs(hash).toString(36);
|
| }
|
|
|
|
|
| encrypt(text) {
|
| if (!text || text.trim() === '') return '';
|
|
|
|
|
| const timestamp = Date.now().toString(36);
|
| const randomPadding = Math.random().toString(36).substring(2);
|
| const paddedText = `${timestamp}:${text}:${randomPadding}`;
|
|
|
|
|
| const saltKey = this.salt + this.additionalSalt;
|
| let encrypted = '';
|
|
|
| for (let i = 0; i < paddedText.length; i++) {
|
| const textChar = paddedText.charCodeAt(i);
|
| const saltChar = saltKey.charCodeAt(i % saltKey.length);
|
| const encryptedChar = textChar ^ saltChar;
|
| encrypted += String.fromCharCode(encryptedChar);
|
| }
|
|
|
|
|
| const base64 = btoa(encrypted);
|
| const obfuscated = base64.split('').reverse().join('');
|
|
|
| return obfuscated;
|
| }
|
|
|
|
|
| decrypt(encryptedText) {
|
| if (!encryptedText || encryptedText.trim() === '') return '';
|
|
|
| try {
|
|
|
| const deobfuscated = encryptedText.split('').reverse().join('');
|
| const encrypted = atob(deobfuscated);
|
|
|
|
|
| const saltKey = this.salt + this.additionalSalt;
|
| let decrypted = '';
|
|
|
| for (let i = 0; i < encrypted.length; i++) {
|
| const encryptedChar = encrypted.charCodeAt(i);
|
| const saltChar = saltKey.charCodeAt(i % saltKey.length);
|
| const decryptedChar = encryptedChar ^ saltChar;
|
| decrypted += String.fromCharCode(decryptedChar);
|
| }
|
|
|
|
|
| const parts = decrypted.split(':');
|
| if (parts.length >= 3) {
|
|
|
| return parts.slice(1, -1).join(':');
|
| }
|
|
|
| return decrypted;
|
| } catch (error) {
|
| console.warn('Failed to decrypt API key:', error);
|
| return '';
|
| }
|
| }
|
|
|
|
|
| storeApiKey(apiKey) {
|
| if (!apiKey || apiKey.trim() === '') {
|
| localStorage.removeItem('luna_secure_config_v2');
|
| return;
|
| }
|
|
|
| const encrypted = this.encrypt(apiKey);
|
| localStorage.setItem('luna_secure_config_v2', encrypted);
|
| }
|
|
|
|
|
| retrieveApiKey() {
|
| const encrypted = localStorage.getItem('luna_secure_config_v2');
|
| if (!encrypted) return '';
|
|
|
| return this.decrypt(encrypted);
|
| }
|
|
|
|
|
| clearApiKey() {
|
| localStorage.removeItem('luna_secure_config_v2');
|
| }
|
|
|
|
|
| hasStoredApiKey() {
|
| return !!localStorage.getItem('luna_secure_config_v2');
|
| }
|
| }
|
|
|
| export default new ApiKeyEncryption(); |