aegis-aead 0.1.1 → 0.2.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 +241 -60
- package/dist/aegis128l-bs.d.ts +162 -0
- package/dist/aegis128l-bs.d.ts.map +1 -0
- package/dist/aegis128l-bs.js +470 -0
- package/dist/aegis128l-bs.js.map +1 -0
- package/dist/aegis128l.d.ts +42 -5
- package/dist/aegis128l.d.ts.map +1 -1
- package/dist/aegis128l.js +79 -5
- package/dist/aegis128l.js.map +1 -1
- package/dist/aegis128x.d.ts +67 -12
- package/dist/aegis128x.d.ts.map +1 -1
- package/dist/aegis128x.js +102 -9
- package/dist/aegis128x.js.map +1 -1
- package/dist/aegis256-bs.d.ts +151 -0
- package/dist/aegis256-bs.d.ts.map +1 -0
- package/dist/aegis256-bs.js +398 -0
- package/dist/aegis256-bs.js.map +1 -0
- package/dist/aegis256.d.ts +42 -5
- package/dist/aegis256.d.ts.map +1 -1
- package/dist/aegis256.js +79 -5
- package/dist/aegis256.js.map +1 -1
- package/dist/aegis256x.d.ts +67 -12
- package/dist/aegis256x.d.ts.map +1 -1
- package/dist/aegis256x.js +102 -9
- package/dist/aegis256x.js.map +1 -1
- package/dist/aes-bs.d.ts +71 -0
- package/dist/aes-bs.d.ts.map +1 -0
- package/dist/aes-bs.js +399 -0
- package/dist/aes-bs.js.map +1 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/random.d.ts +22 -0
- package/dist/random.d.ts.map +1 -0
- package/dist/random.js +36 -0
- package/dist/random.js.map +1 -0
- package/package.json +1 -1
- package/src/aegis128l-bs.ts +602 -0
- package/src/aegis128l.ts +112 -5
- package/src/aegis128x.ts +174 -15
- package/src/aegis256-bs.ts +518 -0
- package/src/aegis256.ts +112 -5
- package/src/aegis256x.ts +174 -15
- package/src/aes-bs.ts +459 -0
- package/src/index.ts +66 -0
- package/src/random.ts +41 -0
- package/README.md~ +0 -154
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bitsliced AEGIS-256 implementation.
|
|
3
|
+
* Provides constant-time operation by processing state blocks simultaneously.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { constantTimeEqual, zeroPad } from "./aes.js";
|
|
7
|
+
import {
|
|
8
|
+
type AesBlock,
|
|
9
|
+
type AesBlocks,
|
|
10
|
+
aesRound,
|
|
11
|
+
blockFromBytes,
|
|
12
|
+
blocksPut,
|
|
13
|
+
blockToBytes,
|
|
14
|
+
blockXor,
|
|
15
|
+
createAesBlock,
|
|
16
|
+
createAesBlocks,
|
|
17
|
+
pack,
|
|
18
|
+
unpack,
|
|
19
|
+
wordIdx,
|
|
20
|
+
} from "./aes-bs.js";
|
|
21
|
+
import { randomBytes } from "./random.js";
|
|
22
|
+
|
|
23
|
+
const RATE = 16;
|
|
24
|
+
|
|
25
|
+
const C0: AesBlock = new Uint32Array([
|
|
26
|
+
0x02010100, 0x0d080503, 0x59372215, 0x6279e990,
|
|
27
|
+
]);
|
|
28
|
+
const C1: AesBlock = new Uint32Array([
|
|
29
|
+
0x55183ddb, 0xf12fc26d, 0x42311120, 0xdd28b573,
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Bitsliced AEGIS-256 cipher state.
|
|
34
|
+
* Uses 6 AES blocks (96 bytes) stored in bitsliced form.
|
|
35
|
+
*/
|
|
36
|
+
export class Aegis256BsState {
|
|
37
|
+
private st: AesBlocks;
|
|
38
|
+
private st1: AesBlocks;
|
|
39
|
+
private tmp: AesBlock;
|
|
40
|
+
|
|
41
|
+
constructor() {
|
|
42
|
+
this.st = createAesBlocks();
|
|
43
|
+
this.st1 = createAesBlocks();
|
|
44
|
+
this.tmp = createAesBlock();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* AEGIS round function: applies AES round to all blocks and rotates.
|
|
49
|
+
* st[i] = AES(st[i]) ^ st[(i+5) mod 6]
|
|
50
|
+
*/
|
|
51
|
+
private aegisRound(): void {
|
|
52
|
+
const st = this.st;
|
|
53
|
+
const st1 = this.st1;
|
|
54
|
+
|
|
55
|
+
st1.set(st);
|
|
56
|
+
pack(st1);
|
|
57
|
+
aesRound(st1);
|
|
58
|
+
unpack(st1);
|
|
59
|
+
|
|
60
|
+
for (let i = 0; i < 6; i++) {
|
|
61
|
+
const prev = (i + 5) % 6;
|
|
62
|
+
st[wordIdx(i, 0)] = (st[wordIdx(i, 0)]! ^ st1[wordIdx(prev, 0)]!) >>> 0;
|
|
63
|
+
st[wordIdx(i, 1)] = (st[wordIdx(i, 1)]! ^ st1[wordIdx(prev, 1)]!) >>> 0;
|
|
64
|
+
st[wordIdx(i, 2)] = (st[wordIdx(i, 2)]! ^ st1[wordIdx(prev, 2)]!) >>> 0;
|
|
65
|
+
st[wordIdx(i, 3)] = (st[wordIdx(i, 3)]! ^ st1[wordIdx(prev, 3)]!) >>> 0;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Absorb rate: XOR message block into state position 0.
|
|
71
|
+
*/
|
|
72
|
+
private absorbRate(m: AesBlock): void {
|
|
73
|
+
const st = this.st;
|
|
74
|
+
st[wordIdx(0, 0)] = (st[wordIdx(0, 0)]! ^ m[0]!) >>> 0;
|
|
75
|
+
st[wordIdx(0, 1)] = (st[wordIdx(0, 1)]! ^ m[1]!) >>> 0;
|
|
76
|
+
st[wordIdx(0, 2)] = (st[wordIdx(0, 2)]! ^ m[2]!) >>> 0;
|
|
77
|
+
st[wordIdx(0, 3)] = (st[wordIdx(0, 3)]! ^ m[3]!) >>> 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Update state with a message block.
|
|
82
|
+
*/
|
|
83
|
+
private update(m: AesBlock): void {
|
|
84
|
+
this.aegisRound();
|
|
85
|
+
this.absorbRate(m);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Initializes the state with a key and nonce.
|
|
90
|
+
* @param key - 32-byte encryption key
|
|
91
|
+
* @param nonce - 32-byte nonce (must be unique per message)
|
|
92
|
+
*/
|
|
93
|
+
init(key: Uint8Array, nonce: Uint8Array): void {
|
|
94
|
+
const k0 = createAesBlock();
|
|
95
|
+
const k1 = createAesBlock();
|
|
96
|
+
const n0 = createAesBlock();
|
|
97
|
+
const n1 = createAesBlock();
|
|
98
|
+
const k0n0 = createAesBlock();
|
|
99
|
+
const k1n1 = createAesBlock();
|
|
100
|
+
const k0c0 = createAesBlock();
|
|
101
|
+
const k1c1 = createAesBlock();
|
|
102
|
+
|
|
103
|
+
blockFromBytes(k0, key.subarray(0, 16));
|
|
104
|
+
blockFromBytes(k1, key.subarray(16, 32));
|
|
105
|
+
blockFromBytes(n0, nonce.subarray(0, 16));
|
|
106
|
+
blockFromBytes(n1, nonce.subarray(16, 32));
|
|
107
|
+
blockXor(k0n0, k0, n0);
|
|
108
|
+
blockXor(k1n1, k1, n1);
|
|
109
|
+
blockXor(k0c0, k0, C0);
|
|
110
|
+
blockXor(k1c1, k1, C1);
|
|
111
|
+
|
|
112
|
+
this.st.fill(0);
|
|
113
|
+
blocksPut(this.st, k0n0, 0);
|
|
114
|
+
blocksPut(this.st, k1n1, 1);
|
|
115
|
+
blocksPut(this.st, C1, 2);
|
|
116
|
+
blocksPut(this.st, C0, 3);
|
|
117
|
+
blocksPut(this.st, k0c0, 4);
|
|
118
|
+
blocksPut(this.st, k1c1, 5);
|
|
119
|
+
|
|
120
|
+
for (let i = 0; i < 4; i++) {
|
|
121
|
+
this.update(k0);
|
|
122
|
+
this.update(k1);
|
|
123
|
+
this.update(k0n0);
|
|
124
|
+
this.update(k1n1);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Absorbs a 16-byte associated data block into the state.
|
|
130
|
+
* @param ai - 16-byte associated data block
|
|
131
|
+
*/
|
|
132
|
+
absorb(ai: Uint8Array): void {
|
|
133
|
+
const msg = this.tmp;
|
|
134
|
+
blockFromBytes(msg, ai);
|
|
135
|
+
this.update(msg);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Encrypts a 16-byte plaintext block and writes to output buffer.
|
|
140
|
+
* @param xi - 16-byte plaintext block
|
|
141
|
+
* @param out - 16-byte output buffer
|
|
142
|
+
*/
|
|
143
|
+
encTo(xi: Uint8Array, out: Uint8Array): void {
|
|
144
|
+
const st = this.st;
|
|
145
|
+
const z = this.tmp;
|
|
146
|
+
const t = createAesBlock();
|
|
147
|
+
|
|
148
|
+
for (let i = 0; i < 4; i++) {
|
|
149
|
+
z[i] =
|
|
150
|
+
(st[wordIdx(1, i)]! ^
|
|
151
|
+
st[wordIdx(4, i)]! ^
|
|
152
|
+
st[wordIdx(5, i)]! ^
|
|
153
|
+
(st[wordIdx(2, i)]! & st[wordIdx(3, i)]!)) >>>
|
|
154
|
+
0;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
blockFromBytes(t, xi);
|
|
158
|
+
|
|
159
|
+
const outBlock = createAesBlock();
|
|
160
|
+
blockXor(outBlock, t, z);
|
|
161
|
+
blockToBytes(out, outBlock);
|
|
162
|
+
|
|
163
|
+
this.update(t);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Encrypts a 16-byte plaintext block.
|
|
168
|
+
* @param xi - 16-byte plaintext block
|
|
169
|
+
* @returns 16-byte ciphertext block
|
|
170
|
+
*/
|
|
171
|
+
enc(xi: Uint8Array): Uint8Array {
|
|
172
|
+
const out = new Uint8Array(16);
|
|
173
|
+
this.encTo(xi, out);
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Decrypts a 16-byte ciphertext block and writes to output buffer.
|
|
179
|
+
* @param ci - 16-byte ciphertext block
|
|
180
|
+
* @param out - 16-byte output buffer
|
|
181
|
+
*/
|
|
182
|
+
decTo(ci: Uint8Array, out: Uint8Array): void {
|
|
183
|
+
const st = this.st;
|
|
184
|
+
const msg = this.tmp;
|
|
185
|
+
|
|
186
|
+
blockFromBytes(msg, ci);
|
|
187
|
+
|
|
188
|
+
for (let i = 0; i < 4; i++) {
|
|
189
|
+
msg[i] =
|
|
190
|
+
(msg[i]! ^
|
|
191
|
+
st[wordIdx(1, i)]! ^
|
|
192
|
+
st[wordIdx(4, i)]! ^
|
|
193
|
+
st[wordIdx(5, i)]! ^
|
|
194
|
+
(st[wordIdx(2, i)]! & st[wordIdx(3, i)]!)) >>>
|
|
195
|
+
0;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
this.update(msg);
|
|
199
|
+
blockToBytes(out, msg);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Decrypts a 16-byte ciphertext block.
|
|
204
|
+
* @param ci - 16-byte ciphertext block
|
|
205
|
+
* @returns 16-byte plaintext block
|
|
206
|
+
*/
|
|
207
|
+
dec(ci: Uint8Array): Uint8Array {
|
|
208
|
+
const out = new Uint8Array(16);
|
|
209
|
+
this.decTo(ci, out);
|
|
210
|
+
return out;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Decrypts a partial (final) ciphertext block smaller than 16 bytes.
|
|
215
|
+
* @param cn - Partial ciphertext block (1-15 bytes)
|
|
216
|
+
* @returns Decrypted plaintext of the same length
|
|
217
|
+
*/
|
|
218
|
+
decPartial(cn: Uint8Array): Uint8Array {
|
|
219
|
+
const st = this.st;
|
|
220
|
+
const msg = this.tmp;
|
|
221
|
+
|
|
222
|
+
const padded = zeroPad(cn, RATE);
|
|
223
|
+
blockFromBytes(msg, padded);
|
|
224
|
+
|
|
225
|
+
for (let i = 0; i < 4; i++) {
|
|
226
|
+
msg[i] =
|
|
227
|
+
(msg[i]! ^
|
|
228
|
+
st[wordIdx(1, i)]! ^
|
|
229
|
+
st[wordIdx(4, i)]! ^
|
|
230
|
+
st[wordIdx(5, i)]! ^
|
|
231
|
+
(st[wordIdx(2, i)]! & st[wordIdx(3, i)]!)) >>>
|
|
232
|
+
0;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const pad = new Uint8Array(RATE);
|
|
236
|
+
blockToBytes(pad, msg);
|
|
237
|
+
|
|
238
|
+
const xn = new Uint8Array(pad.subarray(0, cn.length));
|
|
239
|
+
|
|
240
|
+
pad.fill(0, cn.length);
|
|
241
|
+
blockFromBytes(msg, pad);
|
|
242
|
+
|
|
243
|
+
this.aegisRound();
|
|
244
|
+
this.absorbRate(msg);
|
|
245
|
+
|
|
246
|
+
return xn;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Finalizes encryption/decryption and produces an authentication tag.
|
|
251
|
+
* @param adLen - Associated data length in bytes
|
|
252
|
+
* @param msgLen - Message length in bytes
|
|
253
|
+
* @param tagLen - Tag length (16 or 32 bytes)
|
|
254
|
+
* @returns Authentication tag
|
|
255
|
+
*/
|
|
256
|
+
finalize(adLen: number, msgLen: number, tagLen: 16 | 32 = 16): Uint8Array {
|
|
257
|
+
const st = this.st;
|
|
258
|
+
const tmp = this.tmp;
|
|
259
|
+
|
|
260
|
+
tmp[0] = ((adLen * 8) & 0xffffffff) >>> 0;
|
|
261
|
+
tmp[1] = Math.floor((adLen * 8) / 0x100000000) >>> 0;
|
|
262
|
+
tmp[2] = ((msgLen * 8) & 0xffffffff) >>> 0;
|
|
263
|
+
tmp[3] = Math.floor((msgLen * 8) / 0x100000000) >>> 0;
|
|
264
|
+
|
|
265
|
+
tmp[0] = (tmp[0]! ^ st[wordIdx(3, 0)]!) >>> 0;
|
|
266
|
+
tmp[1] = (tmp[1]! ^ st[wordIdx(3, 1)]!) >>> 0;
|
|
267
|
+
tmp[2] = (tmp[2]! ^ st[wordIdx(3, 2)]!) >>> 0;
|
|
268
|
+
tmp[3] = (tmp[3]! ^ st[wordIdx(3, 3)]!) >>> 0;
|
|
269
|
+
|
|
270
|
+
for (let i = 0; i < 7; i++) {
|
|
271
|
+
this.update(tmp);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (tagLen === 16) {
|
|
275
|
+
const tag = new Uint8Array(16);
|
|
276
|
+
const tagBlock = createAesBlock();
|
|
277
|
+
for (let i = 0; i < 4; i++) {
|
|
278
|
+
tagBlock[i] =
|
|
279
|
+
(st[wordIdx(0, i)]! ^
|
|
280
|
+
st[wordIdx(1, i)]! ^
|
|
281
|
+
st[wordIdx(2, i)]! ^
|
|
282
|
+
st[wordIdx(3, i)]! ^
|
|
283
|
+
st[wordIdx(4, i)]! ^
|
|
284
|
+
st[wordIdx(5, i)]!) >>>
|
|
285
|
+
0;
|
|
286
|
+
}
|
|
287
|
+
blockToBytes(tag, tagBlock);
|
|
288
|
+
return tag;
|
|
289
|
+
} else {
|
|
290
|
+
const tag = new Uint8Array(32);
|
|
291
|
+
const tagBlock0 = createAesBlock();
|
|
292
|
+
const tagBlock1 = createAesBlock();
|
|
293
|
+
for (let i = 0; i < 4; i++) {
|
|
294
|
+
tagBlock0[i] =
|
|
295
|
+
(st[wordIdx(0, i)]! ^ st[wordIdx(1, i)]! ^ st[wordIdx(2, i)]!) >>> 0;
|
|
296
|
+
}
|
|
297
|
+
for (let i = 0; i < 4; i++) {
|
|
298
|
+
tagBlock1[i] =
|
|
299
|
+
(st[wordIdx(3, i)]! ^ st[wordIdx(4, i)]! ^ st[wordIdx(5, i)]!) >>> 0;
|
|
300
|
+
}
|
|
301
|
+
blockToBytes(tag.subarray(0, 16), tagBlock0);
|
|
302
|
+
blockToBytes(tag.subarray(16, 32), tagBlock1);
|
|
303
|
+
return tag;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Encrypts a message using bitsliced AEGIS-256 (detached mode).
|
|
310
|
+
* @param msg - Plaintext message
|
|
311
|
+
* @param ad - Associated data (authenticated but not encrypted)
|
|
312
|
+
* @param key - 32-byte encryption key
|
|
313
|
+
* @param nonce - 32-byte nonce (must be unique per message with the same key)
|
|
314
|
+
* @param tagLen - Authentication tag length: 16 or 32 bytes (default: 16)
|
|
315
|
+
* @returns Object containing ciphertext and authentication tag separately
|
|
316
|
+
*/
|
|
317
|
+
export function aegis256BsEncryptDetached(
|
|
318
|
+
msg: Uint8Array,
|
|
319
|
+
ad: Uint8Array,
|
|
320
|
+
key: Uint8Array,
|
|
321
|
+
nonce: Uint8Array,
|
|
322
|
+
tagLen: 16 | 32 = 16,
|
|
323
|
+
): { ciphertext: Uint8Array; tag: Uint8Array } {
|
|
324
|
+
const state = new Aegis256BsState();
|
|
325
|
+
state.init(key, nonce);
|
|
326
|
+
|
|
327
|
+
const adPadded = zeroPad(ad, RATE);
|
|
328
|
+
for (let i = 0; i + RATE <= adPadded.length; i += RATE) {
|
|
329
|
+
state.absorb(adPadded.subarray(i, i + RATE));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const ciphertext = new Uint8Array(msg.length);
|
|
333
|
+
const fullBlocks = Math.floor(msg.length / RATE) * RATE;
|
|
334
|
+
|
|
335
|
+
for (let i = 0; i < fullBlocks; i += RATE) {
|
|
336
|
+
state.encTo(msg.subarray(i, i + RATE), ciphertext.subarray(i, i + RATE));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (msg.length > fullBlocks) {
|
|
340
|
+
const lastBlock = zeroPad(msg.subarray(fullBlocks), RATE);
|
|
341
|
+
const encBlock = state.enc(lastBlock);
|
|
342
|
+
ciphertext.set(encBlock.subarray(0, msg.length - fullBlocks), fullBlocks);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const tag = state.finalize(ad.length, msg.length, tagLen);
|
|
346
|
+
|
|
347
|
+
return { ciphertext, tag };
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Decrypts a message using bitsliced AEGIS-256 (detached mode).
|
|
352
|
+
* @param ct - Ciphertext
|
|
353
|
+
* @param tag - Authentication tag (16 or 32 bytes)
|
|
354
|
+
* @param ad - Associated data (must match what was used during encryption)
|
|
355
|
+
* @param key - 32-byte encryption key
|
|
356
|
+
* @param nonce - 32-byte nonce (must match what was used during encryption)
|
|
357
|
+
* @returns Decrypted plaintext, or null if authentication fails
|
|
358
|
+
*/
|
|
359
|
+
export function aegis256BsDecryptDetached(
|
|
360
|
+
ct: Uint8Array,
|
|
361
|
+
tag: Uint8Array,
|
|
362
|
+
ad: Uint8Array,
|
|
363
|
+
key: Uint8Array,
|
|
364
|
+
nonce: Uint8Array,
|
|
365
|
+
): Uint8Array | null {
|
|
366
|
+
const tagLen = tag.length as 16 | 32;
|
|
367
|
+
const state = new Aegis256BsState();
|
|
368
|
+
state.init(key, nonce);
|
|
369
|
+
|
|
370
|
+
const adPadded = zeroPad(ad, RATE);
|
|
371
|
+
for (let i = 0; i + RATE <= adPadded.length; i += RATE) {
|
|
372
|
+
state.absorb(adPadded.subarray(i, i + RATE));
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const msg = new Uint8Array(ct.length);
|
|
376
|
+
const fullBlocks = Math.floor(ct.length / RATE) * RATE;
|
|
377
|
+
|
|
378
|
+
for (let i = 0; i < fullBlocks; i += RATE) {
|
|
379
|
+
state.decTo(ct.subarray(i, i + RATE), msg.subarray(i, i + RATE));
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (ct.length > fullBlocks) {
|
|
383
|
+
msg.set(state.decPartial(ct.subarray(fullBlocks)), fullBlocks);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const expectedTag = state.finalize(ad.length, msg.length, tagLen);
|
|
387
|
+
|
|
388
|
+
if (!constantTimeEqual(tag, expectedTag)) {
|
|
389
|
+
msg.fill(0);
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return msg;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export const AEGIS_256_BS_NONCE_SIZE = 32;
|
|
397
|
+
export const AEGIS_256_BS_KEY_SIZE = 32;
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Encrypts a message using bitsliced AEGIS-256.
|
|
401
|
+
* Returns a single buffer containing nonce || ciphertext || tag.
|
|
402
|
+
* @param msg - Plaintext message
|
|
403
|
+
* @param ad - Associated data (authenticated but not encrypted)
|
|
404
|
+
* @param key - 32-byte encryption key
|
|
405
|
+
* @param nonce - 32-byte nonce (optional, generates random nonce if not provided)
|
|
406
|
+
* @param tagLen - Authentication tag length: 16 or 32 bytes (default: 16)
|
|
407
|
+
* @returns Concatenated nonce || ciphertext || tag
|
|
408
|
+
*/
|
|
409
|
+
export function aegis256BsEncrypt(
|
|
410
|
+
msg: Uint8Array,
|
|
411
|
+
ad: Uint8Array,
|
|
412
|
+
key: Uint8Array,
|
|
413
|
+
nonce: Uint8Array | null = null,
|
|
414
|
+
tagLen: 16 | 32 = 16,
|
|
415
|
+
): Uint8Array {
|
|
416
|
+
const actualNonce = nonce ?? randomBytes(AEGIS_256_BS_NONCE_SIZE);
|
|
417
|
+
const { ciphertext, tag } = aegis256BsEncryptDetached(
|
|
418
|
+
msg,
|
|
419
|
+
ad,
|
|
420
|
+
key,
|
|
421
|
+
actualNonce,
|
|
422
|
+
tagLen,
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
const result = new Uint8Array(
|
|
426
|
+
AEGIS_256_BS_NONCE_SIZE + ciphertext.length + tagLen,
|
|
427
|
+
);
|
|
428
|
+
result.set(actualNonce, 0);
|
|
429
|
+
result.set(ciphertext, AEGIS_256_BS_NONCE_SIZE);
|
|
430
|
+
result.set(tag, AEGIS_256_BS_NONCE_SIZE + ciphertext.length);
|
|
431
|
+
|
|
432
|
+
return result;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Decrypts a message using bitsliced AEGIS-256.
|
|
437
|
+
* Expects input as nonce || ciphertext || tag.
|
|
438
|
+
* @param sealed - Concatenated nonce || ciphertext || tag
|
|
439
|
+
* @param ad - Associated data (must match what was used during encryption)
|
|
440
|
+
* @param key - 32-byte encryption key
|
|
441
|
+
* @param tagLen - Authentication tag length: 16 or 32 bytes (default: 16)
|
|
442
|
+
* @returns Decrypted plaintext, or null if authentication fails
|
|
443
|
+
*/
|
|
444
|
+
export function aegis256BsDecrypt(
|
|
445
|
+
sealed: Uint8Array,
|
|
446
|
+
ad: Uint8Array,
|
|
447
|
+
key: Uint8Array,
|
|
448
|
+
tagLen: 16 | 32 = 16,
|
|
449
|
+
): Uint8Array | null {
|
|
450
|
+
const nonceSize = AEGIS_256_BS_NONCE_SIZE;
|
|
451
|
+
if (sealed.length < nonceSize + tagLen) {
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
const nonce = sealed.subarray(0, nonceSize);
|
|
455
|
+
const ct = sealed.subarray(nonceSize, sealed.length - tagLen);
|
|
456
|
+
const tag = sealed.subarray(sealed.length - tagLen);
|
|
457
|
+
return aegis256BsDecryptDetached(ct, tag, ad, key, nonce);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Computes a MAC (Message Authentication Code) using bitsliced AEGIS-256.
|
|
462
|
+
* @param data - Data to authenticate
|
|
463
|
+
* @param key - 32-byte key
|
|
464
|
+
* @param nonce - 32-byte nonce (optional, uses zero nonce if null)
|
|
465
|
+
* @param tagLen - Tag length: 16 or 32 bytes (default: 16)
|
|
466
|
+
* @returns Authentication tag
|
|
467
|
+
*/
|
|
468
|
+
export function aegis256BsMac(
|
|
469
|
+
data: Uint8Array,
|
|
470
|
+
key: Uint8Array,
|
|
471
|
+
nonce: Uint8Array | null = null,
|
|
472
|
+
tagLen: 16 | 32 = 16,
|
|
473
|
+
): Uint8Array {
|
|
474
|
+
const state = new Aegis256BsState();
|
|
475
|
+
state.init(key, nonce ?? new Uint8Array(32));
|
|
476
|
+
|
|
477
|
+
const dataPadded = zeroPad(data, RATE);
|
|
478
|
+
for (let i = 0; i + RATE <= dataPadded.length; i += RATE) {
|
|
479
|
+
state.absorb(dataPadded.subarray(i, i + RATE));
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return state.finalize(data.length, tagLen, tagLen);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Verifies a MAC computed using bitsliced AEGIS-256.
|
|
487
|
+
* @param data - Data to verify
|
|
488
|
+
* @param tag - Expected authentication tag (16 or 32 bytes)
|
|
489
|
+
* @param key - 32-byte key
|
|
490
|
+
* @param nonce - 32-byte nonce (optional, uses zero nonce if null)
|
|
491
|
+
* @returns True if the tag is valid, false otherwise
|
|
492
|
+
*/
|
|
493
|
+
export function aegis256BsMacVerify(
|
|
494
|
+
data: Uint8Array,
|
|
495
|
+
tag: Uint8Array,
|
|
496
|
+
key: Uint8Array,
|
|
497
|
+
nonce: Uint8Array | null = null,
|
|
498
|
+
): boolean {
|
|
499
|
+
const tagLen = tag.length as 16 | 32;
|
|
500
|
+
const expectedTag = aegis256BsMac(data, key, nonce, tagLen);
|
|
501
|
+
return constantTimeEqual(tag, expectedTag);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Generates a random 32-byte key for bitsliced AEGIS-256.
|
|
506
|
+
* @returns 32-byte encryption key
|
|
507
|
+
*/
|
|
508
|
+
export function aegis256BsCreateKey(): Uint8Array {
|
|
509
|
+
return randomBytes(AEGIS_256_BS_KEY_SIZE);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Generates a random 32-byte nonce for bitsliced AEGIS-256.
|
|
514
|
+
* @returns 32-byte nonce
|
|
515
|
+
*/
|
|
516
|
+
export function aegis256BsCreateNonce(): Uint8Array {
|
|
517
|
+
return randomBytes(AEGIS_256_BS_NONCE_SIZE);
|
|
518
|
+
}
|
package/src/aegis256.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
xorBlocksTo,
|
|
9
9
|
zeroPad,
|
|
10
10
|
} from "./aes.js";
|
|
11
|
+
import { randomBytes } from "./random.js";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* AEGIS-256 cipher state.
|
|
@@ -244,15 +245,15 @@ export class Aegis256State {
|
|
|
244
245
|
}
|
|
245
246
|
|
|
246
247
|
/**
|
|
247
|
-
* Encrypts a message using AEGIS-256.
|
|
248
|
+
* Encrypts a message using AEGIS-256 (detached mode).
|
|
248
249
|
* @param msg - Plaintext message
|
|
249
250
|
* @param ad - Associated data (authenticated but not encrypted)
|
|
250
251
|
* @param key - 32-byte encryption key
|
|
251
252
|
* @param nonce - 32-byte nonce (must be unique per message with the same key)
|
|
252
253
|
* @param tagLen - Authentication tag length: 16 or 32 bytes (default: 16)
|
|
253
|
-
* @returns Object containing ciphertext and authentication tag
|
|
254
|
+
* @returns Object containing ciphertext and authentication tag separately
|
|
254
255
|
*/
|
|
255
|
-
export function
|
|
256
|
+
export function aegis256EncryptDetached(
|
|
256
257
|
msg: Uint8Array,
|
|
257
258
|
ad: Uint8Array,
|
|
258
259
|
key: Uint8Array,
|
|
@@ -284,7 +285,7 @@ export function aegis256Encrypt(
|
|
|
284
285
|
}
|
|
285
286
|
|
|
286
287
|
/**
|
|
287
|
-
* Decrypts a message using AEGIS-256.
|
|
288
|
+
* Decrypts a message using AEGIS-256 (detached mode).
|
|
288
289
|
* @param ct - Ciphertext
|
|
289
290
|
* @param tag - Authentication tag (16 or 32 bytes)
|
|
290
291
|
* @param ad - Associated data (must match what was used during encryption)
|
|
@@ -292,7 +293,7 @@ export function aegis256Encrypt(
|
|
|
292
293
|
* @param nonce - 32-byte nonce (must match what was used during encryption)
|
|
293
294
|
* @returns Decrypted plaintext, or null if authentication fails
|
|
294
295
|
*/
|
|
295
|
-
export function
|
|
296
|
+
export function aegis256DecryptDetached(
|
|
296
297
|
ct: Uint8Array,
|
|
297
298
|
tag: Uint8Array,
|
|
298
299
|
ad: Uint8Array,
|
|
@@ -334,6 +335,94 @@ export function aegis256Decrypt(
|
|
|
334
335
|
return msg;
|
|
335
336
|
}
|
|
336
337
|
|
|
338
|
+
/** Nonce size for AEGIS-256 in bytes. */
|
|
339
|
+
export const AEGIS_256_NONCE_SIZE = 32;
|
|
340
|
+
|
|
341
|
+
/** Key size for AEGIS-256 in bytes. */
|
|
342
|
+
export const AEGIS_256_KEY_SIZE = 32;
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Encrypts a message using AEGIS-256.
|
|
346
|
+
* Returns a single buffer containing nonce || ciphertext || tag.
|
|
347
|
+
* @param msg - Plaintext message
|
|
348
|
+
* @param ad - Associated data (authenticated but not encrypted)
|
|
349
|
+
* @param key - 32-byte encryption key
|
|
350
|
+
* @param nonce - 32-byte nonce (optional, generates random nonce if not provided)
|
|
351
|
+
* @param tagLen - Authentication tag length: 16 or 32 bytes (default: 16)
|
|
352
|
+
* @returns Concatenated nonce || ciphertext || tag
|
|
353
|
+
*/
|
|
354
|
+
export function aegis256Encrypt(
|
|
355
|
+
msg: Uint8Array,
|
|
356
|
+
ad: Uint8Array,
|
|
357
|
+
key: Uint8Array,
|
|
358
|
+
nonce: Uint8Array | null = null,
|
|
359
|
+
tagLen: 16 | 32 = 16,
|
|
360
|
+
): Uint8Array {
|
|
361
|
+
const actualNonce = nonce ?? randomBytes(AEGIS_256_NONCE_SIZE);
|
|
362
|
+
const state = new Aegis256State();
|
|
363
|
+
state.init(key, actualNonce);
|
|
364
|
+
|
|
365
|
+
const adPadded = zeroPad(ad, 16);
|
|
366
|
+
for (let i = 0; i + 16 <= adPadded.length; i += 16) {
|
|
367
|
+
state.absorb(adPadded.subarray(i, i + 16));
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const nonceSize = AEGIS_256_NONCE_SIZE;
|
|
371
|
+
const result = new Uint8Array(nonceSize + msg.length + tagLen);
|
|
372
|
+
result.set(actualNonce, 0);
|
|
373
|
+
|
|
374
|
+
const fullBlocks = Math.floor(msg.length / 16) * 16;
|
|
375
|
+
for (let i = 0; i < fullBlocks; i += 16) {
|
|
376
|
+
state.encTo(
|
|
377
|
+
msg.subarray(i, i + 16),
|
|
378
|
+
result.subarray(nonceSize + i, nonceSize + i + 16),
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (msg.length > fullBlocks) {
|
|
383
|
+
const lastBlock = zeroPad(msg.subarray(fullBlocks), 16);
|
|
384
|
+
const encBlock = state.enc(lastBlock);
|
|
385
|
+
result.set(
|
|
386
|
+
encBlock.subarray(0, msg.length - fullBlocks),
|
|
387
|
+
nonceSize + fullBlocks,
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const tag = state.finalize(
|
|
392
|
+
BigInt(ad.length * 8),
|
|
393
|
+
BigInt(msg.length * 8),
|
|
394
|
+
tagLen,
|
|
395
|
+
);
|
|
396
|
+
result.set(tag, nonceSize + msg.length);
|
|
397
|
+
|
|
398
|
+
return result;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Decrypts a message using AEGIS-256.
|
|
403
|
+
* Expects input as nonce || ciphertext || tag.
|
|
404
|
+
* @param sealed - Concatenated nonce || ciphertext || tag
|
|
405
|
+
* @param ad - Associated data (must match what was used during encryption)
|
|
406
|
+
* @param key - 32-byte encryption key
|
|
407
|
+
* @param tagLen - Authentication tag length: 16 or 32 bytes (default: 16)
|
|
408
|
+
* @returns Decrypted plaintext, or null if authentication fails
|
|
409
|
+
*/
|
|
410
|
+
export function aegis256Decrypt(
|
|
411
|
+
sealed: Uint8Array,
|
|
412
|
+
ad: Uint8Array,
|
|
413
|
+
key: Uint8Array,
|
|
414
|
+
tagLen: 16 | 32 = 16,
|
|
415
|
+
): Uint8Array | null {
|
|
416
|
+
const nonceSize = AEGIS_256_NONCE_SIZE;
|
|
417
|
+
if (sealed.length < nonceSize + tagLen) {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
const nonce = sealed.subarray(0, nonceSize);
|
|
421
|
+
const ct = sealed.subarray(nonceSize, sealed.length - tagLen);
|
|
422
|
+
const tag = sealed.subarray(sealed.length - tagLen);
|
|
423
|
+
return aegis256DecryptDetached(ct, tag, ad, key, nonce);
|
|
424
|
+
}
|
|
425
|
+
|
|
337
426
|
/**
|
|
338
427
|
* Computes a MAC (Message Authentication Code) using AEGIS-256.
|
|
339
428
|
* @param data - Data to authenticate
|
|
@@ -377,3 +466,21 @@ export function aegis256MacVerify(
|
|
|
377
466
|
const expectedTag = aegis256Mac(data, key, nonce, tagLen);
|
|
378
467
|
return constantTimeEqual(tag, expectedTag);
|
|
379
468
|
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Generates a random 32-byte key for AEGIS-256.
|
|
472
|
+
* @returns 32-byte encryption key
|
|
473
|
+
* @throws Error if no cryptographic random source is available
|
|
474
|
+
*/
|
|
475
|
+
export function aegis256CreateKey(): Uint8Array {
|
|
476
|
+
return randomBytes(AEGIS_256_KEY_SIZE);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Generates a random 32-byte nonce for AEGIS-256.
|
|
481
|
+
* @returns 32-byte nonce
|
|
482
|
+
* @throws Error if no cryptographic random source is available
|
|
483
|
+
*/
|
|
484
|
+
export function aegis256CreateNonce(): Uint8Array {
|
|
485
|
+
return randomBytes(AEGIS_256_NONCE_SIZE);
|
|
486
|
+
}
|