ERP-node/backend-node/src/utils/passwordEncryption.ts

137 lines
4.4 KiB
TypeScript
Raw Normal View History

2025-09-18 09:32:50 +09:00
// 비밀번호 암호화/복호화 유틸리티
// 작성일: 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);
// 암호화 객체 생성 (IV를 명시적으로 사용)
const cipher = crypto.createCipheriv(this.ALGORITHM, key, iv);
2025-09-18 09:32:50 +09:00
// 암호화 실행
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);
try {
// 새로운 방식: createDecipheriv 사용 (IV 명시적 사용)
const decipher = crypto.createDecipheriv(this.ALGORITHM, key, iv);
let decrypted = decipher.update(encrypted, "hex", "utf8");
decrypted += decipher.final("utf8");
return decrypted;
} catch (newFormatError: unknown) {
const errorMessage =
newFormatError instanceof Error
? newFormatError.message
: String(newFormatError);
console.warn(
"새로운 복호화 방식 실패, 기존 방식으로 시도:",
errorMessage
);
2025-09-18 09:32:50 +09:00
try {
// 기존 방식: createDecipher 사용 (하위 호환성)
const decipher = crypto.createDecipher("aes-256-cbc", key);
let decrypted = decipher.update(encrypted, "hex", "utf8");
decrypted += decipher.final("utf8");
return decrypted;
} catch (oldFormatError: unknown) {
const oldErrorMessage =
oldFormatError instanceof Error
? oldFormatError.message
: String(oldFormatError);
console.error("기존 복호화 방식도 실패:", oldErrorMessage);
throw oldFormatError;
}
}
2025-09-18 09:32:50 +09:00
} 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}`,
};
}
}
}