@gjsify/crypto 0.1.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.
Files changed (87) hide show
  1. package/README.md +27 -0
  2. package/lib/esm/asn1.js +504 -0
  3. package/lib/esm/bigint-math.js +34 -0
  4. package/lib/esm/cipher.js +1272 -0
  5. package/lib/esm/constants.js +15 -0
  6. package/lib/esm/crypto-utils.js +47 -0
  7. package/lib/esm/dh.js +411 -0
  8. package/lib/esm/ecdh.js +356 -0
  9. package/lib/esm/ecdsa.js +125 -0
  10. package/lib/esm/hash.js +100 -0
  11. package/lib/esm/hkdf.js +58 -0
  12. package/lib/esm/hmac.js +93 -0
  13. package/lib/esm/index.js +158 -0
  14. package/lib/esm/key-object.js +330 -0
  15. package/lib/esm/mgf1.js +27 -0
  16. package/lib/esm/pbkdf2.js +68 -0
  17. package/lib/esm/public-encrypt.js +175 -0
  18. package/lib/esm/random.js +138 -0
  19. package/lib/esm/rsa-oaep.js +95 -0
  20. package/lib/esm/rsa-pss.js +100 -0
  21. package/lib/esm/scrypt.js +134 -0
  22. package/lib/esm/sign.js +248 -0
  23. package/lib/esm/timing-safe-equal.js +13 -0
  24. package/lib/esm/x509.js +214 -0
  25. package/lib/types/asn1.d.ts +87 -0
  26. package/lib/types/bigint-math.d.ts +13 -0
  27. package/lib/types/cipher.d.ts +84 -0
  28. package/lib/types/constants.d.ts +10 -0
  29. package/lib/types/crypto-utils.d.ts +22 -0
  30. package/lib/types/dh.d.ts +79 -0
  31. package/lib/types/ecdh.d.ts +96 -0
  32. package/lib/types/ecdsa.d.ts +21 -0
  33. package/lib/types/hash.d.ts +25 -0
  34. package/lib/types/hkdf.d.ts +9 -0
  35. package/lib/types/hmac.d.ts +20 -0
  36. package/lib/types/index.d.ts +105 -0
  37. package/lib/types/key-object.d.ts +36 -0
  38. package/lib/types/mgf1.d.ts +5 -0
  39. package/lib/types/pbkdf2.d.ts +9 -0
  40. package/lib/types/public-encrypt.d.ts +42 -0
  41. package/lib/types/random.d.ts +22 -0
  42. package/lib/types/rsa-oaep.d.ts +8 -0
  43. package/lib/types/rsa-pss.d.ts +8 -0
  44. package/lib/types/scrypt.d.ts +11 -0
  45. package/lib/types/sign.d.ts +61 -0
  46. package/lib/types/timing-safe-equal.d.ts +6 -0
  47. package/lib/types/x509.d.ts +72 -0
  48. package/package.json +45 -0
  49. package/src/asn1.ts +797 -0
  50. package/src/bigint-math.ts +45 -0
  51. package/src/cipher.spec.ts +332 -0
  52. package/src/cipher.ts +952 -0
  53. package/src/constants.ts +16 -0
  54. package/src/crypto-utils.ts +64 -0
  55. package/src/dh.spec.ts +111 -0
  56. package/src/dh.ts +761 -0
  57. package/src/ecdh.spec.ts +116 -0
  58. package/src/ecdh.ts +624 -0
  59. package/src/ecdsa.ts +243 -0
  60. package/src/extended.spec.ts +444 -0
  61. package/src/gcm.spec.ts +141 -0
  62. package/src/hash.spec.ts +86 -0
  63. package/src/hash.ts +119 -0
  64. package/src/hkdf.ts +99 -0
  65. package/src/hmac.spec.ts +64 -0
  66. package/src/hmac.ts +123 -0
  67. package/src/index.ts +93 -0
  68. package/src/key-object.spec.ts +202 -0
  69. package/src/key-object.ts +401 -0
  70. package/src/mgf1.ts +37 -0
  71. package/src/pbkdf2.spec.ts +76 -0
  72. package/src/pbkdf2.ts +106 -0
  73. package/src/public-encrypt.ts +288 -0
  74. package/src/random.spec.ts +133 -0
  75. package/src/random.ts +183 -0
  76. package/src/rsa-oaep.ts +167 -0
  77. package/src/rsa-pss.ts +190 -0
  78. package/src/scrypt.spec.ts +90 -0
  79. package/src/scrypt.ts +191 -0
  80. package/src/sign.spec.ts +160 -0
  81. package/src/sign.ts +319 -0
  82. package/src/test.mts +19 -0
  83. package/src/timing-safe-equal.ts +21 -0
  84. package/src/x509.spec.ts +210 -0
  85. package/src/x509.ts +262 -0
  86. package/tsconfig.json +31 -0
  87. package/tsconfig.tsbuildinfo +1 -0
