APP-Backend / src /db /crypto.js
Luca448's picture
Update independent app to newest state with LFS and binary removals
ff36e71
Raw History Blame Contribute Delete
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 "";
}
}