склади и инструмент готовы
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user