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