Skip to content

Commit c916f56

Browse files
authored
[24.11.26 / TASK-40] Feature - token encrytion (#2)
* feature: token 관련 기본 디렉토리, 모듈 세팅 * feature: token 테스트 완료, 관련 세팅 업데이트 완료 * modify: 타이니한 주석 정도 수정
1 parent 3cd543e commit c916f56

File tree

4 files changed

+146
-5
lines changed

4 files changed

+146
-5
lines changed

.env.sample

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,15 @@ DB_PASSWORD=""
44
DB_HOST=""
55
DB_PORT=5432
66
DB_NAME=""
7+
8+
# 토큰 단방향 암호화 전용 핵심 키
9+
AES_KEY_0=01a8b93c17f8d6e4fba3c5d91e7a6f45
10+
AES_KEY_1=01a8b93c17f8d6e4fba3c5d91e7a6f45
11+
AES_KEY_2=01a8b93c17f8d6e4fba3c5d91e7a6f45
12+
AES_KEY_3=01a8b93c17f8d6e4fba3c5d91e7a6f45
13+
AES_KEY_4=01a8b93c17f8d6e4fba3c5d91e7a6f45
14+
AES_KEY_5=01a8b93c17f8d6e4fba3c5d91e7a6f45
15+
AES_KEY_6=01a8b93c17f8d6e4fba3c5d91e7a6f45
16+
AES_KEY_7=01a8b93c17f8d6e4fba3c5d91e7a6f45
17+
AES_KEY_8=01a8b93c17f8d6e4fba3c5d91e7a6f45
18+
AES_KEY_9=01a8b93c17f8d6e4fba3c5d91e7a6f45

README.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010

1111
```bash
1212
pnpm install
13+
14+
# 만약 pnpm 이 없다면
15+
brew install pnpm
1316
```
1417

1518
2. 환경 변수 설정
@@ -27,11 +30,11 @@ pnpm dev
2730

2831
## 실행 가능한 명령어
2932

30-
```
31-
pnpm dev: 개발 서버 실행
32-
pnpm test: 테스트 실행
33-
pnpm lint: 린트 검사
34-
pnpm lint:fix: 린트 자동 수정
33+
```bash
34+
pnpm dev # 개발 서버 실행
35+
pnpm test # 테스트 실행
36+
pnpm lint # 린트 검사
37+
pnpm lint:fix # 린트 자동 수정
3538
```
3639

3740
## Project Structure
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import crypto from 'crypto';
2+
import AESEncryption from '../token_encryption/aes_encryption';
3+
4+
describe('AESEncryption 클래스 테스트', () => {
5+
const validKey = crypto.randomBytes(32).toString('hex').slice(0, 32); // 32바이트 키 생성
6+
const invalidKey = crypto.randomBytes(16).toString('hex').slice(0, 16); // 16바이트 키 생성 (유효하지 않음)
7+
const aes = new AESEncryption(validKey);
8+
const sampleText = 'This is a test message for AES encryption!';
9+
10+
test('암호화 후 복호화 결과가 원본과 동일해야 한다', () => {
11+
const encrypted = aes.encrypt(sampleText);
12+
const decrypted = aes.decrypt(encrypted);
13+
expect(decrypted).toBe(sampleText);
14+
});
15+
16+
test('잘못된 키 길이를 사용하면 오류가 발생해야 한다', () => {
17+
expect(() => new AESEncryption(invalidKey)).toThrow(
18+
'키는 256비트(32바이트)여야 합니다.'
19+
);
20+
});
21+
22+
test('암호화 결과는 base64 형식이어야 한다', () => {
23+
const encrypted = aes.encrypt(sampleText);
24+
expect(() => Buffer.from(encrypted, 'base64')).not.toThrow();
25+
});
26+
27+
test('동일한 데이터를 여러 번 암호화해도 결과가 달라야 한다 (IV 사용 확인)', () => {
28+
const encrypted1 = aes.encrypt(sampleText);
29+
const encrypted2 = aes.encrypt(sampleText);
30+
expect(encrypted1).not.toBe(encrypted2);
31+
});
32+
33+
test('빈 문자열도 암호화 및 복호화가 정상적으로 동작해야 한다', () => {
34+
const encrypted = aes.encrypt('');
35+
const decrypted = aes.decrypt(encrypted);
36+
expect(decrypted).toBe('');
37+
});
38+
39+
test('큰 입력 데이터를 암호화 및 복호화해도 원본과 동일해야 한다', () => {
40+
const largeText = 'A'.repeat(10_000); // 10,000자 문자열
41+
const encrypted = aes.encrypt(largeText);
42+
const decrypted = aes.decrypt(encrypted);
43+
expect(decrypted).toBe(largeText);
44+
});
45+
46+
test('유니코드 문자열도 암호화 및 복호화가 정상적으로 동작해야 한다', () => {
47+
const unicodeText = '안녕하세요! AES 암호화 테스트입니다. 🚀';
48+
const encrypted = aes.encrypt(unicodeText);
49+
const decrypted = aes.decrypt(encrypted);
50+
expect(decrypted).toBe(unicodeText);
51+
});
52+
53+
test('잘못된 암호화 데이터는 복호화 시 오류가 발생해야 한다', () => {
54+
const invalidData = 'invalid_encrypted_data';
55+
expect(() => aes.decrypt(invalidData)).toThrow();
56+
});
57+
58+
test('손상된 암호화 데이터는 복호화 시 오류가 발생해야 한다', () => {
59+
const encrypted = aes.encrypt(sampleText);
60+
const corruptedData = encrypted.slice(0, -4) + 'abcd'; // 암호화 데이터 끝부분 손상
61+
expect(() => aes.decrypt(corruptedData)).toThrow();
62+
});
63+
});
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import crypto from 'crypto';
2+
3+
/**
4+
* AES 암호화/복호화 유틸리티 클래스
5+
* AES-256-CBC 알고리즘을 사용하여 데이터를 암호화하고 복호화합니다.
6+
*/
7+
class AESEncryption {
8+
private readonly key: Buffer;
9+
10+
/**
11+
* AES 암호화 클래스를 초기화합니다.
12+
* @param {string} key - 256비트(32바이트) 암호화 키
13+
* @throws {Error} 키가 256비트가 아닌 경우 예외를 발생시킵니다.
14+
*/
15+
constructor(key: string) {
16+
if (key.length !== 32) {
17+
throw new Error('키는 256비트(32바이트)여야 합니다.');
18+
}
19+
this.key = Buffer.from(key, 'utf-8');
20+
}
21+
22+
/**
23+
* 주어진 문자열을 AES-256-CBC 알고리즘으로 암호화합니다.
24+
* @param {string} plaintext - 암호화할 문자열
25+
* @returns {string} base64로 인코딩된 암호화 데이터
26+
*/
27+
encrypt(plaintext: string): string {
28+
const iv = crypto.randomBytes(16); // 16바이트 IV 생성
29+
const cipher = crypto.createCipheriv('aes-256-cbc', this.key, iv);
30+
31+
// PKCS7 패딩은 node 에서 자동 처리
32+
const encrypted = Buffer.concat([
33+
cipher.update(Buffer.from(plaintext, 'utf-8')),
34+
cipher.final(),
35+
]);
36+
37+
// IV와 암호화된 데이터를 결합하여 base64로 반환
38+
return Buffer.concat([iv, encrypted]).toString('base64');
39+
}
40+
41+
/**
42+
* AES-256-CBC 알고리즘으로 암호화된 데이터를 복호화합니다.
43+
* @param {string} encryptedData - base64로 인코딩된 암호화 데이터
44+
* @returns {string} 복호화된 문자열
45+
*/
46+
decrypt(encryptedData: string): string {
47+
const encryptedBuffer = Buffer.from(encryptedData, 'base64');
48+
const iv = encryptedBuffer.slice(0, 16); // 암호화 데이터에서 IV 추출
49+
const encryptedContent = encryptedBuffer.slice(16); // 암호화된 본문 추출
50+
51+
const decipher = crypto.createDecipheriv('aes-256-cbc', this.key, iv);
52+
53+
// 복호화된 데이터를 반환
54+
const decrypted = Buffer.concat([
55+
decipher.update(encryptedContent),
56+
decipher.final(),
57+
]);
58+
59+
return decrypted.toString('utf-8');
60+
}
61+
}
62+
63+
export default AESEncryption;

0 commit comments

Comments
 (0)