package/src/cipher.ts ADDED
@@ -0,0 +1,952 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Implements AES-128/192/256 (CBC, CTR, ECB, CFB, OFB, GCM) per FIPS-197 (Rijndael) with PKCS#7 padding
3
+ // GCM mode implements NIST SP 800-38D (Galois/Counter Mode)
4
+ // Adapted from browserify-cipher (refs/browserify-cipher/)
5
+ // Copyright (c) crypto-browserify contributors. MIT license.
6
+ // Modifications: Pure-JS implementation for GJS, no OpenSSL dependency
7
+
8
+ import { Buffer } from 'node:buffer';
9
+
10
+ // ---- AES S-Box and inverse S-Box (pre-computed) ----
11
+
12
+ const SBOX = new Uint8Array([
13
+ 0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
14
+ 0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
15
+ 0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
16
+ 0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
17
+ 0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
18
+ 0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
19
+ 0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
20
+ 0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
21
+ 0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
22
+ 0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
23
+ 0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
24
+ 0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
25
+ 0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
26
+ 0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
27
+ 0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
28
+ 0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16,
29
+ ]);
30
+
31
+ const INV_SBOX = new Uint8Array([
32
+ 0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb,
33
+ 0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb,
34
+ 0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e,
35
+ 0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25,
36
+ 0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92,
37
+ 0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84,
38
+ 0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06,
39
+ 0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b,
40
+ 0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73,
41
+ 0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e,
42
+ 0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b,
43
+ 0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4,
44
+ 0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f,
45
+ 0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef,
46
+ 0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61,
47
+ 0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d,
48
+ ]);
49
+
50
+ const RCON = [0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36];
51
+
52
+ // ---- GF(2^8) multiplication ----
53
+
54
+ function gmul(a: number, b: number): number {
55
+ let p = 0;
56
+ for (let i = 0; i < 8; i++) {
57
+ if (b & 1) p ^= a;
58
+ const hi = a & 0x80;
59
+ a = (a << 1) & 0xff;
60
+ if (hi) a ^= 0x1b;
61
+ b >>= 1;
62
+ }
63
+ return p;
64
+ }
65
+
66
+ // ---- AES Key Expansion ----
67
+
68
+ function keyExpansion(key: Uint8Array): Uint8Array[] {
69
+ const nk = key.length / 4; // 4, 6, or 8 (128, 192, 256 bits)
70
+ const nr = nk + 6; // 10, 12, or 14 rounds
71
+ const nw = 4 * (nr + 1); // total 32-bit words
72
+
73
+ const w = new Array<Uint8Array>(nw);
74
+ for (let i = 0; i < nk; i++) {
75
+ w[i] = new Uint8Array([key[4*i], key[4*i+1], key[4*i+2], key[4*i+3]]);
76
+ }
77
+
78
+ for (let i = nk; i < nw; i++) {
79
+ let temp = new Uint8Array(w[i-1]);
80
+ if (i % nk === 0) {
81
+ // RotWord + SubWord + Rcon
82
+ temp = new Uint8Array([
83
+ SBOX[temp[1]] ^ RCON[(i/nk) - 1],
84
+ SBOX[temp[2]],
85
+ SBOX[temp[3]],
86
+ SBOX[temp[0]],
87
+ ]);
88
+ } else if (nk > 6 && i % nk === 4) {
89
+ temp = new Uint8Array([SBOX[temp[0]], SBOX[temp[1]], SBOX[temp[2]], SBOX[temp[3]]]);
90
+ }
91
+ w[i] = new Uint8Array(4);
92
+ for (let j = 0; j < 4; j++) w[i][j] = w[i-nk][j] ^ temp[j];
93
+ }
94
+
95
+ // Convert to round keys (16 bytes each)
96
+ const roundKeys: Uint8Array[] = [];
97
+ for (let r = 0; r <= nr; r++) {
98
+ const rk = new Uint8Array(16);
99
+ for (let c = 0; c < 4; c++) {
100
+ rk[4*c] = w[4*r + c][0];
101
+ rk[4*c+1] = w[4*r + c][1];
102
+ rk[4*c+2] = w[4*r + c][2];
103
+ rk[4*c+3] = w[4*r + c][3];
104
+ }
105
+ roundKeys.push(rk);
106
+ }
107
+ return roundKeys;
108
+ }
109
+
110
+ // ---- AES Block Encrypt (16 bytes) ----
111
+
112
+ function aesEncryptBlock(block: Uint8Array, roundKeys: Uint8Array[]): Uint8Array {
113
+ const state = new Uint8Array(block);
114
+ const nr = roundKeys.length - 1;
115
+
116
+ // AddRoundKey (initial)
117
+ for (let i = 0; i < 16; i++) state[i] ^= roundKeys[0][i];
118
+
119
+ for (let round = 1; round < nr; round++) {
120
+ // SubBytes
121
+ for (let i = 0; i < 16; i++) state[i] = SBOX[state[i]];
122
+
123
+ // ShiftRows
124
+ const t1 = state[1]; state[1] = state[5]; state[5] = state[9]; state[9] = state[13]; state[13] = t1;
125
+ const t2a = state[2]; const t2b = state[6]; state[2] = state[10]; state[6] = state[14]; state[10] = t2a; state[14] = t2b;
126
+ const t3 = state[15]; state[15] = state[11]; state[11] = state[7]; state[7] = state[3]; state[3] = t3;
127
+
128
+ // MixColumns
129
+ for (let c = 0; c < 4; c++) {
130
+ const i = c * 4;
131
+ const a0 = state[i], a1 = state[i+1], a2 = state[i+2], a3 = state[i+3];
132
+ state[i] = gmul(2,a0) ^ gmul(3,a1) ^ a2 ^ a3;
133
+ state[i+1] = a0 ^ gmul(2,a1) ^ gmul(3,a2) ^ a3;
134
+ state[i+2] = a0 ^ a1 ^ gmul(2,a2) ^ gmul(3,a3);
135
+ state[i+3] = gmul(3,a0) ^ a1 ^ a2 ^ gmul(2,a3);
136
+ }
137
+
138
+ // AddRoundKey
139
+ for (let i = 0; i < 16; i++) state[i] ^= roundKeys[round][i];
140
+ }
141
+
142
+ // Final round (no MixColumns)
143
+ for (let i = 0; i < 16; i++) state[i] = SBOX[state[i]];
144
+ const t1f = state[1]; state[1] = state[5]; state[5] = state[9]; state[9] = state[13]; state[13] = t1f;
145
+ const t2af = state[2]; const t2bf = state[6]; state[2] = state[10]; state[6] = state[14]; state[10] = t2af; state[14] = t2bf;
146
+ const t3f = state[15]; state[15] = state[11]; state[11] = state[7]; state[7] = state[3]; state[3] = t3f;
147
+ for (let i = 0; i < 16; i++) state[i] ^= roundKeys[nr][i];
148
+
149
+ return state;
150
+ }
151
+
152
+ // ---- AES Block Decrypt (16 bytes) ----
153
+
154
+ function aesDecryptBlock(block: Uint8Array, roundKeys: Uint8Array[]): Uint8Array {
155
+ const state = new Uint8Array(block);
156
+ const nr = roundKeys.length - 1;
157
+
158
+ // AddRoundKey (last round key)
159
+ for (let i = 0; i < 16; i++) state[i] ^= roundKeys[nr][i];
160
+
161
+ for (let round = nr - 1; round > 0; round--) {
162
+ // InvShiftRows
163
+ const t1 = state[13]; state[13] = state[9]; state[9] = state[5]; state[5] = state[1]; state[1] = t1;
164
+ const t2a = state[10]; const t2b = state[14]; state[10] = state[2]; state[14] = state[6]; state[2] = t2a; state[6] = t2b;
165
+ const t3 = state[3]; state[3] = state[7]; state[7] = state[11]; state[11] = state[15]; state[15] = t3;
166
+
167
+ // InvSubBytes
168
+ for (let i = 0; i < 16; i++) state[i] = INV_SBOX[state[i]];
169
+
170
+ // AddRoundKey
171
+ for (let i = 0; i < 16; i++) state[i] ^= roundKeys[round][i];
172
+
173
+ // InvMixColumns
174
+ for (let c = 0; c < 4; c++) {
175
+ const i = c * 4;
176
+ const a0 = state[i], a1 = state[i+1], a2 = state[i+2], a3 = state[i+3];
177
+ state[i] = gmul(14,a0) ^ gmul(11,a1) ^ gmul(13,a2) ^ gmul(9,a3);
178
+ state[i+1] = gmul(9,a0) ^ gmul(14,a1) ^ gmul(11,a2) ^ gmul(13,a3);
179
+ state[i+2] = gmul(13,a0) ^ gmul(9,a1) ^ gmul(14,a2) ^ gmul(11,a3);
180
+ state[i+3] = gmul(11,a0) ^ gmul(13,a1) ^ gmul(9,a2) ^ gmul(14,a3);
181
+ }
182
+ }
183
+
184
+ // Final inverse round (no InvMixColumns)
185
+ const t1f = state[13]; state[13] = state[9]; state[9] = state[5]; state[5] = state[1]; state[1] = t1f;
186
+ const t2af = state[10]; const t2bf = state[14]; state[10] = state[2]; state[14] = state[6]; state[2] = t2af; state[6] = t2bf;
187
+ const t3f = state[3]; state[3] = state[7]; state[7] = state[11]; state[11] = state[15]; state[15] = t3f;
188
+ for (let i = 0; i < 16; i++) state[i] = INV_SBOX[state[i]];
189
+ for (let i = 0; i < 16; i++) state[i] ^= roundKeys[0][i];
190
+
191
+ return state;
192
+ }
193
+
194
+ // ---- Counter increment for CTR mode ----
195
+
196
+ function incrementCounter(counter: Uint8Array): void {
197
+ for (let i = 15; i >= 0; i--) {
198
+ if (++counter[i] !== 0) break;
199
+ }
200
+ }
201
+
202
+ // ---- GCM counter increment (only the last 32 bits) ----
203
+
204
+ function gcmIncrementCounter(counter: Uint8Array): void {
205
+ for (let i = 15; i >= 12; i--) {
206
+ if (++counter[i] !== 0) break;
207
+ }
208
+ }
209
+
210
+ // ---- GF(2^128) multiplication for GHASH ----
211
+
212
+ /**
213
+ * Multiply two 128-bit values in GF(2^128) using the irreducible polynomial
214
+ * x^128 + x^7 + x^2 + x + 1 (represented as R = 0xe1 << 120).
215
+ *
216
+ * X and Y are 16-byte Uint8Arrays (big-endian bit ordering).
217
+ * Returns a new 16-byte Uint8Array.
218
+ */
219
+ function gfMul(X: Uint8Array, Y: Uint8Array): Uint8Array {
220
+ // Z starts at 0, V starts as a copy of X
221
+ const Z = new Uint8Array(16);
222
+ const V = new Uint8Array(X);
223
+
224
+ for (let i = 0; i < 128; i++) {
225
+ // Check bit i of Y (big-endian: byte i>>3, bit 7-(i&7))
226
+ if (Y[i >>> 3] & (1 << (7 - (i & 7)))) {
227
+ // Z = Z XOR V
228
+ for (let j = 0; j < 16; j++) Z[j] ^= V[j];
229
+ }
230
+
231
+ // Check if the LSB (rightmost bit) of V is set
232
+ const lsb = V[15] & 1;
233
+
234
+ // Right-shift V by 1 bit
235
+ for (let j = 15; j > 0; j--) {
236
+ V[j] = (V[j] >>> 1) | ((V[j - 1] & 1) << 7);
237
+ }
238
+ V[0] = V[0] >>> 1;
239
+
240
+ // If LSB was set, XOR with R (0xe1 in the most significant byte)
241
+ if (lsb) {
242
+ V[0] ^= 0xe1;
243
+ }
244
+ }
245
+
246
+ return Z;
247
+ }
248
+
249
+ /**
250
+ * GHASH function per NIST SP 800-38D.
251
+ *
252
+ * H: the hash subkey (AES_K(0^128)), 16 bytes
253
+ * aad: additional authenticated data (arbitrary length)
254
+ * ciphertext: ciphertext (arbitrary length)
255
+ *
256
+ * Returns a 16-byte authentication hash.
257
+ */
258
+ function ghash(H: Uint8Array, aad: Uint8Array, ciphertext: Uint8Array): Uint8Array {
259
+ const X = new Uint8Array(16); // X_0 = 0^128
260
+
261
+ // Process AAD blocks (pad to 128-bit boundary)
262
+ const aadBlocks = Math.ceil(aad.length / 16) || 0;
263
+ for (let i = 0; i < aadBlocks; i++) {
264
+ const start = i * 16;
265
+ const end = Math.min(start + 16, aad.length);
266
+ // XOR the block into X (zero-padded if partial)
267
+ for (let j = 0; j < 16; j++) {
268
+ const idx = start + j;
269
+ if (idx < end) {
270
+ X[j] ^= aad[idx];
271
+ }
272
+ // else: XOR with 0 (no-op)
273
+ }
274
+ const product = gfMul(X, H);
275
+ X.set(product);
276
+ }
277
+
278
+ // Process ciphertext blocks (pad to 128-bit boundary)
279
+ const ctBlocks = Math.ceil(ciphertext.length / 16) || 0;
280
+ for (let i = 0; i < ctBlocks; i++) {
281
+ const start = i * 16;
282
+ const end = Math.min(start + 16, ciphertext.length);
283
+ for (let j = 0; j < 16; j++) {
284
+ const idx = start + j;
285
+ if (idx < end) {
286
+ X[j] ^= ciphertext[idx];
287
+ }
288
+ }
289
+ const product = gfMul(X, H);
290
+ X.set(product);
291
+ }
292
+
293
+ // Final block: len(A) || len(C) as 64-bit big-endian bit counts
294
+ const lenBlock = new Uint8Array(16);
295
+ const aadBits = aad.length * 8;
296
+ const ctBits = ciphertext.length * 8;
297
+
298
+ // Write aadBits as 64-bit big-endian into bytes 0..7
299
+ // JavaScript bitwise ops are 32-bit, so we handle high and low 32 bits
300
+ const aadHi = Math.floor(aadBits / 0x100000000);
301
+ const aadLo = aadBits >>> 0;
302
+ lenBlock[0] = (aadHi >>> 24) & 0xff;
303
+ lenBlock[1] = (aadHi >>> 16) & 0xff;
304
+ lenBlock[2] = (aadHi >>> 8) & 0xff;
305
+ lenBlock[3] = aadHi & 0xff;
306
+ lenBlock[4] = (aadLo >>> 24) & 0xff;
307
+ lenBlock[5] = (aadLo >>> 16) & 0xff;
308
+ lenBlock[6] = (aadLo >>> 8) & 0xff;
309
+ lenBlock[7] = aadLo & 0xff;
310
+
311
+ // Write ctBits as 64-bit big-endian into bytes 8..15
312
+ const ctHi = Math.floor(ctBits / 0x100000000);
313
+ const ctLo = ctBits >>> 0;
314
+ lenBlock[8] = (ctHi >>> 24) & 0xff;
315
+ lenBlock[9] = (ctHi >>> 16) & 0xff;
316
+ lenBlock[10] = (ctHi >>> 8) & 0xff;
317
+ lenBlock[11] = ctHi & 0xff;
318
+ lenBlock[12] = (ctLo >>> 24) & 0xff;
319
+ lenBlock[13] = (ctLo >>> 16) & 0xff;
320
+ lenBlock[14] = (ctLo >>> 8) & 0xff;
321
+ lenBlock[15] = ctLo & 0xff;
322
+
323
+ for (let j = 0; j < 16; j++) X[j] ^= lenBlock[j];
324
+ const product = gfMul(X, H);
325
+ X.set(product);
326
+
327
+ return X;
328
+ }
329
+
330
+ // ---- Algorithm parsing ----
331
+
332
+ interface AlgorithmInfo {
333
+ keySize: number; // bytes
334
+ ivSize: number; // bytes
335
+ mode: 'cbc' | 'ctr' | 'ecb' | 'cfb' | 'ofb' | 'gcm';
336
+ }
337
+
338
+ function parseAlgorithm(algorithm: string): AlgorithmInfo {
339
+ const lower = algorithm.toLowerCase();
340
+ const match = lower.match(/^aes-(128|192|256)-(cbc|ctr|ecb|cfb|ofb|gcm)$/);
341
+ if (!match) {
342
+ throw new Error(`Unsupported cipher algorithm: ${algorithm}`);
343
+ }
344
+ const keyBits = parseInt(match[1]);
345
+ const mode = match[2] as AlgorithmInfo['mode'];
346
+ return {
347
+ keySize: keyBits / 8,
348
+ ivSize: mode === 'ecb' ? 0 : (mode === 'gcm' ? 12 : 16),
349
+ mode,
350
+ };
351
+ }
352
+
353
+ // ---- Encoding helpers ----
354
+
355
+ function toBuffer(data: string | Buffer | Uint8Array, encoding?: string): Buffer {
356
+ if (typeof data === 'string') {
357
+ return Buffer.from(data, (encoding || 'utf8') as BufferEncoding);
358
+ }
359
+ return Buffer.from(data);
360
+ }
361
+
362
+ function encodeOutput(data: Uint8Array, encoding?: string): string | Buffer {
363
+ if (!encoding) return Buffer.from(data);
364
+ return Buffer.from(data).toString(encoding as BufferEncoding);
365
+ }
366
+
367
+ /**
368
+ * Count how many trailing bytes at the end of a Uint8Array form an incomplete
369
+ * UTF-8 multibyte sequence. Returns 0 if the last character is complete.
370
+ */
371
+ function incompleteUtf8Tail(buf: Uint8Array): number {
372
+ if (buf.length === 0) return 0;
373
+ // Walk backwards from the end to find the lead byte of the last character
374
+ const end = buf.length;
375
+ for (let back = 1; back <= Math.min(4, end); back++) {
376
+ const b = buf[end - back];
377
+ if ((b & 0x80) === 0) {
378
+ // ASCII byte — this is a complete 1-byte character
379
+ return 0;
380
+ }
381
+ if ((b & 0xC0) === 0x80) {
382
+ // Continuation byte — keep searching backwards for the lead byte
383
+ continue;
384
+ }
385
+ // This is a lead byte — determine expected sequence length
386
+ let expected: number;
387
+ if ((b & 0xE0) === 0xC0) expected = 2;
388
+ else if ((b & 0xF0) === 0xE0) expected = 3;
389
+ else if ((b & 0xF8) === 0xF0) expected = 4;
390
+ else return 0; // Invalid lead byte
391
+ // `back` is how many bytes we have from the lead to the end
392
+ return back < expected ? back : 0;
393
+ }
394
+ return 0;
395
+ }
396
+
397
+ // ---- PKCS#7 Padding ----
398
+
399
+ function pkcs7Pad(data: Uint8Array): Uint8Array {
400
+ const padLen = 16 - (data.length % 16);
401
+ const padded = new Uint8Array(data.length + padLen);
402
+ padded.set(data);
403
+ for (let i = data.length; i < padded.length; i++) padded[i] = padLen;
404
+ return padded;
405
+ }
406
+
407
+ function pkcs7Unpad(data: Uint8Array): Uint8Array {
408
+ if (data.length === 0 || data.length % 16 !== 0) {
409
+ throw new Error('bad decrypt');
410
+ }
411
+ const padLen = data[data.length - 1];
412
+ if (padLen === 0 || padLen > 16) {
413
+ throw new Error('bad decrypt');
414
+ }
415
+ for (let i = data.length - padLen; i < data.length; i++) {
416
+ if (data[i] !== padLen) throw new Error('bad decrypt');
417
+ }
418
+ return new Uint8Array(data.slice(0, data.length - padLen));
419
+ }
420
+
421
+ // ---- Cipher class ----
422
+
423
+ class CipherBase {
424
+ protected _roundKeys: Uint8Array[];
425
+ protected _iv: Uint8Array;
426
+ protected _mode: AlgorithmInfo['mode'];
427
+ protected _buffer: Uint8Array = new Uint8Array(0);
428
+ protected _autoPadding = true;
429
+ protected _finalized = false;
430
+
431
+ constructor(algorithm: string, key: Uint8Array, iv: Uint8Array | null) {
432
+ const info = parseAlgorithm(algorithm);
433
+ if (key.length !== info.keySize) {
434
+ throw new Error(`Invalid key length ${key.length}, expected ${info.keySize} for ${algorithm}`);
435
+ }
436
+ if (info.ivSize > 0 && (!iv || iv.length !== info.ivSize)) {
437
+ throw new Error(`Invalid IV length ${iv?.length ?? 0}, expected ${info.ivSize} for ${algorithm}`);
438
+ }
439
+ this._roundKeys = keyExpansion(key);
440
+ this._iv = iv ? new Uint8Array(iv) : new Uint8Array(16);
441
+ this._mode = info.mode;
442
+ }
443
+
444
+ setAutoPadding(autoPadding: boolean): this {
445
+ this._autoPadding = autoPadding;
446
+ return this;
447
+ }
448
+ }
449
+
450
+ class Cipher extends CipherBase {
451
+ private _prevBlock: Uint8Array;
452
+ private _counter: Uint8Array;
453
+
454
+ // GCM state
455
+ private _gcmH: Uint8Array | null = null; // Hash subkey H = AES_K(0^128)
456
+ private _gcmJ0: Uint8Array | null = null; // Initial counter J0
457
+ private _gcmAAD: Uint8Array = new Uint8Array(0); // Additional authenticated data
458
+ private _gcmCiphertext: Uint8Array[] = []; // Accumulated ciphertext for GHASH
459
+ private _gcmCiphertextLen = 0; // Total ciphertext length
460
+ private _gcmAuthTag: Buffer | null = null; // Computed authentication tag
461
+ private _gcmAADSet = false; // Whether setAAD was called
462
+
463
+ constructor(algorithm: string, key: Uint8Array, iv: Uint8Array | null) {
464
+ super(algorithm, key, iv);
465
+ this._prevBlock = new Uint8Array(this._iv);
466
+
467
+ if (this._mode === 'gcm') {
468
+ // GCM initialization
469
+ // H = AES_K(0^128) — encrypt zero block with the key
470
+ this._gcmH = aesEncryptBlock(new Uint8Array(16), this._roundKeys);
471
+
472
+ // J0 = IV || 0^31 || 1 (when IV is 96 bits / 12 bytes)
473
+ this._gcmJ0 = new Uint8Array(16);
474
+ this._gcmJ0.set(this._iv.subarray(0, 12));
475
+ this._gcmJ0[15] = 1; // last byte = 1 (0^31 || 1)
476
+
477
+ // Counter starts at J0 incremented by 1 (ICB = inc32(J0))
478
+ this._counter = new Uint8Array(this._gcmJ0);
479
+ gcmIncrementCounter(this._counter);
480
+ } else {
481
+ this._counter = new Uint8Array(this._iv);
482
+ }
483
+ }
484
+
485
+ /**
486
+ * Set Additional Authenticated Data for GCM mode.
487
+ * Must be called before any update() calls.
488
+ */
489
+ setAAD(data: Buffer | Uint8Array): this {
490
+ if (this._mode !== 'gcm') {
491
+ throw new Error('setAAD is only supported in GCM mode');
492
+ }
493
+ if (this._gcmCiphertextLen > 0) {
494
+ throw new Error('setAAD must be called before update()');
495
+ }
496
+ this._gcmAAD = new Uint8Array(data);
497
+ this._gcmAADSet = true;
498
+ return this;
499
+ }
500
+
501
+ /**
502
+ * Get the authentication tag after final() has been called.
503
+ * Only valid for GCM mode.
504
+ */
505
+ getAuthTag(): Buffer {
506
+ if (this._mode !== 'gcm') {
507
+ throw new Error('getAuthTag is only supported in GCM mode');
508
+ }
509
+ if (!this._gcmAuthTag) {
510
+ throw new Error('getAuthTag must be called after final()');
511
+ }
512
+ return Buffer.from(this._gcmAuthTag);
513
+ }
514
+
515
+ update(data: string | Buffer | Uint8Array, inputEncoding?: string, outputEncoding?: string): string | Buffer {
516
+ const input = toBuffer(data, inputEncoding);
517
+
518
+ // Append to buffer
519
+ const combined = new Uint8Array(this._buffer.length + input.length);
520
+ combined.set(this._buffer);
521
+ combined.set(input, this._buffer.length);
522
+
523
+ if (this._mode === 'gcm') {
524
+ // GCM uses CTR mode for encryption — process all available bytes
525
+ const output = this._processGcmEncrypt(combined);
526
+ this._buffer = new Uint8Array(0);
527
+ return encodeOutput(output, outputEncoding);
528
+ }
529
+
530
+ if (this._mode === 'ctr' || this._mode === 'cfb' || this._mode === 'ofb') {
531
+ // Stream cipher modes: process all available bytes
532
+ const output = this._processStream(combined);
533
+ this._buffer = new Uint8Array(0);
534
+ return encodeOutput(output, outputEncoding);
535
+ }
536
+
537
+ // Block cipher modes (CBC, ECB): process complete blocks
538
+ const fullBlocks = Math.floor(combined.length / 16);
539
+ const processLen = fullBlocks * 16;
540
+ const output: Uint8Array[] = [];
541
+
542
+ for (let i = 0; i < processLen; i += 16) {
543
+ const block = combined.slice(i, i + 16);
544
+ output.push(this._encryptBlock(block));
545
+ }
546
+
547
+ this._buffer = combined.slice(processLen);
548
+ const result = new Uint8Array(output.length * 16);
549
+ for (let i = 0; i < output.length; i++) result.set(output[i], i * 16);
550
+ return encodeOutput(result, outputEncoding);
551
+ }
552
+
553
+ final(outputEncoding?: string): string | Buffer {
554
+ if (this._finalized) throw new Error('Cipher already finalized');
555
+ this._finalized = true;
556
+
557
+ if (this._mode === 'gcm') {
558
+ // GCM: process any remaining buffer, then compute auth tag
559
+ let finalOutput = new Uint8Array(0);
560
+ if (this._buffer.length > 0) {
561
+ finalOutput = this._processGcmEncrypt(this._buffer) as Uint8Array<ArrayBuffer>;
562
+ this._buffer = new Uint8Array(0);
563
+ }
564
+
565
+ // Concatenate all ciphertext chunks for GHASH
566
+ const allCiphertext = new Uint8Array(this._gcmCiphertextLen);
567
+ let offset = 0;
568
+ for (const chunk of this._gcmCiphertext) {
569
+ allCiphertext.set(chunk, offset);
570
+ offset += chunk.length;
571
+ }
572
+
573
+ // Compute GHASH(H, AAD, ciphertext)
574
+ const ghashResult = ghash(this._gcmH!, this._gcmAAD, allCiphertext);
575
+
576
+ // Tag = GHASH(H, AAD, C) XOR AES_K(J0)
577
+ const encJ0 = aesEncryptBlock(this._gcmJ0!, this._roundKeys);
578
+ const tag = new Uint8Array(16);
579
+ for (let i = 0; i < 16; i++) tag[i] = ghashResult[i] ^ encJ0[i];
580
+
581
+ this._gcmAuthTag = Buffer.from(tag);
582
+
583
+ return encodeOutput(finalOutput, outputEncoding);
584
+ }
585
+
586
+ if (this._mode === 'ctr' || this._mode === 'cfb' || this._mode === 'ofb') {
587
+ // Stream modes: no padding needed, just process remaining
588
+ if (this._buffer.length > 0) {
589
+ const output = this._processStream(this._buffer);
590
+ this._buffer = new Uint8Array(0);
591
+ return encodeOutput(output, outputEncoding);
592
+ }
593
+ return encodeOutput(new Uint8Array(0), outputEncoding);
594
+ }
595
+
596
+ // Block modes: apply padding
597
+ let data = this._buffer;
598
+ if (this._autoPadding) {
599
+ data = pkcs7Pad(data);
600
+ } else if (data.length % 16 !== 0) {
601
+ throw new Error('data not multiple of block size');
602
+ }
603
+
604
+ const output: Uint8Array[] = [];
605
+ for (let i = 0; i < data.length; i += 16) {
606
+ output.push(this._encryptBlock(data.slice(i, i + 16)));
607
+ }
608
+
609
+ this._buffer = new Uint8Array(0);
610
+ if (output.length === 0) return encodeOutput(new Uint8Array(0), outputEncoding);
611
+ const result = new Uint8Array(output.length * 16);
612
+ for (let i = 0; i < output.length; i++) result.set(output[i], i * 16);
613
+ return encodeOutput(result, outputEncoding);
614
+ }
615
+
616
+ private _encryptBlock(block: Uint8Array): Uint8Array {
617
+ if (this._mode === 'cbc') {
618
+ // XOR with previous ciphertext (or IV)
619
+ const xored = new Uint8Array(16);
620
+ for (let i = 0; i < 16; i++) xored[i] = block[i] ^ this._prevBlock[i];
621
+ const encrypted = aesEncryptBlock(xored, this._roundKeys);
622
+ this._prevBlock = encrypted;
623
+ return encrypted;
624
+ } else if (this._mode === 'ecb') {
625
+ return aesEncryptBlock(block, this._roundKeys);
626
+ }
627
+ throw new Error(`Block encryption not supported for mode: ${this._mode}`);
628
+ }
629
+
630
+ private _processStream(data: Uint8Array): Uint8Array {
631
+ const output = new Uint8Array(data.length);
632
+ for (let i = 0; i < data.length; i += 16) {
633
+ const keystream = aesEncryptBlock(this._counter, this._roundKeys);
634
+ const remaining = Math.min(16, data.length - i);
635
+ for (let j = 0; j < remaining; j++) {
636
+ output[i + j] = data[i + j] ^ keystream[j];
637
+ }
638
+ incrementCounter(this._counter);
639
+ }
640
+ return output;
641
+ }
642
+
643
+ /**
644
+ * GCM encryption: CTR mode encryption, also accumulates ciphertext for GHASH.
645
+ */
646
+ private _processGcmEncrypt(data: Uint8Array): Uint8Array {
647
+ const output = new Uint8Array(data.length);
648
+ for (let i = 0; i < data.length; i += 16) {
649
+ const keystream = aesEncryptBlock(this._counter, this._roundKeys);
650
+ const remaining = Math.min(16, data.length - i);
651
+ for (let j = 0; j < remaining; j++) {
652
+ output[i + j] = data[i + j] ^ keystream[j];
653
+ }
654
+ gcmIncrementCounter(this._counter);
655
+ }
656
+ // Accumulate ciphertext for auth tag computation
657
+ this._gcmCiphertext.push(new Uint8Array(output));
658
+ this._gcmCiphertextLen += output.length;
659
+ return output;
660
+ }
661
+ }
662
+
663
+ class Decipher extends CipherBase {
664
+ private _prevBlock: Uint8Array;
665
+ private _counter: Uint8Array;
666
+ private _pendingUtf8: Uint8Array = new Uint8Array(0);
667
+
668
+ // GCM state
669
+ private _gcmH: Uint8Array | null = null; // Hash subkey H = AES_K(0^128)
670
+ private _gcmJ0: Uint8Array | null = null; // Initial counter J0
671
+ private _gcmAAD: Uint8Array = new Uint8Array(0); // Additional authenticated data
672
+ private _gcmCiphertext: Uint8Array[] = []; // Accumulated ciphertext for GHASH
673
+ private _gcmCiphertextLen = 0; // Total ciphertext length
674
+ private _gcmExpectedTag: Buffer | null = null; // Expected authentication tag
675
+ private _gcmAADSet = false; // Whether setAAD was called
676
+
677
+ constructor(algorithm: string, key: Uint8Array, iv: Uint8Array | null) {
678
+ super(algorithm, key, iv);
679
+ this._prevBlock = new Uint8Array(this._iv);
680
+
681
+ if (this._mode === 'gcm') {
682
+ // GCM initialization (same as Cipher)
683
+ this._gcmH = aesEncryptBlock(new Uint8Array(16), this._roundKeys);
684
+
685
+ this._gcmJ0 = new Uint8Array(16);
686
+ this._gcmJ0.set(this._iv.subarray(0, 12));
687
+ this._gcmJ0[15] = 1;
688
+
689
+ this._counter = new Uint8Array(this._gcmJ0);
690
+ gcmIncrementCounter(this._counter);
691
+ } else {
692
+ this._counter = new Uint8Array(this._iv);
693
+ }
694
+ }
695
+
696
+ /**
697
+ * Set Additional Authenticated Data for GCM mode.
698
+ * Must be called before any update() calls.
699
+ */
700
+ setAAD(data: Buffer | Uint8Array): this {
701
+ if (this._mode !== 'gcm') {
702
+ throw new Error('setAAD is only supported in GCM mode');
703
+ }
704
+ if (this._gcmCiphertextLen > 0) {
705
+ throw new Error('setAAD must be called before update()');
706
+ }
707
+ this._gcmAAD = new Uint8Array(data);
708
+ this._gcmAADSet = true;
709
+ return this;
710
+ }
711
+
712
+ /**
713
+ * Set the expected authentication tag for GCM decryption.
714
+ * Must be called before final().
715
+ */
716
+ setAuthTag(tag: Buffer | Uint8Array): this {
717
+ if (this._mode !== 'gcm') {
718
+ throw new Error('setAuthTag is only supported in GCM mode');
719
+ }
720
+ this._gcmExpectedTag = Buffer.from(tag);
721
+ return this;
722
+ }
723
+
724
+ private _encodeWithUtf8Handling(bytes: Uint8Array, encoding: string | undefined, isFinal: boolean): string | Buffer {
725
+ if (!encoding || (encoding !== 'utf8' && encoding !== 'utf-8')) {
726
+ return encodeOutput(bytes, encoding);
727
+ }
728
+
729
+ // Prepend any leftover bytes from previous call
730
+ let data: Uint8Array;
731
+ if (this._pendingUtf8.length > 0) {
732
+ data = new Uint8Array(this._pendingUtf8.length + bytes.length);
733
+ data.set(this._pendingUtf8);
734
+ data.set(bytes, this._pendingUtf8.length);
735
+ this._pendingUtf8 = new Uint8Array(0);
736
+ } else {
737
+ data = bytes;
738
+ }
739
+
740
+ if (!isFinal) {
741
+ // Check for incomplete UTF-8 at the end
742
+ const tail = incompleteUtf8Tail(data);
743
+ if (tail > 0) {
744
+ this._pendingUtf8 = new Uint8Array(data.slice(data.length - tail));
745
+ data = new Uint8Array(data.slice(0, data.length - tail));
746
+ }
747
+ }
748
+
749
+ return Buffer.from(data).toString('utf8');
750
+ }
751
+
752
+ update(data: string | Buffer | Uint8Array, inputEncoding?: string, outputEncoding?: string): string | Buffer {
753
+ const input = toBuffer(data, inputEncoding);
754
+
755
+ const combined = new Uint8Array(this._buffer.length + input.length);
756
+ combined.set(this._buffer);
757
+ combined.set(input, this._buffer.length);
758
+
759
+ if (this._mode === 'gcm') {
760
+ // GCM uses CTR mode for decryption — process all available bytes
761
+ // Accumulate ciphertext BEFORE decryption (for GHASH)
762
+ this._gcmCiphertext.push(new Uint8Array(combined));
763
+ this._gcmCiphertextLen += combined.length;
764
+ const output = this._processGcmDecrypt(combined);
765
+ this._buffer = new Uint8Array(0);
766
+ return this._encodeWithUtf8Handling(output, outputEncoding, false);
767
+ }
768
+
769
+ if (this._mode === 'ctr' || this._mode === 'cfb' || this._mode === 'ofb') {
770
+ const output = this._processStream(combined);
771
+ this._buffer = new Uint8Array(0);
772
+ return this._encodeWithUtf8Handling(output, outputEncoding, false);
773
+ }
774
+
775
+ // Block cipher modes: need to keep last block for padding check in final()
776
+ const fullBlocks = Math.floor(combined.length / 16);
777
+ if (fullBlocks === 0) {
778
+ this._buffer = combined;
779
+ return this._encodeWithUtf8Handling(new Uint8Array(0), outputEncoding, false);
780
+ }
781
+
782
+ // Keep last block in buffer for padding removal in final()
783
+ const processBlocks = this._autoPadding ? fullBlocks - 1 : fullBlocks;
784
+ const processLen = processBlocks * 16;
785
+ const output: Uint8Array[] = [];
786
+
787
+ for (let i = 0; i < processLen; i += 16) {
788
+ const block = combined.slice(i, i + 16);
789
+ output.push(this._decryptBlock(block));
790
+ }
791
+
792
+ this._buffer = combined.slice(processLen);
793
+ const result = new Uint8Array(output.length * 16);
794
+ for (let i = 0; i < output.length; i++) result.set(output[i], i * 16);
795
+ return this._encodeWithUtf8Handling(result, outputEncoding, false);
796
+ }
797
+
798
+ final(outputEncoding?: string): string | Buffer {
799
+ if (this._finalized) throw new Error('Decipher already finalized');
800
+ this._finalized = true;
801
+
802
+ if (this._mode === 'gcm') {
803
+ // GCM: process any remaining buffer, then verify auth tag
804
+ let finalOutput = new Uint8Array(0);
805
+ if (this._buffer.length > 0) {
806
+ // Accumulate remaining ciphertext for GHASH
807
+ this._gcmCiphertext.push(new Uint8Array(this._buffer));
808
+ this._gcmCiphertextLen += this._buffer.length;
809
+ finalOutput = this._processGcmDecrypt(this._buffer) as Uint8Array<ArrayBuffer>;
810
+ this._buffer = new Uint8Array(0);
811
+ }
812
+
813
+ // Verify the authentication tag
814
+ if (!this._gcmExpectedTag) {
815
+ throw new Error('Unsupported state or unable to authenticate data');
816
+ }
817
+
818
+ // Concatenate all ciphertext chunks for GHASH
819
+ const allCiphertext = new Uint8Array(this._gcmCiphertextLen);
820
+ let offset = 0;
821
+ for (const chunk of this._gcmCiphertext) {
822
+ allCiphertext.set(chunk, offset);
823
+ offset += chunk.length;
824
+ }
825
+
826
+ // Compute GHASH(H, AAD, ciphertext)
827
+ const ghashResult = ghash(this._gcmH!, this._gcmAAD, allCiphertext);
828
+
829
+ // Tag = GHASH(H, AAD, C) XOR AES_K(J0)
830
+ const encJ0 = aesEncryptBlock(this._gcmJ0!, this._roundKeys);
831
+ const computedTag = new Uint8Array(16);
832
+ for (let i = 0; i < 16; i++) computedTag[i] = ghashResult[i] ^ encJ0[i];
833
+
834
+ // Compare tags (constant-time comparison)
835
+ const expectedTag = this._gcmExpectedTag;
836
+ const tagLen = Math.min(expectedTag.length, 16);
837
+ let diff = 0;
838
+ for (let i = 0; i < tagLen; i++) {
839
+ diff |= computedTag[i] ^ expectedTag[i];
840
+ }
841
+ if (diff !== 0) {
842
+ throw new Error('Unsupported state or unable to authenticate data');
843
+ }
844
+
845
+ return this._encodeWithUtf8Handling(finalOutput, outputEncoding, true);
846
+ }
847
+
848
+ if (this._mode === 'ctr' || this._mode === 'cfb' || this._mode === 'ofb') {
849
+ if (this._buffer.length > 0) {
850
+ const output = this._processStream(this._buffer);
851
+ this._buffer = new Uint8Array(0);
852
+ return this._encodeWithUtf8Handling(output, outputEncoding, true);
853
+ }
854
+ return this._encodeWithUtf8Handling(new Uint8Array(0), outputEncoding, true);
855
+ }
856
+
857
+ if (this._buffer.length === 0) {
858
+ return this._encodeWithUtf8Handling(new Uint8Array(0), outputEncoding, true);
859
+ }
860
+
861
+ if (this._buffer.length % 16 !== 0) {
862
+ throw new Error('bad decrypt');
863
+ }
864
+
865
+ // Decrypt remaining blocks
866
+ const output: Uint8Array[] = [];
867
+ for (let i = 0; i < this._buffer.length; i += 16) {
868
+ output.push(this._decryptBlock(this._buffer.slice(i, i + 16)));
869
+ }
870
+
871
+ const combined = new Uint8Array(output.length * 16);
872
+ for (let i = 0; i < output.length; i++) combined.set(output[i], i * 16);
873
+
874
+ const result = this._autoPadding ? pkcs7Unpad(combined) : combined;
875
+
876
+ this._buffer = new Uint8Array(0);
877
+ return this._encodeWithUtf8Handling(result, outputEncoding, true);
878
+ }
879
+
880
+ private _decryptBlock(block: Uint8Array): Uint8Array {
881
+ if (this._mode === 'cbc') {
882
+ const decrypted = aesDecryptBlock(block, this._roundKeys);
883
+ const output = new Uint8Array(16);
884
+ for (let i = 0; i < 16; i++) output[i] = decrypted[i] ^ this._prevBlock[i];
885
+ this._prevBlock = new Uint8Array(block);
886
+ return output;
887
+ } else if (this._mode === 'ecb') {
888
+ return aesDecryptBlock(block, this._roundKeys);
889
+ }
890
+ throw new Error(`Block decryption not supported for mode: ${this._mode}`);
891
+ }
892
+
893
+ private _processStream(data: Uint8Array): Uint8Array {
894
+ const output = new Uint8Array(data.length);
895
+ for (let i = 0; i < data.length; i += 16) {
896
+ const keystream = aesEncryptBlock(this._counter, this._roundKeys);
897
+ const remaining = Math.min(16, data.length - i);
898
+ for (let j = 0; j < remaining; j++) {
899
+ output[i + j] = data[i + j] ^ keystream[j];
900
+ }
901
+ incrementCounter(this._counter);
902
+ }
903
+ return output;
904
+ }
905
+
906
+ /**
907
+ * GCM decryption: CTR mode decryption (same as encryption, since CTR is symmetric).
908
+ */
909
+ private _processGcmDecrypt(data: Uint8Array): Uint8Array {
910
+ const output = new Uint8Array(data.length);
911
+ for (let i = 0; i < data.length; i += 16) {
912
+ const keystream = aesEncryptBlock(this._counter, this._roundKeys);
913
+ const remaining = Math.min(16, data.length - i);
914
+ for (let j = 0; j < remaining; j++) {
915
+ output[i + j] = data[i + j] ^ keystream[j];
916
+ }
917
+ gcmIncrementCounter(this._counter);
918
+ }
919
+ return output;
920
+ }
921
+ }
922
+
923
+ // ---- Public API ----
924
+
925
+ export function createCipher(_algorithm: string, _password: string | Buffer | Uint8Array): never {
926
+ throw new Error('crypto.createCipher() is deprecated. Use createCipheriv() instead.');
927
+ }
928
+
929
+ export function createCipheriv(algorithm: string, key: string | Buffer | Uint8Array, iv: string | Buffer | Uint8Array | null): Cipher {
930
+ const keyBuf = typeof key === 'string' ? Buffer.from(key) : new Uint8Array(key);
931
+ const ivBuf = iv == null ? null : (typeof iv === 'string' ? Buffer.from(iv) : new Uint8Array(iv));
932
+ return new Cipher(algorithm, keyBuf, ivBuf);
933
+ }
934
+
935
+ export function createDecipher(_algorithm: string, _password: string | Buffer | Uint8Array): never {
936
+ throw new Error('crypto.createDecipher() is deprecated. Use createDecipheriv() instead.');
937
+ }
938
+
939
+ export function createDecipheriv(algorithm: string, key: string | Buffer | Uint8Array, iv: string | Buffer | Uint8Array | null): Decipher {
940
+ const keyBuf = typeof key === 'string' ? Buffer.from(key) : new Uint8Array(key);
941
+ const ivBuf = iv == null ? null : (typeof iv === 'string' ? Buffer.from(iv) : new Uint8Array(iv));
942
+ return new Decipher(algorithm, keyBuf, ivBuf);
943
+ }
944
+
945
+ export function getCiphers(): string[] {
946
+ return [
947
+ 'aes-128-cbc', 'aes-128-ecb', 'aes-192-cbc', 'aes-192-ecb',
948
+ 'aes-256-cbc', 'aes-256-ecb', 'aes-128-ctr', 'aes-192-ctr', 'aes-256-ctr',
949
+ 'aes-128-cfb', 'aes-192-cfb', 'aes-256-cfb',
950
+ 'aes-128-gcm', 'aes-192-gcm', 'aes-256-gcm',
951
+ ];
952
+ }