| |
|
| |
|
| |
|
| | 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(); |