mirror of
https://github.com/stackblitz/bolt.new
synced 2025-06-26 18:17:50 +00:00
- Enhanced encryption system with AES-GCM and PBKDF2 - Added secure authentication system with session management - Implemented security middleware with headers and rate limiting - Created secure storage utilities for sensitive data - Added login system with brute force protection - Implemented security components (password strength, 2FA) - Added security utility functions for threat detection Copyright (c) 2024 Ervin Remus Radosavlevici
129 lines
3.6 KiB
TypeScript
129 lines
3.6 KiB
TypeScript
/**
|
|
* Enhanced encryption utilities
|
|
* Copyright (c) 2024 Ervin Remus Radosavlevici
|
|
* All rights reserved.
|
|
*/
|
|
|
|
const encoder = new TextEncoder();
|
|
const decoder = new TextDecoder();
|
|
const IV_LENGTH = 16;
|
|
const SALT_LENGTH = 16;
|
|
const ITERATIONS = 100000; // Higher iteration count for PBKDF2
|
|
const KEY_LENGTH = 256; // Using AES-GCM with 256-bit key
|
|
|
|
/**
|
|
* Encrypts data with a key using AES-GCM (more secure than AES-CBC)
|
|
* Includes salt for key derivation and authentication tag for integrity
|
|
*/
|
|
export async function encrypt(key: string, data: string) {
|
|
// Generate random salt and IV
|
|
const salt = crypto.getRandomValues(new Uint8Array(SALT_LENGTH));
|
|
const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));
|
|
|
|
// Derive a strong key using PBKDF2
|
|
const cryptoKey = await deriveKey(key, salt);
|
|
|
|
// Encrypt with AES-GCM (includes authentication)
|
|
const ciphertext = await crypto.subtle.encrypt(
|
|
{
|
|
name: 'AES-GCM',
|
|
iv,
|
|
tagLength: 128, // Authentication tag length
|
|
},
|
|
cryptoKey,
|
|
encoder.encode(data),
|
|
);
|
|
|
|
// Combine salt + IV + ciphertext into a single array
|
|
const bundle = new Uint8Array(SALT_LENGTH + IV_LENGTH + ciphertext.byteLength);
|
|
bundle.set(salt, 0);
|
|
bundle.set(iv, SALT_LENGTH);
|
|
bundle.set(new Uint8Array(ciphertext), SALT_LENGTH + IV_LENGTH);
|
|
|
|
// Return as base64 string
|
|
return arrayBufferToBase64(bundle);
|
|
}
|
|
|
|
/**
|
|
* Decrypts data that was encrypted with the encrypt function
|
|
*/
|
|
export async function decrypt(key: string, payload: string) {
|
|
try {
|
|
// Convert base64 string back to array buffer
|
|
const bundle = base64ToArrayBuffer(payload);
|
|
|
|
// Extract salt, IV, and ciphertext
|
|
const salt = new Uint8Array(bundle.slice(0, SALT_LENGTH));
|
|
const iv = new Uint8Array(bundle.slice(SALT_LENGTH, SALT_LENGTH + IV_LENGTH));
|
|
const ciphertext = new Uint8Array(bundle.slice(SALT_LENGTH + IV_LENGTH));
|
|
|
|
// Derive the same key using the stored salt
|
|
const cryptoKey = await deriveKey(key, salt);
|
|
|
|
// Decrypt with AES-GCM (automatically verifies authentication tag)
|
|
const plaintext = await crypto.subtle.decrypt(
|
|
{
|
|
name: 'AES-GCM',
|
|
iv,
|
|
tagLength: 128,
|
|
},
|
|
cryptoKey,
|
|
ciphertext,
|
|
);
|
|
|
|
return decoder.decode(plaintext);
|
|
} catch (error) {
|
|
console.error('Decryption failed:', error);
|
|
throw new Error('Failed to decrypt data. The key may be incorrect or the data may have been tampered with.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Derives a cryptographic key from a password and salt using PBKDF2
|
|
*/
|
|
async function deriveKey(password: string, salt: Uint8Array) {
|
|
// First, create a key from the password
|
|
const baseKey = await crypto.subtle.importKey(
|
|
'raw',
|
|
encoder.encode(password),
|
|
{ name: 'PBKDF2' },
|
|
false,
|
|
['deriveKey']
|
|
);
|
|
|
|
// Then derive a key suitable for AES-GCM
|
|
return await crypto.subtle.deriveKey(
|
|
{
|
|
name: 'PBKDF2',
|
|
salt,
|
|
iterations: ITERATIONS,
|
|
hash: 'SHA-256',
|
|
},
|
|
baseKey,
|
|
{ name: 'AES-GCM', length: KEY_LENGTH },
|
|
false,
|
|
['encrypt', 'decrypt']
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Converts an ArrayBuffer to a Base64 string
|
|
*/
|
|
function arrayBufferToBase64(buffer: ArrayBuffer): string {
|
|
const bytes = new Uint8Array(buffer);
|
|
const binString = Array.from(bytes, byte => String.fromCharCode(byte)).join('');
|
|
return btoa(binString);
|
|
}
|
|
|
|
/**
|
|
* Converts a Base64 string to an ArrayBuffer
|
|
*/
|
|
function base64ToArrayBuffer(base64: string): ArrayBuffer {
|
|
const binString = atob(base64);
|
|
const bytes = new Uint8Array(binString.length);
|
|
for (let i = 0; i < binString.length; i++) {
|
|
bytes[i] = binString.charCodeAt(i);
|
|
}
|
|
return bytes.buffer;
|
|
}
|