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

146 lines
4.0 KiB
TypeScript

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);
}
}