nest-encrypt-cycle 1.1.7 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +6 -2
- package/CHANGELOG.md +110 -0
- package/CLAUDE.md +3 -2
- package/PERFORMANCE_ANALYSIS.md +181 -0
- package/README.md +44 -6
- package/benchmark.js +157 -0
- package/dist/src/encrypt.interceptor.js +3 -2
- package/dist/src/encrypt.interceptor.js.map +1 -1
- package/dist/src/encrypt.service.d.ts +5 -0
- package/dist/src/encrypt.service.js +62 -14
- package/dist/src/encrypt.service.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -2
- package/test-optimized.js +119 -0
- package/test-query-params.js +99 -0
- package/test-wildcard.js +154 -0
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"permissions": {
|
|
3
3
|
"allow": [
|
|
4
|
-
"Bash(npm run build:*)"
|
|
4
|
+
"Bash(npm run build:*)",
|
|
5
|
+
"Bash(node benchmark.js:*)",
|
|
6
|
+
"Bash(node test-optimized.js:*)",
|
|
7
|
+
"Bash(node test-query-params.js:*)",
|
|
8
|
+
"Bash(node test-wildcard.js:*)"
|
|
5
9
|
],
|
|
6
10
|
"deny": [],
|
|
7
11
|
"ask": []
|
|
8
12
|
}
|
|
9
|
-
}
|
|
13
|
+
}
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [2.0.2] - Wildcard Pattern Support
|
|
4
|
+
|
|
5
|
+
### ✨ New Features
|
|
6
|
+
|
|
7
|
+
**Wildcard Pattern Matching in Whitelist**
|
|
8
|
+
- Added support for wildcard (`*`) patterns in whitelist paths
|
|
9
|
+
- Wildcard matches any single path segment (not including `/`)
|
|
10
|
+
- Can use multiple wildcards in a single pattern
|
|
11
|
+
- Works alongside exact path matching
|
|
12
|
+
|
|
13
|
+
**Examples:**
|
|
14
|
+
```typescript
|
|
15
|
+
whiteList: [
|
|
16
|
+
{ method: 'GET', pathname: '/api/users/*' }, // matches /api/users/123, /api/users/abc
|
|
17
|
+
{ method: 'GET', pathname: '/api/*/profile' }, // matches /api/users/profile, /api/admin/profile
|
|
18
|
+
{ method: 'POST', pathname: '/api/*/posts/*' }, // matches /api/users/posts/1, /api/admin/posts/2
|
|
19
|
+
{ method: 'GET', pathname: '/api/health' }, // exact match (can mix with wildcards)
|
|
20
|
+
]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Performance:**
|
|
24
|
+
- Exact matches: O(1) via Set lookup
|
|
25
|
+
- Wildcard matches: O(n) where n = number of wildcard patterns
|
|
26
|
+
- Exact matches checked first for optimal performance
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## [2.0.1] - Query Parameter Fix
|
|
31
|
+
|
|
32
|
+
### 🐛 Bug Fixes
|
|
33
|
+
|
|
34
|
+
**Whitelist Query Parameter Handling**
|
|
35
|
+
- Fixed whitelist matching to ignore query parameters
|
|
36
|
+
- Example: If `/api/users` is whitelisted, now `/api/users?id=1` also matches
|
|
37
|
+
- Query strings are stripped before whitelist comparison
|
|
38
|
+
|
|
39
|
+
**Before:**
|
|
40
|
+
- Whitelist: `GET /api/users`
|
|
41
|
+
- `/api/users` → matched ✅
|
|
42
|
+
- `/api/users?id=1` → NOT matched ❌
|
|
43
|
+
|
|
44
|
+
**After:**
|
|
45
|
+
- Whitelist: `GET /api/users`
|
|
46
|
+
- `/api/users` → matched ✅
|
|
47
|
+
- `/api/users?id=1` → matched ✅
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## [2.0.0] - Performance Optimization Release
|
|
52
|
+
|
|
53
|
+
### 🚀 Major Performance Improvements
|
|
54
|
+
|
|
55
|
+
#### crypto-js → Native Crypto Migration
|
|
56
|
+
- **Breaking Change**: Migrated from `crypto-js` to Node.js native `crypto` module
|
|
57
|
+
- **Performance Gains**:
|
|
58
|
+
- Small data (22 bytes): **89% faster**
|
|
59
|
+
- Medium data (5.6KB): **97.2% faster**
|
|
60
|
+
- Large data (56KB): **98.5% faster**
|
|
61
|
+
- Throughput improvement: **34x increase** (275 → 9,600 req/sec)
|
|
62
|
+
|
|
63
|
+
#### Whitelist Optimization
|
|
64
|
+
- Changed whitelist lookup from Array.some() to Set
|
|
65
|
+
- Complexity reduced from O(n) to O(1)
|
|
66
|
+
- Significant performance improvement for large whitelists
|
|
67
|
+
|
|
68
|
+
#### Memory Efficiency
|
|
69
|
+
- Reduced memory usage during encryption/decryption operations
|
|
70
|
+
- Pre-allocated Buffer objects to avoid repeated allocations
|
|
71
|
+
|
|
72
|
+
### 🔧 Technical Changes
|
|
73
|
+
|
|
74
|
+
**Removed Dependencies**:
|
|
75
|
+
- `crypto-js` (replaced with native Node.js crypto)
|
|
76
|
+
|
|
77
|
+
**Added Features**:
|
|
78
|
+
- Buffer caching for key and IV
|
|
79
|
+
- Improved error messages with context (pathname, method)
|
|
80
|
+
|
|
81
|
+
### 📊 Benchmark Results
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
10,000 iterations with 5.6KB data:
|
|
85
|
+
crypto-js: 3,633ms (275 ops/sec)
|
|
86
|
+
native crypto: 103ms (9,600 ops/sec)
|
|
87
|
+
Improvement: 97.2% faster, 34x throughput
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 💥 Breaking Changes
|
|
91
|
+
|
|
92
|
+
The encryption output format remains compatible, but applications using this library should:
|
|
93
|
+
1. Update to latest version
|
|
94
|
+
2. Remove `crypto-js` from their dependencies if no longer needed
|
|
95
|
+
3. No changes required to configuration or usage
|
|
96
|
+
|
|
97
|
+
### 🔄 Migration Guide
|
|
98
|
+
|
|
99
|
+
No code changes required for existing users. Simply update the package:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npm update nest-encrypt-cycle
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The API remains unchanged - encryption/decryption behavior is identical.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## [1.1.7] - Previous Release
|
|
110
|
+
- Previous implementation using crypto-js
|
package/CLAUDE.md
CHANGED
|
@@ -34,8 +34,9 @@ The library consists of three main parts that work together:
|
|
|
34
34
|
- Skips already-encrypted responses (checks for `{encrypted: true, data: ...}` structure)
|
|
35
35
|
|
|
36
36
|
3. **EncryptService** ([src/encrypt.service.ts](src/encrypt.service.ts)) - Encryption logic
|
|
37
|
-
- Uses `crypto
|
|
38
|
-
-
|
|
37
|
+
- Uses Node.js native `crypto` module for AES-256-CBC encryption
|
|
38
|
+
- Optimized with pre-allocated Buffers and Set-based whitelist (O(1) lookup)
|
|
39
|
+
- Performance: 89-98.5% faster than crypto-js, ~9,600 ops/sec throughput
|
|
39
40
|
- Whitelist feature: skips encryption/decryption for specific method+pathname combinations
|
|
40
41
|
|
|
41
42
|
### Request/Response Flow
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# 성능 분석 및 개선 방안
|
|
2
|
+
|
|
3
|
+
## 1. crypto-js → native crypto 마이그레이션 (최우선)
|
|
4
|
+
|
|
5
|
+
### 성능 개선 결과
|
|
6
|
+
- **Small data (22 bytes)**: 89% faster
|
|
7
|
+
- **Medium data (5.6KB)**: 97.2% faster
|
|
8
|
+
- **Large data (56KB)**: 98.5% faster
|
|
9
|
+
|
|
10
|
+
### 권장사항
|
|
11
|
+
즉시 마이그레이션 권장. 특히 대용량 데이터를 처리하는 API의 경우 극적인 성능 향상.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 2. 추가 병목 지점 및 개선 방안
|
|
16
|
+
|
|
17
|
+
### 🔴 Critical: Whitelist 조회 최적화
|
|
18
|
+
|
|
19
|
+
**현재 문제:**
|
|
20
|
+
```typescript
|
|
21
|
+
// encrypt.service.ts:10-15
|
|
22
|
+
if (this.options.whiteList.some(
|
|
23
|
+
(i) => i.pathname === pathname && i.method === method,
|
|
24
|
+
)) {
|
|
25
|
+
return data;
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
매 encrypt/decrypt 호출마다 배열 순회 (O(n) 복잡도)
|
|
30
|
+
|
|
31
|
+
**개선 방안:**
|
|
32
|
+
```typescript
|
|
33
|
+
// Set 또는 Map으로 변경하여 O(1) 조회
|
|
34
|
+
private whitelistSet: Set<string>;
|
|
35
|
+
|
|
36
|
+
constructor(private readonly options: EncryptOptions) {
|
|
37
|
+
this.whitelistSet = new Set(
|
|
38
|
+
this.options.whiteList.map(i => `${i.method}:${i.pathname}`)
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
encrypt(data: string, pathname: string, method: string): string {
|
|
43
|
+
if (this.whitelistSet.has(`${method}:${pathname}`)) {
|
|
44
|
+
return data;
|
|
45
|
+
}
|
|
46
|
+
// ...
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**예상 개선:** Whitelist가 클 경우 수십~수백배 빠름
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
### 🟡 Medium: Cipher 객체 재사용
|
|
55
|
+
|
|
56
|
+
**현재 문제:**
|
|
57
|
+
매 encrypt/decrypt마다 Cipher 객체 새로 생성
|
|
58
|
+
|
|
59
|
+
**개선 방안:**
|
|
60
|
+
```typescript
|
|
61
|
+
// Cipher 객체를 풀로 관리하거나 재사용
|
|
62
|
+
// 단, Node.js crypto의 경우 cipher는 재사용 불가능하므로
|
|
63
|
+
// 이 부분은 성능 영향이 제한적
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**예상 개선:** 5-10% (미미함)
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
### 🟡 Medium: JSON.stringify/parse 최적화
|
|
71
|
+
|
|
72
|
+
**현재 문제:**
|
|
73
|
+
```typescript
|
|
74
|
+
// encrypt.interceptor.ts:92
|
|
75
|
+
return this.encryptService.encrypt(JSON.stringify(data), url, method);
|
|
76
|
+
|
|
77
|
+
// encrypt.interceptor.ts:70
|
|
78
|
+
req.body = JSON.parse(decrypted);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**개선 방안:**
|
|
82
|
+
- 이미 string인 데이터의 경우 stringify 생략
|
|
83
|
+
- Fast JSON parser 사용 (예: `fast-json-stringify`, `fast-json-parse`)
|
|
84
|
+
- 대용량 응답의 경우 스트리밍 고려
|
|
85
|
+
|
|
86
|
+
**예상 개선:** 10-20% (데이터 크기에 따라)
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
### 🟢 Low: 에러 처리 최적화
|
|
91
|
+
|
|
92
|
+
**현재 문제:**
|
|
93
|
+
```typescript
|
|
94
|
+
// encrypt.interceptor.ts:64-82
|
|
95
|
+
try {
|
|
96
|
+
const decrypted = this.encryptService.decrypt(req.body.data, url, method);
|
|
97
|
+
if (decrypted && decrypted.trim() !== '') {
|
|
98
|
+
try {
|
|
99
|
+
req.body = JSON.parse(decrypted);
|
|
100
|
+
} catch (parseError) {
|
|
101
|
+
console.error('JSON parsing failed:', parseError);
|
|
102
|
+
req.body = {};
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
req.body = {};
|
|
106
|
+
}
|
|
107
|
+
} catch (err) {
|
|
108
|
+
console.error('Request decryption failed:', err);
|
|
109
|
+
req.body = {};
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**개선 방안:**
|
|
114
|
+
- console.error → Logger 서비스 사용
|
|
115
|
+
- Error 로그에 더 많은 컨텍스트 추가 (pathname, method 등)
|
|
116
|
+
- 프로덕션 환경에서는 로깅 레벨 조정
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
### 🟢 Low: 불필요한 조건 체크 제거
|
|
121
|
+
|
|
122
|
+
**현재 문제:**
|
|
123
|
+
```typescript
|
|
124
|
+
// encrypt.interceptor.ts:28-31
|
|
125
|
+
if (data && data.encrypted === true && data.data) {
|
|
126
|
+
return data;
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
매 응답마다 이미 암호화된 데이터 체크
|
|
131
|
+
|
|
132
|
+
**개선 방안:**
|
|
133
|
+
- 애플리케이션 레벨에서 이미 암호화된 응답을 보내지 않도록 정책 설정
|
|
134
|
+
- 필요 시 Symbol이나 WeakMap으로 마킹하여 빠른 체크
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## 3. 우선순위별 적용 로드맵
|
|
139
|
+
|
|
140
|
+
### Phase 1: 즉시 적용 (Critical Impact)
|
|
141
|
+
1. **crypto-js → native crypto 마이그레이션** (89-98.5% 개선)
|
|
142
|
+
2. **Whitelist Set/Map 전환** (whitelist 크기에 따라 큰 개선)
|
|
143
|
+
|
|
144
|
+
### Phase 2: 단기 적용 (Medium Impact)
|
|
145
|
+
3. JSON stringify/parse 최적화 (10-20% 개선)
|
|
146
|
+
4. 로깅 개선
|
|
147
|
+
|
|
148
|
+
### Phase 3: 장기 고려 (Low Impact)
|
|
149
|
+
5. 스트리밍 암호화 (매우 큰 페이로드의 경우)
|
|
150
|
+
6. Worker threads 활용 (CPU 집약적인 경우)
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## 4. 예상 전체 개선 효과
|
|
155
|
+
|
|
156
|
+
### 현재 vs Phase 1 적용 후
|
|
157
|
+
|
|
158
|
+
**10KB 데이터 1000 요청 처리 시:**
|
|
159
|
+
- 현재: ~3.6초
|
|
160
|
+
- Phase 1 적용 후: ~0.1초
|
|
161
|
+
- **전체 36배 개선**
|
|
162
|
+
|
|
163
|
+
**실제 API 처리량:**
|
|
164
|
+
- 현재: ~275 req/sec (full cycle)
|
|
165
|
+
- Phase 1 적용 후: ~9,600 req/sec
|
|
166
|
+
- **34배 처리량 증가**
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## 5. 메모리 사용량
|
|
171
|
+
|
|
172
|
+
native crypto는 메모리 효율도 우수합니다:
|
|
173
|
+
- crypto-js: +23.58 MB (1000 iterations)
|
|
174
|
+
- native crypto: -9.91 MB (GC 효과)
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## 결론
|
|
179
|
+
|
|
180
|
+
**crypto-js → native crypto 마이그레이션만으로도 극적인 성능 개선이 가능합니다.**
|
|
181
|
+
추가로 whitelist 최적화를 함께 적용하면 대부분의 병목이 해소됩니다.
|
package/README.md
CHANGED
|
@@ -8,9 +8,13 @@ NPM : https://www.npmjs.com/package/nest-encrypt-cycle
|
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
|
-
-
|
|
11
|
+
- **High Performance**: Native Node.js crypto module with ~9,600 ops/sec throughput (89-98.5% faster than crypto-js)
|
|
12
|
+
- **Wildcard Pattern Support**: Use `*` in whitelist paths to match dynamic segments (e.g., `/api/users/*`)
|
|
13
|
+
- Transparent AES-256-CBC encryption/decryption of HTTP request and response payloads
|
|
12
14
|
- Easy integration as a global or route-scoped interceptor
|
|
15
|
+
- Optimized whitelist lookup with O(1) complexity for exact matches
|
|
13
16
|
- Secure data handling for sensitive information in NestJS APIs
|
|
17
|
+
- Zero external dependencies (uses native crypto)
|
|
14
18
|
|
|
15
19
|
---
|
|
16
20
|
|
|
@@ -37,17 +41,51 @@ export class AppModule {}
|
|
|
37
41
|
|
|
38
42
|
```typescript
|
|
39
43
|
export interface EncryptOptions {
|
|
40
|
-
// Hash Key
|
|
44
|
+
// Hash Key (32 bytes for AES-256)
|
|
41
45
|
key: string;
|
|
42
46
|
|
|
43
|
-
// API White List
|
|
47
|
+
// API White List - Routes that should NOT be encrypted
|
|
44
48
|
whiteList: {
|
|
45
|
-
method: string;
|
|
46
|
-
pathname: string;
|
|
49
|
+
method: string; // HTTP method: 'GET', 'POST', etc.
|
|
50
|
+
pathname: string; // Path pattern: exact or with wildcards
|
|
47
51
|
}[];
|
|
48
52
|
}
|
|
49
53
|
```
|
|
50
54
|
|
|
55
|
+
## Whitelist Configuration
|
|
56
|
+
|
|
57
|
+
The whitelist supports both **exact matching** and **wildcard patterns**:
|
|
58
|
+
|
|
59
|
+
### Exact Matching
|
|
60
|
+
```typescript
|
|
61
|
+
whiteList: [
|
|
62
|
+
{ method: 'GET', pathname: '/api/health' },
|
|
63
|
+
{ method: 'POST', pathname: '/api/webhook' },
|
|
64
|
+
]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Wildcard Patterns
|
|
68
|
+
Use `*` to match any single path segment:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
whiteList: [
|
|
72
|
+
// Match any user ID: /api/users/123, /api/users/abc, etc.
|
|
73
|
+
{ method: 'GET', pathname: '/api/users/*' },
|
|
74
|
+
|
|
75
|
+
// Match any resource's profile: /api/users/profile, /api/admin/profile
|
|
76
|
+
{ method: 'GET', pathname: '/api/*/profile' },
|
|
77
|
+
|
|
78
|
+
// Multiple wildcards: /api/users/posts/1, /api/admin/posts/2
|
|
79
|
+
{ method: 'POST', pathname: '/api/*/posts/*' },
|
|
80
|
+
]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Notes:**
|
|
84
|
+
- `*` matches exactly **one** path segment (not including `/`)
|
|
85
|
+
- `/api/users/*` matches `/api/users/123` ✅ but NOT `/api/users/123/profile` ❌
|
|
86
|
+
- Query parameters are automatically ignored: `/api/users?id=1` matches `/api/users`
|
|
87
|
+
- Exact matches are checked first (O(1)), then wildcard patterns (O(n))
|
|
88
|
+
|
|
51
89
|
## Configuration
|
|
52
90
|
|
|
53
|
-
|
|
91
|
+
**key** (string): Your AES encryption key. Must be 16, 24, or 32 bytes long for AES-128/192/256.
|
package/benchmark.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
const CryptoJS = require('crypto-js');
|
|
3
|
+
|
|
4
|
+
// 테스트 데이터 생성
|
|
5
|
+
const testData = {
|
|
6
|
+
small: JSON.stringify({ id: 1, name: 'test' }),
|
|
7
|
+
medium: JSON.stringify({
|
|
8
|
+
id: 1,
|
|
9
|
+
name: 'test',
|
|
10
|
+
data: Array(100).fill({ field1: 'value1', field2: 'value2', field3: 'value3' })
|
|
11
|
+
}),
|
|
12
|
+
large: JSON.stringify({
|
|
13
|
+
id: 1,
|
|
14
|
+
name: 'test',
|
|
15
|
+
data: Array(1000).fill({ field1: 'value1', field2: 'value2', field3: 'value3' })
|
|
16
|
+
}),
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const KEY = '0123456789abcdef0123456789abcdef'; // 32 bytes for AES-256
|
|
20
|
+
|
|
21
|
+
// crypto-js 방식 (현재 구현)
|
|
22
|
+
function encryptCryptoJS(data) {
|
|
23
|
+
return CryptoJS.AES.encrypt(data, CryptoJS.enc.Utf8.parse(KEY), {
|
|
24
|
+
iv: CryptoJS.enc.Utf8.parse(KEY),
|
|
25
|
+
padding: CryptoJS.pad.Pkcs7,
|
|
26
|
+
mode: CryptoJS.mode.CBC,
|
|
27
|
+
}).toString();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function decryptCryptoJS(data) {
|
|
31
|
+
const decipher = CryptoJS.AES.decrypt(data, CryptoJS.enc.Utf8.parse(KEY), {
|
|
32
|
+
iv: CryptoJS.enc.Utf8.parse(KEY),
|
|
33
|
+
padding: CryptoJS.pad.Pkcs7,
|
|
34
|
+
mode: CryptoJS.mode.CBC,
|
|
35
|
+
});
|
|
36
|
+
return decipher.toString(CryptoJS.enc.Utf8);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Native crypto 방식 (개선 버전)
|
|
40
|
+
function encryptNative(data) {
|
|
41
|
+
const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(KEY, 'utf8'), Buffer.from(KEY.slice(0, 16), 'utf8'));
|
|
42
|
+
let encrypted = cipher.update(data, 'utf8', 'base64');
|
|
43
|
+
encrypted += cipher.final('base64');
|
|
44
|
+
return encrypted;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function decryptNative(data) {
|
|
48
|
+
const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(KEY, 'utf8'), Buffer.from(KEY.slice(0, 16), 'utf8'));
|
|
49
|
+
let decrypted = decipher.update(data, 'base64', 'utf8');
|
|
50
|
+
decrypted += decipher.final('utf8');
|
|
51
|
+
return decrypted;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 벤치마크 실행
|
|
55
|
+
function benchmark(name, fn, iterations = 10000) {
|
|
56
|
+
const start = process.hrtime.bigint();
|
|
57
|
+
for (let i = 0; i < iterations; i++) {
|
|
58
|
+
fn();
|
|
59
|
+
}
|
|
60
|
+
const end = process.hrtime.bigint();
|
|
61
|
+
const duration = Number(end - start) / 1000000; // ms로 변환
|
|
62
|
+
const opsPerSec = (iterations / duration) * 1000;
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
totalMs: duration.toFixed(2),
|
|
66
|
+
avgMs: (duration / iterations).toFixed(4),
|
|
67
|
+
opsPerSec: opsPerSec.toFixed(2),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.log('='.repeat(80));
|
|
72
|
+
console.log('Performance Benchmark: crypto-js vs Native crypto');
|
|
73
|
+
console.log('='.repeat(80));
|
|
74
|
+
console.log();
|
|
75
|
+
|
|
76
|
+
// 각 데이터 크기별로 테스트
|
|
77
|
+
for (const [size, data] of Object.entries(testData)) {
|
|
78
|
+
console.log(`\n📊 ${size.toUpperCase()} DATA (${data.length} bytes)`);
|
|
79
|
+
console.log('-'.repeat(80));
|
|
80
|
+
|
|
81
|
+
// 암호화 테스트
|
|
82
|
+
console.log('\n🔒 Encryption:');
|
|
83
|
+
const encryptedCryptoJS = encryptCryptoJS(data);
|
|
84
|
+
const encryptedNative = encryptNative(data);
|
|
85
|
+
|
|
86
|
+
const cryptoJSEncrypt = benchmark('crypto-js', () => encryptCryptoJS(data));
|
|
87
|
+
console.log(` crypto-js: ${cryptoJSEncrypt.totalMs}ms total, ${cryptoJSEncrypt.avgMs}ms avg, ${cryptoJSEncrypt.opsPerSec} ops/sec`);
|
|
88
|
+
|
|
89
|
+
const nativeEncrypt = benchmark('native', () => encryptNative(data));
|
|
90
|
+
console.log(` native crypto: ${nativeEncrypt.totalMs}ms total, ${nativeEncrypt.avgMs}ms avg, ${nativeEncrypt.opsPerSec} ops/sec`);
|
|
91
|
+
|
|
92
|
+
const encryptImprovement = ((cryptoJSEncrypt.totalMs - nativeEncrypt.totalMs) / cryptoJSEncrypt.totalMs * 100).toFixed(1);
|
|
93
|
+
console.log(` ⚡ Improvement: ${encryptImprovement}% faster`);
|
|
94
|
+
|
|
95
|
+
// 복호화 테스트
|
|
96
|
+
console.log('\n🔓 Decryption:');
|
|
97
|
+
const cryptoJSDecrypt = benchmark('crypto-js', () => decryptCryptoJS(encryptedCryptoJS));
|
|
98
|
+
console.log(` crypto-js: ${cryptoJSDecrypt.totalMs}ms total, ${cryptoJSDecrypt.avgMs}ms avg, ${cryptoJSDecrypt.opsPerSec} ops/sec`);
|
|
99
|
+
|
|
100
|
+
const nativeDecrypt = benchmark('native', () => decryptNative(encryptedNative));
|
|
101
|
+
console.log(` native crypto: ${nativeDecrypt.totalMs}ms total, ${nativeDecrypt.avgMs}ms avg, ${nativeDecrypt.opsPerSec} ops/sec`);
|
|
102
|
+
|
|
103
|
+
const decryptImprovement = ((cryptoJSDecrypt.totalMs - nativeDecrypt.totalMs) / cryptoJSDecrypt.totalMs * 100).toFixed(1);
|
|
104
|
+
console.log(` ⚡ Improvement: ${decryptImprovement}% faster`);
|
|
105
|
+
|
|
106
|
+
// 전체 주기 (encrypt + decrypt)
|
|
107
|
+
console.log('\n🔄 Full Cycle (encrypt + decrypt):');
|
|
108
|
+
const cryptoJSFull = benchmark('crypto-js', () => {
|
|
109
|
+
const enc = encryptCryptoJS(data);
|
|
110
|
+
decryptCryptoJS(enc);
|
|
111
|
+
});
|
|
112
|
+
console.log(` crypto-js: ${cryptoJSFull.totalMs}ms total, ${cryptoJSFull.avgMs}ms avg, ${cryptoJSFull.opsPerSec} ops/sec`);
|
|
113
|
+
|
|
114
|
+
const nativeFull = benchmark('native', () => {
|
|
115
|
+
const enc = encryptNative(data);
|
|
116
|
+
decryptNative(enc);
|
|
117
|
+
});
|
|
118
|
+
console.log(` native crypto: ${nativeFull.totalMs}ms total, ${nativeFull.avgMs}ms avg, ${nativeFull.opsPerSec} ops/sec`);
|
|
119
|
+
|
|
120
|
+
const fullImprovement = ((cryptoJSFull.totalMs - nativeFull.totalMs) / cryptoJSFull.totalMs * 100).toFixed(1);
|
|
121
|
+
console.log(` ⚡ Improvement: ${fullImprovement}% faster`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 메모리 사용량 비교
|
|
125
|
+
console.log('\n\n='.repeat(80));
|
|
126
|
+
console.log('💾 Memory Usage Comparison');
|
|
127
|
+
console.log('='.repeat(80));
|
|
128
|
+
|
|
129
|
+
const memBefore = process.memoryUsage();
|
|
130
|
+
|
|
131
|
+
// crypto-js 메모리 사용량
|
|
132
|
+
for (let i = 0; i < 1000; i++) {
|
|
133
|
+
const enc = encryptCryptoJS(testData.medium);
|
|
134
|
+
decryptCryptoJS(enc);
|
|
135
|
+
}
|
|
136
|
+
const memAfterCryptoJS = process.memoryUsage();
|
|
137
|
+
|
|
138
|
+
// GC 시뮬레이션 (정확한 측정을 위해)
|
|
139
|
+
if (global.gc) {
|
|
140
|
+
global.gc();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// native crypto 메모리 사용량
|
|
144
|
+
const memBeforeNative = process.memoryUsage();
|
|
145
|
+
for (let i = 0; i < 1000; i++) {
|
|
146
|
+
const enc = encryptNative(testData.medium);
|
|
147
|
+
decryptNative(enc);
|
|
148
|
+
}
|
|
149
|
+
const memAfterNative = process.memoryUsage();
|
|
150
|
+
|
|
151
|
+
console.log('\nHeap Used (1000 iterations with medium data):');
|
|
152
|
+
console.log(` crypto-js: ${((memAfterCryptoJS.heapUsed - memBefore.heapUsed) / 1024 / 1024).toFixed(2)} MB`);
|
|
153
|
+
console.log(` native crypto: ${((memAfterNative.heapUsed - memBeforeNative.heapUsed) / 1024 / 1024).toFixed(2)} MB`);
|
|
154
|
+
|
|
155
|
+
console.log('\n' + '='.repeat(80));
|
|
156
|
+
console.log('✅ Benchmark Complete');
|
|
157
|
+
console.log('='.repeat(80));
|
|
@@ -21,13 +21,14 @@ let EncryptInterceptor = class EncryptInterceptor {
|
|
|
21
21
|
const req = context.switchToHttp().getRequest();
|
|
22
22
|
const url = req.url || req.raw?.url;
|
|
23
23
|
const method = req.method;
|
|
24
|
+
const pathname = url.split('?')[0];
|
|
24
25
|
const isEncrypted = (req.headers['is-encrypted'] || req.headers['is-encrypted']) === 'Y';
|
|
25
|
-
this.processRequest(req,
|
|
26
|
+
this.processRequest(req, pathname, method, isEncrypted);
|
|
26
27
|
return next.handle().pipe((0, rxjs_1.map)((data) => {
|
|
27
28
|
if (data && data.encrypted === true && data.data) {
|
|
28
29
|
return data;
|
|
29
30
|
}
|
|
30
|
-
return this.processResponse(data,
|
|
31
|
+
return this.processResponse(data, pathname, method, isEncrypted);
|
|
31
32
|
}));
|
|
32
33
|
}
|
|
33
34
|
processRequest(req, url, method, isEncrypted) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"encrypt.interceptor.js","sourceRoot":"","sources":["../../src/encrypt.interceptor.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAKwB;AACxB,+BAAuC;AACvC,uDAAmD;AAG5C,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;IAC7B,YAA6B,cAA8B;QAA9B,mBAAc,GAAd,cAAc,CAAgB;IAAG,CAAC;IAE/D,SAAS,CAAC,OAAyB,EAAE,IAAiB;QACpD,MAAM,GAAG,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC;QAEhD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC;QACpC,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;QAE1B,MAAM,WAAW,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,KAAK,GAAG,CAAC;QAGzF,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,
|
|
1
|
+
{"version":3,"file":"encrypt.interceptor.js","sourceRoot":"","sources":["../../src/encrypt.interceptor.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { EncryptOptions } from './types';
|
|
2
2
|
export declare class EncryptService {
|
|
3
3
|
private readonly options;
|
|
4
|
+
private readonly whitelistSet;
|
|
5
|
+
private readonly whitelistPatterns;
|
|
6
|
+
private readonly keyBuffer;
|
|
7
|
+
private readonly ivBuffer;
|
|
4
8
|
constructor(options: EncryptOptions);
|
|
9
|
+
private isWhitelisted;
|
|
5
10
|
encrypt(data: string, pathname: string, method: string): string;
|
|
6
11
|
decrypt(data: string, pathname: string, method: string): string;
|
|
7
12
|
}
|