@crossdyne/security 0.5.0-beta.2 → 1.0.0
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/README.md +358 -8
- package/dist/index.cjs +57 -100
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -41
- package/dist/index.d.ts +37 -41
- package/dist/index.js +57 -100
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,15 +1,365 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Crossdyne.Security
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Cross-platform cryptographic library — [.NET](https://github.com/crossdyne/dotnet-security) | [TypeScript](https://github.com/crossdyne/typescript-security)
|
|
4
|
+
>
|
|
5
|
+
> [English](#english) | [Русский](#русский)
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
---
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
<a name="english"></a>
|
|
10
|
+
## English
|
|
11
|
+
|
|
12
|
+
TypeScript / JavaScript implementation of the Crossdyne.Security cryptographic library via the native **Web Crypto API**.
|
|
10
13
|
|
|
11
14
|
## Installation
|
|
12
15
|
|
|
13
16
|
```bash
|
|
14
|
-
npm install @
|
|
15
|
-
```
|
|
17
|
+
npm install @crossdyne/security
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
- **AES-256-GCM** — authenticated symmetric encryption via `crypto.subtle`
|
|
23
|
+
- **PBKDF2 + HKDF** — secure key derivation from passwords
|
|
24
|
+
- **SRP-6a** — password-authenticated key exchange without sending the password to the server
|
|
25
|
+
- **Zero-memory** — sensitive buffers are explicitly overwritten after use
|
|
26
|
+
- **No custom crypto primitives** — relies entirely on the browser / Node.js Web Crypto API
|
|
27
|
+
- **Cross-platform** — compatible with the [.NET implementation](https://github.com/crossdyne/dotnet-security). Encrypted payloads and SRP messages are interchangeable between TS and .NET.
|
|
28
|
+
|
|
29
|
+
### Requirements
|
|
30
|
+
|
|
31
|
+
- TypeScript 5.3+
|
|
32
|
+
- Web Crypto API (`crypto.subtle`) — modern browsers and Node.js 18+
|
|
33
|
+
|
|
34
|
+
### Project Structure
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
crossdyne-security/
|
|
38
|
+
├── crypto/
|
|
39
|
+
│ ├── crypto-service.ts # AES-GCM encrypt / decrypt
|
|
40
|
+
│ ├── key-derivation-service.ts # Derive KEK and AuthHash (PBKDF2 → HKDF)
|
|
41
|
+
│ ├── crypto-profile-registry.ts # Versioned crypto profiles
|
|
42
|
+
│ ├── crypto-version.ts # Enum: V1, V2, ...
|
|
43
|
+
│ └── kdf-options.ts # PBKDF2 / HKDF parameters
|
|
44
|
+
├── srp/
|
|
45
|
+
│ ├── srp-server-service.ts # SRP server (challenge, verify)
|
|
46
|
+
│ ├── srp-client-service.ts # SRP client (proof, verifier)
|
|
47
|
+
│ ├── srp-key-derivation-service.ts # Derive auth-hash for SRP
|
|
48
|
+
│ ├── srp-context-factory.ts # Creates SRP context (N, g, k, hash)
|
|
49
|
+
│ ├── srp-group.ts # Enum: Group2048, Group4096, ...
|
|
50
|
+
│ └── srp-encoding.ts # BigInt ↔ bytes helpers
|
|
51
|
+
└── utils/
|
|
52
|
+
├── security-utils.ts # Base64, BigInt, fixed-time compare
|
|
53
|
+
└── srp-encoding.ts # Hash moduli, session key, M1 / M2
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Quick Start
|
|
57
|
+
|
|
58
|
+
#### Encrypt / Decrypt
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { CryptoService } from './crypto/crypto-service.js';
|
|
62
|
+
import { CryptoVersion } from './crypto/crypto-version.js';
|
|
63
|
+
|
|
64
|
+
const crypto = new CryptoService();
|
|
65
|
+
const key = crypto.generateRandomBytes(32); // AES-256
|
|
66
|
+
|
|
67
|
+
const encrypted = await crypto.encryptData(
|
|
68
|
+
{ message: "Hello, World!" },
|
|
69
|
+
key,
|
|
70
|
+
CryptoVersion.V1
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const decrypted = await crypto.decryptData<MyData>(encrypted, key);
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Encrypted payload format (Base64):
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
[Version (1 byte)][Nonce (N bytes)][Ciphertext + Tag]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
#### Key Derivation from Password
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { KeyDerivationService } from './crypto/key-derivation-service.js';
|
|
86
|
+
import { CryptoVersion } from './crypto/crypto-version.js';
|
|
87
|
+
|
|
88
|
+
const kdf = new KeyDerivationService();
|
|
89
|
+
const salt = crypto.getRandomValues(new Uint8Array(16));
|
|
90
|
+
|
|
91
|
+
const { kek, authHash } = await kdf.deriveKeysFromPassword(
|
|
92
|
+
"user@example.com",
|
|
93
|
+
"SuperSecret123!",
|
|
94
|
+
salt,
|
|
95
|
+
CryptoVersion.V1
|
|
96
|
+
);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
- `kek` — `Uint8Array` key for AES-GCM
|
|
100
|
+
- `authHash` — Base64 string for SRP authentication
|
|
101
|
+
|
|
102
|
+
#### SRP Authentication Flow
|
|
103
|
+
|
|
104
|
+
**Server — generate challenge**
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
import { SrpServerService } from './srp/srp-server-service.js';
|
|
108
|
+
import { SrpGroup } from './srp/srp-group.js';
|
|
109
|
+
|
|
110
|
+
const server = new SrpServerService();
|
|
111
|
+
|
|
112
|
+
const session = await server.getSrpChallenge(
|
|
113
|
+
"user@example.com",
|
|
114
|
+
verifierBytes,
|
|
115
|
+
salt,
|
|
116
|
+
SrpGroup.Group2048
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// Send to client: salt + session.publicKeyB (B)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Client — generate proof**
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { SrpClientService } from './srp/srp-client-service.js';
|
|
126
|
+
import { SrpKeyDerivationService } from './srp/srp-key-derivation-service.js';
|
|
127
|
+
import { SrpGroup } from './srp/srp-group.js';
|
|
128
|
+
import { CryptoVersion } from './crypto/crypto-version.js';
|
|
129
|
+
|
|
130
|
+
const client = new SrpClientService();
|
|
131
|
+
const kdf = new SrpKeyDerivationService();
|
|
132
|
+
|
|
133
|
+
// 1. Derive auth-hash from password
|
|
134
|
+
const authHash = await kdf.deriveAuthHashForSrp(
|
|
135
|
+
identity, password, salt, SrpGroup.Group2048, CryptoVersion.V1
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
// 2. Generate proof
|
|
139
|
+
const { A, M1, SessionKeyK } = await client.generateSrpProof(
|
|
140
|
+
identity,
|
|
141
|
+
authHash,
|
|
142
|
+
btoa(String.fromCharCode(...salt)),
|
|
143
|
+
btoa(String.fromCharCode(...session.publicKeyB)),
|
|
144
|
+
SrpGroup.Group2048
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
// Send to server: A + M1
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Server — verify proof**
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
const serverM2 = await server.verifySrpProof(
|
|
154
|
+
session,
|
|
155
|
+
A,
|
|
156
|
+
M1,
|
|
157
|
+
SrpGroup.Group2048
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
// Send to client: serverM2
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Client — verify server proof**
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
const isValid = await client.verifyServerM2(
|
|
167
|
+
A,
|
|
168
|
+
M1,
|
|
169
|
+
SessionKeyK,
|
|
170
|
+
serverM2,
|
|
171
|
+
SrpGroup.Group2048
|
|
172
|
+
);
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Security Notes
|
|
176
|
+
|
|
177
|
+
- All sensitive buffers are explicitly overwritten after use
|
|
178
|
+
- Salt must be **at least 16 bytes**
|
|
179
|
+
- AES-256 key must be **exactly 32 bytes**
|
|
180
|
+
- SRP groups are protected against small-subgroup attacks (checks `A % N != 0`, `B != 0`)
|
|
181
|
+
- Relies entirely on the native Web Crypto API — no custom crypto primitives
|
|
182
|
+
|
|
183
|
+
### License
|
|
184
|
+
|
|
185
|
+
MIT
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
<a name="русский"></a>
|
|
190
|
+
## Русский
|
|
191
|
+
|
|
192
|
+
Реализация криптографической библиотеки Crossdyne.Security для TypeScript / JavaScript через нативный **Web Crypto API**.
|
|
193
|
+
|
|
194
|
+
## Установка
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
npm install @crossdyne/security
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Возможности
|
|
201
|
+
|
|
202
|
+
- **AES-256-GCM** — симметричное шифрование с аутентификацией через `crypto.subtle`
|
|
203
|
+
- **PBKDF2 + HKDF** — надёжный вывод ключей из пароля
|
|
204
|
+
- **SRP-6a** — протокол аутентификации без передачи пароля на сервер
|
|
205
|
+
- **Zero-memory** — чувствительные буферы явно перезаписываются после использования
|
|
206
|
+
- **Никаких самописных криптопримитивов** — только нативный Web Crypto API браузера / Node.js
|
|
207
|
+
- **Кроссплатформенность** — совместима с [.NET-реализацией](https://github.com/crossdyne/dotnet-security). Зашифрованные данные и SRP-сообщения взаимозаменяемы между TS и .NET.
|
|
208
|
+
|
|
209
|
+
### Требования
|
|
210
|
+
|
|
211
|
+
- TypeScript 5.3+
|
|
212
|
+
- Web Crypto API (`crypto.subtle`) — современные браузеры и Node.js 18+
|
|
213
|
+
|
|
214
|
+
### Структура проекта
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
crossdyne-security/
|
|
218
|
+
├── crypto/
|
|
219
|
+
│ ├── crypto-service.ts # AES-GCM шифрование / дешифрование
|
|
220
|
+
│ ├── key-derivation-service.ts # Вывод KEK и AuthHash (PBKDF2 → HKDF)
|
|
221
|
+
│ ├── crypto-profile-registry.ts # Версионированные криптопрофили
|
|
222
|
+
│ ├── crypto-version.ts # Enum: V1, V2, ...
|
|
223
|
+
│ └── kdf-options.ts # Параметры PBKDF2 / HKDF
|
|
224
|
+
├── srp/
|
|
225
|
+
│ ├── srp-server-service.ts # SRP сервер (challenge, проверка)
|
|
226
|
+
│ ├── srp-client-service.ts # SRP клиент (proof, верификатор)
|
|
227
|
+
│ ├── srp-key-derivation-service.ts # Вывод auth-hash для SRP
|
|
228
|
+
│ ├── srp-context-factory.ts # Создание SRP-контекста (N, g, k, hash)
|
|
229
|
+
│ ├── srp-group.ts # Enum: Group2048, Group4096, ...
|
|
230
|
+
│ └── srp-encoding.ts # BigInt ↔ bytes хелперы
|
|
231
|
+
└── utils/
|
|
232
|
+
├── security-utils.ts # Base64, BigInt, сравнение в постоянное время
|
|
233
|
+
└── srp-encoding.ts # Хеш модулей, сессионный ключ, M1 / M2
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Быстрый старт
|
|
237
|
+
|
|
238
|
+
#### Шифрование / дешифрование
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
import { CryptoService } from './crypto/crypto-service.js';
|
|
242
|
+
import { CryptoVersion } from './crypto/crypto-version.js';
|
|
243
|
+
|
|
244
|
+
const crypto = new CryptoService();
|
|
245
|
+
const key = crypto.generateRandomBytes(32); // AES-256
|
|
246
|
+
|
|
247
|
+
const encrypted = await crypto.encryptData(
|
|
248
|
+
{ message: "Hello, World!" },
|
|
249
|
+
key,
|
|
250
|
+
CryptoVersion.V1
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
const decrypted = await crypto.decryptData<MyData>(encrypted, key);
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Формат зашифрованных данных (Base64):
|
|
257
|
+
|
|
258
|
+
```
|
|
259
|
+
[Version (1 byte)][Nonce (N bytes)][Ciphertext + Tag]
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
#### Вывод ключей из пароля
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import { KeyDerivationService } from './crypto/key-derivation-service.js';
|
|
266
|
+
import { CryptoVersion } from './crypto/crypto-version.js';
|
|
267
|
+
|
|
268
|
+
const kdf = new KeyDerivationService();
|
|
269
|
+
const salt = crypto.getRandomValues(new Uint8Array(16));
|
|
270
|
+
|
|
271
|
+
const { kek, authHash } = await kdf.deriveKeysFromPassword(
|
|
272
|
+
"user@example.com",
|
|
273
|
+
"SuperSecret123!",
|
|
274
|
+
salt,
|
|
275
|
+
CryptoVersion.V1
|
|
276
|
+
);
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
- `kek` — `Uint8Array` ключ для AES-GCM
|
|
280
|
+
- `authHash` — Base64-строка для SRP-аутентификации
|
|
281
|
+
|
|
282
|
+
#### SRP-аутентификация
|
|
283
|
+
|
|
284
|
+
**Сервер — генерация challenge**
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
import { SrpServerService } from './srp/srp-server-service.js';
|
|
288
|
+
import { SrpGroup } from './srp/srp-group.js';
|
|
289
|
+
|
|
290
|
+
const server = new SrpServerService();
|
|
291
|
+
|
|
292
|
+
const session = await server.getSrpChallenge(
|
|
293
|
+
"user@example.com",
|
|
294
|
+
verifierBytes,
|
|
295
|
+
salt,
|
|
296
|
+
SrpGroup.Group2048
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
// Отправить клиенту: salt + session.publicKeyB (B)
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**Клиент — генерация proof**
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
import { SrpClientService } from './srp/srp-client-service.js';
|
|
306
|
+
import { SrpKeyDerivationService } from './srp/srp-key-derivation-service.js';
|
|
307
|
+
import { SrpGroup } from './srp/srp-group.js';
|
|
308
|
+
import { CryptoVersion } from './crypto/crypto-version.js';
|
|
309
|
+
|
|
310
|
+
const client = new SrpClientService();
|
|
311
|
+
const kdf = new SrpKeyDerivationService();
|
|
312
|
+
|
|
313
|
+
// 1. Вывести auth-hash из пароля
|
|
314
|
+
const authHash = await kdf.deriveAuthHashForSrp(
|
|
315
|
+
identity, password, salt, SrpGroup.Group2048, CryptoVersion.V1
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
// 2. Сгенерировать proof
|
|
319
|
+
const { A, M1, SessionKeyK } = await client.generateSrpProof(
|
|
320
|
+
identity,
|
|
321
|
+
authHash,
|
|
322
|
+
btoa(String.fromCharCode(...salt)),
|
|
323
|
+
btoa(String.fromCharCode(...session.publicKeyB)),
|
|
324
|
+
SrpGroup.Group2048
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
// Отправить серверу: A + M1
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**Сервер — проверка proof**
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
const serverM2 = await server.verifySrpProof(
|
|
334
|
+
session,
|
|
335
|
+
A,
|
|
336
|
+
M1,
|
|
337
|
+
SrpGroup.Group2048
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
// Отправить клиенту: serverM2
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
**Клиент — проверка server proof**
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
const isValid = await client.verifyServerM2(
|
|
347
|
+
A,
|
|
348
|
+
M1,
|
|
349
|
+
SessionKeyK,
|
|
350
|
+
serverM2,
|
|
351
|
+
SrpGroup.Group2048
|
|
352
|
+
);
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Безопасность
|
|
356
|
+
|
|
357
|
+
- Все чувствительные буферы явно перезаписываются после использования
|
|
358
|
+
- Соль должна быть **минимум 16 байт**
|
|
359
|
+
- Ключ AES-256 — **строго 32 байта**
|
|
360
|
+
- SRP-группы защищены от атак на малые подгруппы (проверки `A % N != 0`, `B != 0`)
|
|
361
|
+
- Полностью опирается на нативный Web Crypto API — никаких самописных криптопримитивов
|
|
362
|
+
|
|
363
|
+
### Лицензия
|
|
364
|
+
|
|
365
|
+
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
// src/configurations/security-constants.ts
|
|
4
4
|
var SecurityConstants = {
|
|
5
|
+
/** Standard nonce size for AES-GCM (96 bits). Fixed by NIST SP 800-38D. */
|
|
5
6
|
AesGcmNonceSize: 12,
|
|
6
|
-
|
|
7
|
+
/** Minimum allowed authentication tag size (96 bits). */
|
|
7
8
|
AesGcmTagSizeMin: 12,
|
|
9
|
+
/** Maximum allowed authentication tag size (128 bits). */
|
|
8
10
|
AesGcmTagSizeMax: 16,
|
|
11
|
+
/** Key size for AES-256 (256 bits). */
|
|
9
12
|
KeySizeBytes: 32,
|
|
10
|
-
|
|
11
|
-
Pbkdf2IterationsDefault: 6e5,
|
|
13
|
+
/** Absolute minimum PBKDF2 iterations for any profile version. */
|
|
12
14
|
Pbkdf2IterationsMinimum: 1e5
|
|
13
15
|
};
|
|
14
16
|
var HashSizes = {
|
|
@@ -110,54 +112,36 @@ var SecurityUtils = class {
|
|
|
110
112
|
};
|
|
111
113
|
|
|
112
114
|
// src/crypto/aes-gcm-options.ts
|
|
113
|
-
var
|
|
114
|
-
constructor() {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
return this._tagSize;
|
|
129
|
-
}
|
|
130
|
-
set tagSize(v) {
|
|
131
|
-
if (v < SecurityConstants.AesGcmTagSizeMin || v > SecurityConstants.AesGcmTagSizeMax) throw new RangeError(`Tag size must be between ${SecurityConstants.AesGcmTagSizeMin} and ${SecurityConstants.AesGcmTagSizeMax}`);
|
|
132
|
-
this._tagSize = v;
|
|
115
|
+
var _AesGcmOptions = class _AesGcmOptions {
|
|
116
|
+
constructor(nonceSize, tagSize) {
|
|
117
|
+
if (nonceSize !== SecurityConstants.AesGcmNonceSize) {
|
|
118
|
+
throw new RangeError(
|
|
119
|
+
`AES-GCM requires exactly ${SecurityConstants.AesGcmNonceSize}-byte nonce per NIST SP 800-38D.`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
if (tagSize < SecurityConstants.AesGcmTagSizeMin || tagSize > SecurityConstants.AesGcmTagSizeMax) {
|
|
123
|
+
throw new RangeError(
|
|
124
|
+
`Tag size must be between ${SecurityConstants.AesGcmTagSizeMin} and ${SecurityConstants.AesGcmTagSizeMax} bytes.`
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
this.nonceSize = nonceSize;
|
|
128
|
+
this.tagSize = tagSize;
|
|
129
|
+
Object.freeze(this);
|
|
133
130
|
}
|
|
134
131
|
/** Validates that {@link tagSize} is in the allowed range. */
|
|
135
132
|
validate() {
|
|
136
|
-
if (this.tagSize < SecurityConstants.AesGcmTagSizeMin || this.tagSize > SecurityConstants.AesGcmTagSizeMax)
|
|
137
|
-
|
|
138
|
-
/** Default preset: nonce=12, tag=16, no AAD. */
|
|
139
|
-
static get default() {
|
|
140
|
-
return new _AesGcmOptions();
|
|
141
|
-
}
|
|
142
|
-
/** Fluent setter for {@link tagSize}. */
|
|
143
|
-
withTagSize(s) {
|
|
144
|
-
this.tagSize = s;
|
|
145
|
-
return this;
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Fluent setter for {@link associatedData}.
|
|
149
|
-
* Accepts a byte array or a UTF-8 string (encoded internally).
|
|
150
|
-
*/
|
|
151
|
-
withAssociatedData(aad) {
|
|
152
|
-
this.associatedData = typeof aad === "string" ? new TextEncoder().encode(aad) : aad;
|
|
153
|
-
return this;
|
|
154
|
-
}
|
|
155
|
-
/** Validates and returns this instance. */
|
|
156
|
-
build() {
|
|
157
|
-
this.validate();
|
|
158
|
-
return this;
|
|
133
|
+
if (this.tagSize < SecurityConstants.AesGcmTagSizeMin || this.tagSize > SecurityConstants.AesGcmTagSizeMax)
|
|
134
|
+
throw new Error(`Invalid Tag Size: ${this.tagSize}`);
|
|
159
135
|
}
|
|
160
136
|
};
|
|
137
|
+
/**
|
|
138
|
+
* V1 preset: nonce=12, tag=16, no AAD.
|
|
139
|
+
* These exact values are frozen for all V1-encrypted payloads.
|
|
140
|
+
*/
|
|
141
|
+
_AesGcmOptions.V1 = Object.freeze(
|
|
142
|
+
new _AesGcmOptions(12, 16)
|
|
143
|
+
);
|
|
144
|
+
var AesGcmOptions = _AesGcmOptions;
|
|
161
145
|
|
|
162
146
|
// src/crypto/crypto-profile.ts
|
|
163
147
|
var CryptoProfile = class {
|
|
@@ -179,28 +163,21 @@ var CryptoVersion = /* @__PURE__ */ ((CryptoVersion2) => {
|
|
|
179
163
|
})(CryptoVersion || {});
|
|
180
164
|
|
|
181
165
|
// src/crypto/kdf-options.ts
|
|
182
|
-
var
|
|
183
|
-
constructor() {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
this.
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
get hashAlgorithm() {
|
|
198
|
-
return this._hashAlgorithm;
|
|
199
|
-
}
|
|
200
|
-
set hashAlgorithm(v) {
|
|
201
|
-
if (!SupportedHashAlgorithms.includes(v))
|
|
202
|
-
throw new RangeError(`Unsupported hash: ${v}`);
|
|
203
|
-
this._hashAlgorithm = v;
|
|
166
|
+
var _KdfOptions = class _KdfOptions {
|
|
167
|
+
constructor(pbkdf2Iterations, hashAlgorithm) {
|
|
168
|
+
if (pbkdf2Iterations < SecurityConstants.Pbkdf2IterationsMinimum) {
|
|
169
|
+
throw new RangeError(
|
|
170
|
+
`PBKDF2 iterations must be at least ${SecurityConstants.Pbkdf2IterationsMinimum}.`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
if (!SupportedHashAlgorithms.includes(hashAlgorithm)) {
|
|
174
|
+
throw new RangeError(
|
|
175
|
+
`Unsupported hash algorithm: ${hashAlgorithm}. Supported: ${SupportedHashAlgorithms.join(", ")}.`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
this.pbkdf2Iterations = pbkdf2Iterations;
|
|
179
|
+
this.hashAlgorithm = hashAlgorithm;
|
|
180
|
+
Object.freeze(this);
|
|
204
181
|
}
|
|
205
182
|
/** Validates iterations and hash algorithm. */
|
|
206
183
|
validate() {
|
|
@@ -209,26 +186,15 @@ var KdfOptions = class _KdfOptions {
|
|
|
209
186
|
if (!SupportedHashAlgorithms.includes(this.hashAlgorithm))
|
|
210
187
|
throw new Error(`Invalid hash algorithm: ${this.hashAlgorithm}`);
|
|
211
188
|
}
|
|
212
|
-
/** Default preset: SHA-256, 600_000 iterations. */
|
|
213
|
-
static get default() {
|
|
214
|
-
return new _KdfOptions();
|
|
215
|
-
}
|
|
216
|
-
/** Fluent setter for {@link pbkdf2Iterations}. */
|
|
217
|
-
withPbkdf2Iterations(i) {
|
|
218
|
-
this.pbkdf2Iterations = i;
|
|
219
|
-
return this;
|
|
220
|
-
}
|
|
221
|
-
/** Fluent setter for {@link hashAlgorithm}. */
|
|
222
|
-
withHashAlgorithm(h) {
|
|
223
|
-
this.hashAlgorithm = h;
|
|
224
|
-
return this;
|
|
225
|
-
}
|
|
226
|
-
/** Validates and returns this instance. */
|
|
227
|
-
build() {
|
|
228
|
-
this.validate();
|
|
229
|
-
return this;
|
|
230
|
-
}
|
|
231
189
|
};
|
|
190
|
+
/**
|
|
191
|
+
* V1 preset: SHA-256, 600_000 iterations.
|
|
192
|
+
* These exact values are frozen for all V1-derived keys.
|
|
193
|
+
*/
|
|
194
|
+
_KdfOptions.V1 = Object.freeze(
|
|
195
|
+
new _KdfOptions(6e5, "SHA-256")
|
|
196
|
+
);
|
|
197
|
+
var KdfOptions = _KdfOptions;
|
|
232
198
|
|
|
233
199
|
// src/crypto/crypto-profile-registry.ts
|
|
234
200
|
var CryptoProfileRegistry = class {
|
|
@@ -237,8 +203,8 @@ var CryptoProfileRegistry = class {
|
|
|
237
203
|
case 1 /* V1 */:
|
|
238
204
|
return new CryptoProfile({
|
|
239
205
|
version: 1 /* V1 */,
|
|
240
|
-
kdfOptions: KdfOptions.
|
|
241
|
-
aesGcmOptions: AesGcmOptions.
|
|
206
|
+
kdfOptions: KdfOptions.V1,
|
|
207
|
+
aesGcmOptions: AesGcmOptions.V1
|
|
242
208
|
});
|
|
243
209
|
default:
|
|
244
210
|
throw new Error(`Unsupported crypto version: ${version}`);
|
|
@@ -286,16 +252,11 @@ var CryptoService = class {
|
|
|
286
252
|
false,
|
|
287
253
|
["encrypt"]
|
|
288
254
|
);
|
|
289
|
-
let associatedData = new Uint8Array(0);
|
|
290
|
-
if (opts.associatedData != null) {
|
|
291
|
-
associatedData = opts.associatedData;
|
|
292
|
-
}
|
|
293
255
|
const encryptedContent = await crypto.subtle.encrypt(
|
|
294
256
|
{
|
|
295
257
|
name: "AES-GCM",
|
|
296
258
|
iv: nonce,
|
|
297
|
-
tagLength: opts.tagSize * 8
|
|
298
|
-
additionalData: associatedData
|
|
259
|
+
tagLength: opts.tagSize * 8
|
|
299
260
|
},
|
|
300
261
|
cryptoKey,
|
|
301
262
|
plainBytes
|
|
@@ -334,15 +295,11 @@ var CryptoService = class {
|
|
|
334
295
|
["decrypt"]
|
|
335
296
|
);
|
|
336
297
|
try {
|
|
337
|
-
let associatedData = new Uint8Array(0);
|
|
338
|
-
if (opts.associatedData != null)
|
|
339
|
-
associatedData = opts.associatedData;
|
|
340
298
|
const decryptedBuffer = await crypto.subtle.decrypt(
|
|
341
299
|
{
|
|
342
300
|
name: "AES-GCM",
|
|
343
301
|
iv: nonce,
|
|
344
|
-
tagLength: opts.tagSize * 8
|
|
345
|
-
additionalData: associatedData
|
|
302
|
+
tagLength: opts.tagSize * 8
|
|
346
303
|
},
|
|
347
304
|
cryptoKey,
|
|
348
305
|
ciphertextWithTag
|