Spaces:
Running
Running
Download src/db/crypto.js from Luca448/APP-Backend: direct link, hf CLI and curl.
- Browser
- Download file 3.39 kB
-
https://huggingface.co/spaces/Luca448/APP-Backend/resolve/main/src/db/crypto.js
- Command line
-
hf download hf://spaces/Luca448/APP-Backend/src/db/crypto.js
-
curl -L -o crypto.js https://huggingface.co/spaces/Luca448/APP-Backend/resolve/main/src/db/crypto.js
3.39 kB
| // AES Encryption wrapper using Web Crypto API | |
| // In a real mobile app, this should be backed by the OS Keychain (via Capacitor Secure Storage). | |
| // For now, we generate a master key and store it in localStorage if it doesn't exist. | |
| function isCryptoAvailable() { | |
| return window.crypto && window.crypto.subtle; | |
| } | |
| // Fallback for non-secure contexts (HTTP via local IP on mobile) | |
| // In a real app, this should only run via HTTPS or capacitor:// where crypto.subtle is available. | |
| function fallbackEncrypt(text) { | |
| // Simple Base64 + rot13 obfuscation to prevent plain text in localstorage for development | |
| const rot13 = str => str.replace(/[a-zA-Z]/g, c => String.fromCharCode((c <= "Z" ? 90 : 122) >= (c = c.charCodeAt(0) + 13) ? c : c - 26)); | |
| return 'FALLBACK:' + btoa(rot13(text)); | |
| } | |
| function fallbackDecrypt(text) { | |
| if (!text.startsWith('FALLBACK:')) return text; // If it's old plain text or we screwed up | |
| const rot13 = str => str.replace(/[a-zA-Z]/g, c => String.fromCharCode((c <= "Z" ? 90 : 122) >= (c = c.charCodeAt(0) + 13) ? c : c - 26)); | |
| return rot13(atob(text.replace('FALLBACK:', ''))); | |
| } | |
| async function getMasterKey() { | |
| if (!isCryptoAvailable()) return null; | |
| let rawKey = localStorage.getItem('iserv_master_key'); | |
| if (!rawKey) { | |
| const key = await window.crypto.subtle.generateKey( | |
| { name: "AES-GCM", length: 256 }, | |
| true, | |
| ["encrypt", "decrypt"] | |
| ); | |
| const exported = await window.crypto.subtle.exportKey("raw", key); | |
| rawKey = btoa(String.fromCharCode(...new Uint8Array(exported))); | |
| localStorage.setItem('iserv_master_key', rawKey); | |
| } | |
| const keyBytes = Uint8Array.from(atob(rawKey), c => c.charCodeAt(0)); | |
| return window.crypto.subtle.importKey( | |
| "raw", | |
| keyBytes, | |
| { name: "AES-GCM" }, | |
| false, | |
| ["encrypt", "decrypt"] | |
| ); | |
| } | |
| export async function encryptPassword(password) { | |
| if (!password) return ""; | |
| if (!isCryptoAvailable()) { | |
| console.warn("Crypto Subtle API not available. Using fallback obfuscation."); | |
| return fallbackEncrypt(password); | |
| } | |
| const key = await getMasterKey(); | |
| const iv = window.crypto.getRandomValues(new Uint8Array(12)); | |
| const encoded = new TextEncoder().encode(password); | |
| const encrypted = await window.crypto.subtle.encrypt( | |
| { name: "AES-GCM", iv: iv }, | |
| key, | |
| encoded | |
| ); | |
| const ivBase64 = btoa(String.fromCharCode(...iv)); | |
| const encryptedBase64 = btoa(String.fromCharCode(...new Uint8Array(encrypted))); | |
| return `${ivBase64}:${encryptedBase64}`; | |
| } | |
| export async function decryptPassword(encryptedStr) { | |
| if (!encryptedStr) return ""; | |
| if (encryptedStr.startsWith('FALLBACK:')) { | |
| return fallbackDecrypt(encryptedStr); | |
| } | |
| if (!isCryptoAvailable()) { | |
| console.warn("Crypto Subtle API not available and string is not a fallback. Cannot decrypt."); | |
| return ""; | |
| } | |
| try { | |
| const parts = encryptedStr.split(':'); | |
| if (parts.length !== 2) return ""; | |
| const key = await getMasterKey(); | |
| const iv = Uint8Array.from(atob(parts[0]), c => c.charCodeAt(0)); | |
| const encryptedData = Uint8Array.from(atob(parts[1]), c => c.charCodeAt(0)); | |
| const decrypted = await window.crypto.subtle.decrypt( | |
| { name: "AES-GCM", iv: iv }, | |
| key, | |
| encryptedData | |
| ); | |
| return new TextDecoder().decode(decrypted); | |
| } catch (e) { | |
| console.error("Decryption failed", e); | |
| return ""; | |
| } | |
| } | |