import crypto from "crypto"; /** * 기존 Java EncryptUtil과 동일한 AES 암호화 로직 * AES/ECB/NoPadding 방식 사용 */ export class EncryptUtil { private static readonly keyName = "ILJIAESSECRETKEY"; // 16자리 키 private static readonly algorithm = "AES"; /** * 문자열을 AES로 암호화 * @param source 원본 문자열 * @returns 암호화된 16진수 문자열 */ public static encrypt(source: string): string { try { const key = Buffer.from(this.keyName, "utf8"); const paddedSource = this.addPadding(Buffer.from(source, "utf8")); const cipher = crypto.createCipher("aes-128-ecb", key); cipher.setAutoPadding(false); // NoPadding 모드 const encrypted = Buffer.concat([ cipher.update(paddedSource), cipher.final(), ]); return this.fromHex(encrypted); } catch (error) { console.error("암호화 실패:", error); throw new Error("암호화 중 오류가 발생했습니다."); } } /** * 암호화된 문자열을 복호화 * @param source 암호화된 16진수 문자열 * @returns 복호화된 원본 문자열 */ public static decrypt(source: string): string { try { const key = Buffer.from(this.keyName, "utf8"); const encryptedBytes = this.toBytes(source); const decipher = crypto.createDecipher("aes-128-ecb", key); decipher.setAutoPadding(false); // NoPadding 모드 const decrypted = Buffer.concat([ decipher.update(encryptedBytes), decipher.final(), ]); const unpadded = this.removePadding(decrypted); return unpadded.toString("utf8"); } catch (error) { console.error("복호화 실패:", error); throw new Error("복호화 중 오류가 발생했습니다."); } } /** * SHA-256 해시 생성 * @param source 원본 문자열 * @returns SHA-256 해시 문자열 */ public static encryptSha256(source: string): string { try { const hash = crypto.createHash("sha256"); hash.update(source); return hash.digest("hex"); } catch (error) { console.error("SHA-256 해시 생성 실패:", error); throw new Error("해시 생성 중 오류가 발생했습니다."); } } /** * 패스워드 검증 (암호화된 패스워드와 평문 패스워드 비교) * @param plainPassword 평문 패스워드 * @param encryptedPassword 암호화된 패스워드 * @returns 일치 여부 */ public static matches( plainPassword: string, encryptedPassword: string ): boolean { try { const encrypted = this.encrypt(plainPassword); return encrypted === encryptedPassword; } catch (error) { console.error("패스워드 검증 실패:", error); return false; } } /** * 바이트 배열을 16진수 문자열로 변환 */ private static fromHex(bytes: Buffer): string { return bytes.toString("hex"); } /** * 16진수 문자열을 바이트 배열로 변환 */ private static toBytes(source: string): Buffer { const buffer = Buffer.alloc(source.length / 2); for (let i = 0; i < source.length; i += 2) { buffer[i / 2] = parseInt(source.substr(i, 2), 16); } return buffer; } /** * 패딩 추가 (16바이트 블록 크기에 맞춤) */ private static addPadding(bytes: Buffer): Buffer { const blockSize = 16; const paddingSize = blockSize - (bytes.length % blockSize); const padded = Buffer.alloc(bytes.length + paddingSize); bytes.copy(padded); // 나머지 부분을 0x00으로 채움 for (let i = bytes.length; i < padded.length; i++) { padded[i] = 0x00; } return padded; } /** * 패딩 제거 */ private static removePadding(bytes: Buffer): Buffer { let endIndex = bytes.length - 1; // 끝에서부터 0x00이 아닌 첫 번째 바이트를 찾음 while (endIndex >= 0 && bytes[endIndex] === 0x00) { endIndex--; } return bytes.slice(0, endIndex + 1); } }