// 비밀번호 암호화/복호화 유틸리티 // 작성일: 2024-12-17 import crypto from "crypto"; export class PasswordEncryption { private static readonly ALGORITHM = "aes-256-cbc"; private static readonly SECRET_KEY = process.env.DB_PASSWORD_SECRET || "default-fallback-key-change-in-production"; private static readonly IV_LENGTH = 16; // AES-CBC의 경우 16바이트 /** * 비밀번호를 암호화합니다. * @param password 암호화할 평문 비밀번호 * @returns 암호화된 비밀번호 (base64 인코딩) */ static encrypt(password: string): string { try { // 랜덤 IV 생성 const iv = crypto.randomBytes(this.IV_LENGTH); // 암호화 키 생성 (SECRET_KEY를 해시하여 32바이트 키 생성) const key = crypto.scryptSync(this.SECRET_KEY, "salt", 32); // 암호화 객체 생성 const cipher = crypto.createCipher("aes-256-cbc", key); // 암호화 실행 let encrypted = cipher.update(password, "utf8", "hex"); encrypted += cipher.final("hex"); // IV와 암호화된 데이터를 결합하여 반환 return `${iv.toString("hex")}:${encrypted}`; } catch (error) { console.error("Password encryption failed:", error); throw new Error("비밀번호 암호화에 실패했습니다."); } } /** * 암호화된 비밀번호를 복호화합니다. * @param encryptedPassword 암호화된 비밀번호 * @returns 복호화된 평문 비밀번호 */ static decrypt(encryptedPassword: string): string { try { // IV와 암호화된 데이터 분리 const parts = encryptedPassword.split(":"); if (parts.length !== 2) { throw new Error("잘못된 암호화된 비밀번호 형식입니다."); } const iv = Buffer.from(parts[0], "hex"); const encrypted = parts[1]; // 암호화 키 생성 (암호화 시와 동일) const key = crypto.scryptSync(this.SECRET_KEY, "salt", 32); // 복호화 객체 생성 const decipher = crypto.createDecipher("aes-256-cbc", key); // 복호화 실행 let decrypted = decipher.update(encrypted, "hex", "utf8"); decrypted += decipher.final("utf8"); return decrypted; } catch (error) { console.error("Password decryption failed:", error); throw new Error("비밀번호 복호화에 실패했습니다."); } } /** * 암호화 키가 설정되어 있는지 확인합니다. * @returns 키 설정 여부 */ static isKeyConfigured(): boolean { return ( process.env.DB_PASSWORD_SECRET !== undefined && process.env.DB_PASSWORD_SECRET !== "" ); } /** * 암호화/복호화 기능을 테스트합니다. * @returns 테스트 결과 */ static testEncryption(): { success: boolean; message: string } { try { const testPassword = "test123!@#"; const encrypted = this.encrypt(testPassword); const decrypted = this.decrypt(encrypted); if (testPassword === decrypted) { return { success: true, message: "암호화/복호화 테스트가 성공했습니다.", }; } else { return { success: false, message: "암호화/복호화 결과가 일치하지 않습니다.", }; } } catch (error) { return { success: false, message: `암호화/복호화 테스트 실패: ${error}`, }; } } }