@futdevpro/fsm-dynamo 1.10.50 → 1.10.51

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.
@@ -5,94 +5,36 @@ import {
5
5
  } from '../../../_models/control-models/error.control-model';
6
6
 
7
7
 
8
- /**
9
- * Error codes for crypto operations
10
- */
11
- /* export enum CryptoErrorCode {
12
- INVALID_INPUT = 'DyFM-CRY-EA0',
13
- DECRYPTION_FAILED = '',
14
- ENCRYPTION_FAILED = 'DyFM-CRY-ENF',
15
- INVALID_KEY = 'DyFM-CRY-IKY',
16
- INVALID_DATA = 'DyFM-CRY-IDT'
17
- } */
18
-
19
- /**
20
- * Configuration options for encryption/decryption
21
- */
22
- export interface CryptoConfig {
23
- ivLength?: number;
24
- saltLength?: number;
25
- keyIterations?: number;
26
- keySize?: number;
27
- }
28
-
29
8
  /**
30
9
  * A utility class for secure encryption and decryption of data
31
- * Uses AES-256-CBC with PBKDF2 key derivation
10
+ * Uses AES-256-CBC with deterministic IV and salt for consistent results
32
11
  */
33
12
  export class DyFM_Crypto {
34
- private static readonly DEFAULT_CONFIG: Required<CryptoConfig> = {
35
- ivLength: 16, // 128 bits
36
- saltLength: 16, // 128 bits
37
- keyIterations: 10000,
38
- keySize: 8 // 256 bits (8 * 32)
39
- };
40
13
  private static readonly defaultErrorUserMsg =
41
14
  `We encountered an unhandled Authentication Error, ` +
42
15
  `\nplease contact the responsible development team.`;
43
16
 
44
- // Tömör: kb. 60–80 karakteres token, nem 200+
45
- // Nem szabványos: nehéz visszafejteni
46
- // Használható cookie, header, URL-ben
47
-
48
17
  /**
49
- * Validates the input data and key
50
- * @throws {DyFM_Error} if validation fails
18
+ * Validates the encryption key
51
19
  */
52
- private static validateInput(data: any, key: string): void {
53
- if (!key || typeof key !== 'string' || key.trim().length === 0) {
20
+ private static validateKey(key: string): void {
21
+ if (!key || typeof key !== 'string') {
54
22
  throw new DyFM_Error({
55
- ...this.getDefaultErrorSettings('validateInput'),
56
- errorCode: 'DyFM-CRY-IKY',
57
- message: 'Invalid encryption key'
23
+ status: 401,
24
+ message: 'Encryption key is required and must be a string',
25
+ errorCode: 'DyFM-CRY-KEY-REQ'
58
26
  });
59
27
  }
60
-
61
- if (data === undefined || data === null) {
28
+
29
+ if (key.trim().length === 0) {
62
30
  throw new DyFM_Error({
63
- ...this.getDefaultErrorSettings('validateInput'),
64
- errorCode: 'DyFM-CRY-IDT',
65
- message: 'Invalid data to encrypt/decrypt'
31
+ status: 401,
32
+ message: 'Encryption key cannot be empty or whitespace-only',
33
+ errorCode: 'DyFM-CRY-KEY-EMPTY'
66
34
  });
67
35
  }
68
36
  }
69
37
 
70
- /**
71
- * Generates a deterministic IV based on the input data and key
72
- */
73
- private static generateIV(data: string, key: string, config: Required<CryptoConfig>): CryptoJS.lib.WordArray {
74
- const hash = CryptoJS.SHA256(data + key);
75
- return CryptoJS.lib.WordArray.create(hash.words.slice(0, config.ivLength / 4));
76
- }
77
-
78
- /**
79
- * Generates a deterministic salt based on the input data and key
80
- */
81
- private static generateSalt(data: string, key: string, config: Required<CryptoConfig>): CryptoJS.lib.WordArray {
82
- const hash = CryptoJS.SHA256(key + data);
83
- return CryptoJS.lib.WordArray.create(hash.words.slice(0, config.saltLength / 4));
84
- }
85
-
86
- /**
87
- * Derives a key using PBKDF2
88
- */
89
- private static deriveKey(key: string, salt: CryptoJS.lib.WordArray, config: Required<CryptoConfig>): CryptoJS.lib.WordArray {
90
- return CryptoJS.PBKDF2(key, salt, {
91
- keySize: config.keySize,
92
- iterations: config.keyIterations
93
- });
94
- }
95
-
96
38
  /**
97
39
  * Safely serializes data to JSON
98
40
  */
@@ -141,40 +83,58 @@ export class DyFM_Crypto {
141
83
  }
142
84
 
143
85
  /**
144
- * Encrypts data using AES-256-CBC
86
+ * Derives a 256-bit key from the passphrase using SHA256
87
+ */
88
+ private static deriveKey(key: string): CryptoJS.lib.WordArray {
89
+ return CryptoJS.SHA256(key);
90
+ }
91
+
92
+ /**
93
+ * Creates a deterministic IV from the key only
94
+ */
95
+ private static createDeterministicIV(key: string): CryptoJS.lib.WordArray {
96
+ const hash = CryptoJS.SHA256(key + ':iv');
97
+ return CryptoJS.lib.WordArray.create(hash.words.slice(0, 4)); // Use first 16 bytes (128 bits)
98
+ }
99
+
100
+ /**
101
+ * Encrypts data using AES-256-CBC with deterministic key and IV
145
102
  * @param data The data to encrypt
146
103
  * @param key The encryption key
147
- * @param config Optional configuration
148
104
  * @returns URL-safe encrypted string
149
105
  * @throws {DyFM_Error} if encryption fails
150
106
  */
151
- static encrypt<T>(data: T, key: string, config?: CryptoConfig): string {
107
+ static encrypt<T>(data: T, key: string): string {
152
108
  try {
153
- this.validateInput(data, key);
154
- const finalConfig = { ...this.DEFAULT_CONFIG, ...config };
109
+ // Validate key
110
+ this.validateKey(key);
111
+
112
+ // Validate data
113
+ if (data === undefined || data === null) {
114
+ throw new DyFM_Error({
115
+ status: 401,
116
+ message: 'Data cannot be undefined or null',
117
+ errorCode: 'DyFM-CRY-DATA-NULL'
118
+ });
119
+ }
155
120
 
156
121
  // Convert data to string
157
122
  const dataStr = this.safeSerialize(data);
158
123
 
159
- // Generate deterministic IV and salt based on data and key
160
- const iv = this.generateIV(dataStr, key, finalConfig);
161
- const salt = this.generateSalt(dataStr, key, finalConfig);
162
-
163
- // Derive key using PBKDF2
164
- const derivedKey = this.deriveKey(key, salt, finalConfig);
124
+ // Derive key and IV
125
+ const aesKey = this.deriveKey(key);
126
+ const iv = this.createDeterministicIV(key);
165
127
 
166
- // Encrypt the data
167
- const encrypted = CryptoJS.AES.encrypt(dataStr, derivedKey, {
128
+ // Encrypt the data with deterministic key and IV
129
+ const encrypted = CryptoJS.AES.encrypt(dataStr, aesKey, {
168
130
  iv: iv,
169
131
  mode: CryptoJS.mode.CBC,
170
132
  padding: CryptoJS.pad.Pkcs7
171
133
  });
172
134
 
173
- // Combine IV + Salt + Ciphertext
174
- const combined = iv.concat(salt).concat(encrypted.ciphertext);
175
-
176
135
  // Convert to URL-safe base64
177
- return CryptoJS.enc.Base64.stringify(combined)
136
+ const base64 = encrypted.ciphertext.toString(CryptoJS.enc.Base64);
137
+ return base64
178
138
  .replace(/\+/g, '-')
179
139
  .replace(/\//g, '_')
180
140
  .replace(/=+$/, '');
@@ -190,57 +150,57 @@ export class DyFM_Crypto {
190
150
  * Decrypts data that was encrypted using encrypt()
191
151
  * @param encryptedData The encrypted data
192
152
  * @param key The decryption key
193
- * @param config Optional configuration
194
153
  * @returns The decrypted data
195
154
  * @throws {DyFM_Error} if decryption fails
196
155
  */
197
- static decrypt<T>(encryptedData: string, key: string, config?: CryptoConfig): T {
156
+ static decrypt<T>(encryptedData: string, key: string): T {
198
157
  try {
199
- this.validateInput(encryptedData, key);
200
- const finalConfig = { ...this.DEFAULT_CONFIG, ...config };
158
+ // Validate key
159
+ this.validateKey(key);
160
+
161
+ // Validate encrypted data
162
+ if (!encryptedData || typeof encryptedData !== 'string') {
163
+ throw new DyFM_Error({
164
+ status: 401,
165
+ message: 'Encrypted data is required and must be a string',
166
+ errorCode: 'DyFM-CRY-DATA-REQ'
167
+ });
168
+ }
201
169
 
202
170
  // Convert from URL-safe base64
203
- const base64 = encryptedData
171
+ let base64 = encryptedData
204
172
  .replace(/-/g, '+')
205
173
  .replace(/_/g, '/');
206
174
 
207
- // Parse the combined data
208
- const combined = CryptoJS.enc.Base64.parse(base64);
209
-
210
- // Validate minimum length (IV + Salt + minimum ciphertext)
211
- const minLength = (finalConfig.ivLength + finalConfig.saltLength + 16) / 4; // 16 bytes minimum for ciphertext
212
- if (combined.words.length < minLength) {
213
- throw new Error('Invalid encrypted data length');
175
+ // Add padding if needed
176
+ const padLength = 4 - (base64.length % 4);
177
+ if (padLength < 4) {
178
+ base64 += '='.repeat(padLength);
214
179
  }
215
180
 
216
- // Extract IV, salt, and ciphertext
217
- const iv = CryptoJS.lib.WordArray.create(combined.words.slice(0, finalConfig.ivLength / 4));
218
- const salt = CryptoJS.lib.WordArray.create(
219
- combined.words.slice(
220
- finalConfig.ivLength / 4,
221
- (finalConfig.ivLength + finalConfig.saltLength) / 4
222
- )
223
- );
224
- const ciphertext = CryptoJS.lib.WordArray.create(
225
- combined.words.slice((finalConfig.ivLength + finalConfig.saltLength) / 4)
226
- );
227
-
228
- // Derive key using PBKDF2
229
- const derivedKey = this.deriveKey(key, salt, finalConfig);
181
+ // Derive key and IV
182
+ const aesKey = this.deriveKey(key);
183
+ const iv = this.createDeterministicIV(key);
230
184
 
231
185
  // Decrypt the data
232
- const decrypted = CryptoJS.AES.decrypt(
233
- { ciphertext: ciphertext },
234
- derivedKey,
235
- {
236
- iv: iv,
237
- mode: CryptoJS.mode.CBC,
238
- padding: CryptoJS.pad.Pkcs7
239
- }
240
- );
186
+ const encryptedWA = CryptoJS.enc.Base64.parse(base64);
187
+ const decrypted = CryptoJS.AES.decrypt({ ciphertext: encryptedWA }, aesKey, {
188
+ iv: iv,
189
+ mode: CryptoJS.mode.CBC,
190
+ padding: CryptoJS.pad.Pkcs7
191
+ });
241
192
 
242
193
  // Parse JSON
243
194
  const decryptedStr = decrypted.toString(CryptoJS.enc.Utf8);
195
+
196
+ if (!decryptedStr) {
197
+ throw new DyFM_Error({
198
+ status: 401,
199
+ message: 'Failed to decrypt data - invalid key or corrupted data',
200
+ errorCode: 'DyFM-CRY-DEC-FAIL'
201
+ });
202
+ }
203
+
244
204
  return this.safeDeserialize<T>(decryptedStr);
245
205
  } catch (error) {
246
206
  throw new DyFM_Error({
@@ -250,27 +210,6 @@ export class DyFM_Crypto {
250
210
  }
251
211
  }
252
212
 
253
- /**
254
- * Generates a secure random key
255
- * @param length Length of the key in bytes (default: 32)
256
- * @returns A secure random key
257
- */
258
- static generateKey(length: number = 32): string {
259
- return CryptoJS.lib.WordArray.random(length).toString();
260
- }
261
-
262
- /**
263
- * Validates if a string is a valid encrypted data
264
- * @param encryptedData The data to validate
265
- * @returns true if the data appears to be valid encrypted data
266
- */
267
- static isValidEncryptedData(encryptedData: string): boolean {
268
- if (!encryptedData || typeof encryptedData !== 'string') {
269
- return false;
270
- }
271
- return /^[A-Za-z0-9\-_]+$/.test(encryptedData);
272
- }
273
-
274
213
  /**
275
214
  * Gets default error settings
276
215
  */
Binary file