/** * Utilitários para autenticação e criptografia * Usando Web Crypto API para criptografia segura */ /** * Gera um hash seguro de senha usando PBKDF2 */ export async function hashPassword(password: string): Promise { const encoder = new TextEncoder(); const data = encoder.encode(password); // Gerar salt aleatório const salt = crypto.getRandomValues(new Uint8Array(16)); // Importar a senha como chave const keyMaterial = await crypto.subtle.importKey('raw', data, 'PBKDF2', false, ['deriveBits']); // Derivar a chave usando PBKDF2 const derivedBits = await crypto.subtle.deriveBits( { name: 'PBKDF2', salt: salt, iterations: 100000, hash: 'SHA-256' }, keyMaterial, 256 ); // Combinar salt + hash const hashArray = new Uint8Array(derivedBits); const combined = new Uint8Array(salt.length + hashArray.length); combined.set(salt); combined.set(hashArray, salt.length); // Converter para base64 return btoa(String.fromCharCode(...combined)); } /** * Verifica se uma senha corresponde ao hash */ export async function verifyPassword(password: string, hash: string): Promise { try { // Decodificar o hash de base64 const combined = Uint8Array.from(atob(hash), (c) => c.charCodeAt(0)); // Extrair salt e hash const salt = combined.slice(0, 16); const storedHash = combined.slice(16); // Gerar hash da senha fornecida const encoder = new TextEncoder(); const data = encoder.encode(password); const keyMaterial = await crypto.subtle.importKey('raw', data, 'PBKDF2', false, ['deriveBits']); const derivedBits = await crypto.subtle.deriveBits( { name: 'PBKDF2', salt: salt, iterations: 100000, hash: 'SHA-256' }, keyMaterial, 256 ); const newHash = new Uint8Array(derivedBits); // Comparar os hashes if (newHash.length !== storedHash.length) { return false; } for (let i = 0; i < newHash.length; i++) { if (newHash[i] !== storedHash[i]) { return false; } } return true; } catch (error) { console.error('Erro ao verificar senha:', error); return false; } } /** * Gera um token aleatório seguro */ export function generateToken(): string { const array = new Uint8Array(32); crypto.getRandomValues(array); return btoa(String.fromCharCode(...array)) .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=/g, ''); } /** * Valida formato de matrícula (apenas números) */ export function validarMatricula(matricula: string): boolean { return /^\d+$/.test(matricula) && matricula.length >= 3; } /** * Valida formato de senha (alfanuméricos e símbolos) */ export function validarSenha(senha: string): boolean { // Mínimo 8 caracteres, pelo menos uma letra, um número e um símbolo const regex = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/; return regex.test(senha); } /** * Criptografia reversível para senhas SMTP usando AES-GCM * NOTA: Esta função é usada apenas para senhas SMTP que precisam ser descriptografadas. * Para senhas de usuários, use hashPassword() que é unidirecional. */ // Chave de criptografia derivada (em produção, deve vir de variável de ambiente) // Para desenvolvimento, usando uma chave fixa. Em produção, deve ser configurada via env var. const getEncryptionKey = async (): Promise => { // Chave base - em produção, isso deve vir de process.env.ENCRYPTION_KEY // Por enquanto, usando uma chave derivada de um valor fixo const keyMaterial = new TextEncoder().encode('SGSE-EMAIL-ENCRYPTION-KEY-2024'); // Deriva uma chave de 256 bits usando PBKDF2 const key = await crypto.subtle.importKey('raw', keyMaterial, { name: 'PBKDF2' }, false, [ 'deriveBits', 'deriveKey' ]); return await crypto.subtle.deriveKey( { name: 'PBKDF2', salt: new TextEncoder().encode('SGSE-SALT'), iterations: 100000, hash: 'SHA-256' }, key, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt'] ); }; /** * Criptografa uma senha SMTP usando AES-GCM */ export async function encryptSMTPPassword(password: string): Promise { try { const key = await getEncryptionKey(); const encoder = new TextEncoder(); const data = encoder.encode(password); // Gerar IV (Initialization Vector) aleatório const iv = crypto.getRandomValues(new Uint8Array(12)); // Criptografar const encrypted = await crypto.subtle.encrypt( { name: 'AES-GCM', iv: iv }, key, data ); // Combinar IV + dados criptografados e converter para base64 const combined = new Uint8Array(iv.length + encrypted.byteLength); combined.set(iv); combined.set(new Uint8Array(encrypted), iv.length); return btoa(String.fromCharCode(...combined)); } catch (error) { console.error('Erro ao criptografar senha SMTP:', error); throw new Error('Falha ao criptografar senha SMTP'); } } /** * Descriptografa uma senha SMTP usando AES-GCM */ export async function decryptSMTPPassword(encryptedPassword: string): Promise { try { const key = await getEncryptionKey(); // Decodificar base64 const combined = Uint8Array.from(atob(encryptedPassword), (c) => c.charCodeAt(0)); // Extrair IV e dados criptografados const iv = combined.slice(0, 12); const encrypted = combined.slice(12); // Descriptografar const decrypted = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: iv }, key, encrypted ); // Converter para string const decoder = new TextDecoder(); return decoder.decode(decrypted); } catch (error) { console.error('Erro ao descriptografar senha SMTP:', error); throw new Error('Falha ao descriptografar senha SMTP'); } }