Files

94 lines
2.3 KiB
JavaScript

export async function deriveKeyFromSecret(secret, salt = 'toolbox_salt_v1') {
if (!secret || typeof secret !== 'string') {
throw new Error('Invalid secret: must be a non-empty string');
}
const enc = new TextEncoder();
const secretKey = await crypto.subtle.importKey(
'raw',
enc.encode(secret),
{ name: 'PBKDF2' },
false,
['deriveKey']
);
const key = await crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: enc.encode(salt),
iterations: 100000,
hash: 'SHA-256'
},
secretKey,
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);
return key;
}
function _randBytes(len = 12) {
const b = new Uint8Array(len);
crypto.getRandomValues(b);
return b;
}
export async function encryptJSON(key, obj) {
if (!key || !(key instanceof CryptoKey)) {
throw new Error('Valid CryptoKey is required');
}
const enc = new TextEncoder();
const iv = _randBytes(12);
const plain = enc.encode(JSON.stringify(obj));
const cipher = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv: iv },
key,
plain
);
const combined = new Uint8Array(iv.byteLength + cipher.byteLength);
combined.set(iv, 0);
combined.set(new Uint8Array(cipher), iv.byteLength);
// Безопасное преобразование в base64
const binaryString = Array.from(combined, byte =>
String.fromCharCode(byte)).join('');
return btoa(binaryString);
}
export async function decryptToJSON(key, b64) {
if (!key || !(key instanceof CryptoKey)) {
throw new Error('Valid CryptoKey is required');
}
if (!b64 || typeof b64 !== 'string') {
throw new Error('Invalid base64 string');
}
try {
// Безопасное преобразование из base64
const binaryString = atob(b64);
const raw = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
raw[i] = binaryString.charCodeAt(i);
}
const iv = raw.slice(0, 12);
const cipher = raw.slice(12);
const plainBuf = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: iv },
key,
cipher
);
const dec = new TextDecoder();
return JSON.parse(dec.decode(plainBuf));
} catch (error) {
console.error('Decryption error:', error);
throw new Error('Failed to decrypt data: ' + error.message);
}
}