146 lines
4.0 KiB
TypeScript
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);
|
|
}
|
|
}
|