@1auth/crypto 0.0.0-alpha.71 → 0.0.0-alpha.73

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 (2) hide show
  1. package/index.js +464 -429
  2. package/package.json +54 -54
package/index.js CHANGED
@@ -1,16 +1,16 @@
1
- import { promisify } from "node:util";
2
1
  import {
3
- randomBytes,
4
- randomInt,
5
- createHash,
6
- createCipheriv,
7
- createDecipheriv,
8
- createHmac,
9
- timingSafeEqual,
10
- generateKeyPair as generateKeyPairCallback,
11
- sign as signCallback,
12
- verify as verifyCallback,
2
+ createCipheriv,
3
+ createDecipheriv,
4
+ createHash,
5
+ createHmac,
6
+ generateKeyPair as generateKeyPairCallback,
7
+ randomBytes,
8
+ randomInt,
9
+ sign as signCallback,
10
+ timingSafeEqual,
11
+ verify as verifyCallback,
13
12
  } from "node:crypto";
13
+ import { promisify } from "node:util";
14
14
  // https://github.com/napi-rs/node-rs/tree/main/packages/argon2
15
15
  import { hash as secretHash, verify as secretVerify } from "@node-rs/argon2";
16
16
  import { customAlphabet } from "nanoid";
@@ -20,63 +20,94 @@ const sign = promisify(signCallback);
20
20
  const verify = promisify(verifyCallback);
21
21
 
22
22
  const defaults = {
23
- symmetricEncryptionKey: undefined, // symmetricRandomEncryptionKey()
24
- symmetricEncryptionAlgorithm: "chacha20-poly1305", // 2025-03: AES-256 GCM (aes-256-gcm) or ChaCha20-Poly1305 (chacha20-poly1305)
25
- symmetricEncryptionEncoding: undefined, // https://nodejs.org/api/buffer.html#buffers-and-character-encodings
26
- symmetricSignatureHashAlgorithm: undefined, // fallback to defaultHashAlgorithm
27
- symmetricSignatureSecret: undefined, // symmetricRandomSignatureSecret()
28
- symmetricSignatureEncoding: undefined, // fallback to defaultEncoding
29
- asymmetricKeyNamedCurve: "P-384", // P-512
30
- asymmetricSignatureHashAlgorithm: undefined, // fallback to defaultHashAlgorithm
31
- asymmetricSignatureEncoding: undefined, // fallback to defaultEncoding
32
- digestChecksumHashAlgorithm: undefined, // fallback to defaultHashAlgorithm
33
- digestChecksumEncoding: undefined,
34
- digestChecksumSalt: undefined, // randomChecksumSalt()
35
- digestChecksumPepper: undefined, // randomChecksumPepper()
36
- defaultEncoding: "base64",
37
- defaultHashAlgorithm: "sha3-384",
23
+ symmetricEncryptionKey: undefined, // symmetricRandomEncryptionKey()
24
+ symmetricEncryptionAlgorithm: "chacha20-poly1305", // 2025-03: AES-256 GCM (aes-256-gcm) or ChaCha20-Poly1305 (chacha20-poly1305)
25
+ symmetricEncryptionEncoding: undefined, // https://nodejs.org/api/buffer.html#buffers-and-character-encodings
26
+ symmetricSignatureHashAlgorithm: undefined, // fallback to defaultHashAlgorithm
27
+ symmetricSignatureSecret: undefined, // symmetricRandomSignatureSecret()
28
+ symmetricSignatureEncoding: undefined, // fallback to defaultEncoding
29
+ asymmetricKeyNamedCurve: "P-384", // P-512
30
+ asymmetricSignatureHashAlgorithm: undefined, // fallback to defaultHashAlgorithm
31
+ asymmetricSignatureEncoding: undefined, // fallback to defaultEncoding
32
+ digestChecksumHashAlgorithm: undefined, // fallback to defaultHashAlgorithm
33
+ digestChecksumEncoding: undefined,
34
+ digestChecksumSalt: undefined, // randomChecksumSalt()
35
+ digestChecksumPepper: undefined, // randomChecksumPepper()
36
+ secretHashAlgorithm: "argon2id",
37
+ secretTimeCost: 3, // argon2id
38
+ secretMemoryCost: 2 ** 15, // argon2id
39
+ secretParallelism: 1, // argon2id
40
+ defaultEncoding: "base64",
41
+ defaultHashAlgorithm: "sha3-384",
38
42
  };
39
43
  const symmetricEncryptionEncodingLengths = {};
40
44
  const options = {};
41
45
  export default (opt = {}) => {
42
- Object.assign(options, defaults, opt);
43
-
44
- // Check options, set defaults
45
- if (!options.symmetricEncryptionKey) {
46
- throw new Error(
47
- "@1auth/crypto symmetricEncryptionKey is empty, use a stored secret made from randomBytes(32) Encryption disabled.",
48
- );
49
- }
50
- options.symmetricEncryptionEncoding ??= options.defaultEncoding;
51
- options.symmetricSignatureHashAlgorithm ??= options.defaultHashAlgorithm;
52
- if (!options.symmetricSignatureSecret) {
53
- throw new Error(
54
- "@1auth/crypto symmetricSignatureSecret is empty, use a stored secret made from randomBytes(32) Signature disabled.",
55
- );
56
- }
57
- options.symmetricSignatureEncoding ??= options.defaultEncoding;
58
- options.asymmetricSignatureHashAlgorithm ??= options.defaultHashAlgorithm;
59
- options.asymmetricSignatureEncoding ??= options.defaultEncoding;
60
- if (!options.digestChecksumSalt) {
61
- throw new Error(
62
- "@1auth/crypto digestChecksumSalt is empty, use a stored secret made from randomBytes(32) Checksum salting disabled.",
63
- );
64
- }
65
- if (!options.digestChecksumPepper) {
66
- throw new Error(
67
- "@1auth/crypto digestChecksumPepper is empty, use a stored secret made from randomBytes(12) Checksum peppering disabled.",
68
- );
69
- }
70
- options.digestChecksumHashAlgorithm ??= options.defaultHashAlgorithm;
71
- options.digestChecksumEncoding ??= options.defaultEncoding;
72
-
73
- // Lengths
74
- symmetricEncryptionEncodingLengths.iv = randomIV().toString(
75
- options.symmetricEncryptionEncoding,
76
- ).length;
77
- symmetricEncryptionEncodingLengths.ivAndAuthTag =
78
- symmetricEncryptionEncodingLengths.iv +
79
- randomBytes(16).toString(options.symmetricEncryptionEncoding).length;
46
+ Object.assign(options, defaults, opt);
47
+
48
+ // Check options, set defaults
49
+ if (!options.symmetricEncryptionKey) {
50
+ throw new Error(
51
+ "@1auth/crypto symmetricEncryptionKey is empty, use a stored secret made from randomBytes(32) Encryption disabled.",
52
+ );
53
+ }
54
+ options.symmetricEncryptionKey = makeOptionsBuffer(
55
+ options.symmetricEncryptionKey,
56
+ );
57
+ options.symmetricEncryptionEncoding ??= options.defaultEncoding;
58
+ options.symmetricSignatureHashAlgorithm ??= options.defaultHashAlgorithm;
59
+ if (!options.symmetricSignatureSecret) {
60
+ throw new Error(
61
+ "@1auth/crypto symmetricSignatureSecret is empty, use a stored secret made from randomBytes(32) Signature disabled.",
62
+ );
63
+ }
64
+ options.symmetricSignatureSecret = makeOptionsBuffer(
65
+ options.symmetricSignatureSecret,
66
+ );
67
+ options.symmetricSignatureEncoding ??= options.defaultEncoding;
68
+ options.asymmetricSignatureHashAlgorithm ??= options.defaultHashAlgorithm;
69
+ options.asymmetricSignatureEncoding ??= options.defaultEncoding;
70
+ if (!options.digestChecksumSalt) {
71
+ throw new Error(
72
+ "@1auth/crypto digestChecksumSalt is empty, use a stored secret made from randomBytes(32) Checksum salting disabled.",
73
+ );
74
+ }
75
+ options.digestChecksumSalt = makeOptionsBuffer(options.digestChecksumSalt);
76
+ if (!options.digestChecksumPepper) {
77
+ throw new Error(
78
+ "@1auth/crypto digestChecksumPepper is empty, use a stored secret made from randomBytes(12) Checksum peppering disabled.",
79
+ );
80
+ }
81
+ options.digestChecksumPepper = makeOptionsBuffer(
82
+ options.digestChecksumPepper,
83
+ );
84
+ options.digestChecksumHashAlgorithm ??= options.defaultHashAlgorithm;
85
+ options.digestChecksumEncoding ??= options.defaultEncoding;
86
+
87
+ // Secrets
88
+ Object.assign(hashOptions, {
89
+ timeCost: options.secretTimeCost,
90
+ memoryCost: options.secretMemoryCost,
91
+ parallelism: options.secretParallelism,
92
+ });
93
+
94
+ // Lengths
95
+ symmetricEncryptionEncodingLengths.iv = randomIV().toString(
96
+ options.symmetricEncryptionEncoding,
97
+ ).length;
98
+ symmetricEncryptionEncodingLengths.ivAndAuthTag =
99
+ symmetricEncryptionEncodingLengths.iv +
100
+ randomBytes(16).toString(options.symmetricEncryptionEncoding).length;
101
+ };
102
+
103
+ export const makeOptionsBuffer = (
104
+ value,
105
+ encoding = options.defaultEncoding,
106
+ ) => {
107
+ if (typeof value === "string") {
108
+ return Buffer.from(value, encoding);
109
+ }
110
+ return value;
80
111
  };
81
112
 
82
113
  export const getOptions = () => options;
@@ -100,8 +131,8 @@ export const getOptions = () => options;
100
131
  // *** Helpers *** //
101
132
  // Ref: https://therootcompany.com/blog/how-many-bits-of-entropy-per-character/
102
133
  export const entropyToCharacterLength = (bits, characterPoolSize) => {
103
- // bits*ln(2)/ln(characterPoolSize)
104
- return Math.ceil((bits * Math.LN2) / Math.log(characterPoolSize));
134
+ // bits*ln(2)/ln(characterPoolSize)
135
+ return Math.ceil((bits * Math.LN2) / Math.log(characterPoolSize));
105
136
  };
106
137
  /* export const characterLengthToEntropy = (
107
138
  characterLength,
@@ -122,494 +153,498 @@ export const charactersAlphaNumeric = charactersAlpha + charactersNumeric;
122
153
  export const charactersDistinguishable = "CDEHKMPRTUWXY012458";
123
154
 
124
155
  const randomCharactersCache = {
125
- charactersAlphaNumeric: customAlphabet(charactersAlphaNumeric),
156
+ charactersAlphaNumeric: customAlphabet(charactersAlphaNumeric),
126
157
  };
127
158
  export const randomCharacters = (
128
- length,
129
- characters = charactersAlphaNumeric,
159
+ length,
160
+ characters = charactersAlphaNumeric,
130
161
  ) => {
131
- randomCharactersCache[characters] ??= customAlphabet(characters);
132
- return randomCharactersCache[characters](length);
162
+ randomCharactersCache[characters] ??= customAlphabet(characters);
163
+ return randomCharactersCache[characters](length);
133
164
  };
134
165
 
135
166
  export const randomAlphaNumeric = (characterLength) => {
136
- return randomCharacters(characterLength, charactersAlphaNumeric);
167
+ return randomCharacters(characterLength, charactersAlphaNumeric);
137
168
  };
138
169
 
139
170
  export const randomNumeric = (characterLength) => {
140
- let value = "";
141
- for (let i = characterLength; i--; ) {
142
- value += randomInt(9);
143
- }
144
- return value;
171
+ let value = "";
172
+ for (let i = characterLength; i--; ) {
173
+ value += randomInt(9);
174
+ }
175
+ return value;
145
176
  };
146
177
 
147
178
  // *** configs *** //
148
179
  // Input: {id, prefix, entropy, characters, opt, expire}
149
180
  // Output: {id, type, opt, expire, create, ...}
150
181
  export const makeRandomConfigObject = ({
151
- id,
152
- prefix = "",
153
- entropy = 64,
154
- characters = charactersAlphaNumeric,
155
- ...params
182
+ id,
183
+ prefix = "",
184
+ entropy = 64,
185
+ characters = charactersAlphaNumeric,
186
+ ...params
156
187
  } = {}) => {
157
- const minLength = entropyToCharacterLength(entropy, characters.length);
158
- const config = {
159
- id,
160
- type: "id",
161
- create: () => prefix + randomCharacters(minLength, characters),
162
- ...params,
163
- };
164
- return config;
188
+ const minLength = entropyToCharacterLength(entropy, characters.length);
189
+ const config = {
190
+ id,
191
+ type: "id",
192
+ create: () => prefix + randomCharacters(minLength, characters),
193
+ ...params,
194
+ };
195
+ return config;
165
196
  };
166
197
 
167
198
  // *** Digests *** //
168
199
  export const randomChecksumSalt = () => {
169
- return randomBytes(32); // 256 bits
200
+ return randomBytes(32); // 256 bits
170
201
  };
171
202
  export const randomChecksumPepper = () => {
172
- return randomIV(); // 96
203
+ return randomIV(); // 96
173
204
  };
174
205
 
175
206
  export const createSaltedValue = (value, { checksumSalt } = {}) => {
176
- checksumSalt ??= options.digestChecksumSalt;
177
- if (!checksumSalt) {
178
- return value;
179
- }
180
- const newValue = value + checksumSalt;
181
- return newValue;
207
+ checksumSalt ??= options.digestChecksumSalt;
208
+ if (!checksumSalt) {
209
+ return value;
210
+ }
211
+ const newValue = value + checksumSalt;
212
+ return newValue;
182
213
  };
183
214
  export const createPepperedValue = (
184
- value,
185
- { checksumPepper, encryptionKey } = {},
215
+ value,
216
+ { checksumPepper, encryptionKey } = {},
186
217
  ) => {
187
- checksumPepper ??= options.digestChecksumPepper;
188
- encryptionKey ??= options.symmetricEncryptionKey;
189
- if (!checksumPepper || !encryptionKey) {
190
- return value;
191
- }
192
- const newValue = symmetricEncrypt(value, {
193
- encryptionKey,
194
- sub: "",
195
- iv: checksumPepper,
196
- });
197
- return newValue;
218
+ checksumPepper ??= options.digestChecksumPepper;
219
+ encryptionKey ??= options.symmetricEncryptionKey;
220
+ if (!checksumPepper || !encryptionKey) {
221
+ return value;
222
+ }
223
+ const newValue = symmetricEncrypt(value, {
224
+ encryptionKey,
225
+ sub: "",
226
+ iv: checksumPepper,
227
+ });
228
+ return newValue;
198
229
  };
199
230
 
200
231
  export const createChecksum = (value, { hashAlgorithm, encoding } = {}) => {
201
- hashAlgorithm ??= options.digestChecksumHashAlgorithm;
202
- encoding ??= options.digestChecksumEncoding;
203
- return createHash(hashAlgorithm).update(value).digest(encoding);
232
+ hashAlgorithm ??= options.digestChecksumHashAlgorithm;
233
+ encoding ??= options.digestChecksumEncoding;
234
+ return createHash(hashAlgorithm).update(value).digest(encoding);
204
235
  };
205
236
  export const createSeasonedChecksum = (
206
- value,
207
- { hashAlgorithm, encoding, checksumSalt, checksumPepper } = {},
237
+ value,
238
+ { hashAlgorithm, encoding, checksumSalt, checksumPepper } = {},
208
239
  ) => {
209
- return createChecksum(
210
- createPepperedValue(createSaltedValue(value, { checksumSalt }), {
211
- checksumPepper,
212
- }),
213
- {
214
- hashAlgorithm,
215
- encoding,
216
- },
217
- );
240
+ return createChecksum(
241
+ createPepperedValue(createSaltedValue(value, { checksumSalt }), {
242
+ checksumPepper,
243
+ }),
244
+ {
245
+ hashAlgorithm,
246
+ encoding,
247
+ },
248
+ );
218
249
  };
219
250
 
220
251
  export const createDigest = (value, { hashAlgorithm, encoding } = {}) => {
221
- hashAlgorithm ??= options.digestChecksumHashAlgorithm;
222
- const checksum = createChecksum(value, { hashAlgorithm, encoding });
223
- return `${hashAlgorithm}:${checksum}`;
252
+ hashAlgorithm ??= options.digestChecksumHashAlgorithm;
253
+ const checksum = createChecksum(value, { hashAlgorithm, encoding });
254
+ return `${hashAlgorithm}:${checksum}`;
224
255
  };
225
256
  export const createSaltedDigest = (
226
- value,
227
- { hashAlgorithm, encoding, checksumSalt } = {},
257
+ value,
258
+ { hashAlgorithm, encoding, checksumSalt } = {},
228
259
  ) => {
229
- hashAlgorithm ??= options.digestChecksumHashAlgorithm;
230
- const checksum = createChecksum(createSaltedValue(value, { checksumSalt }), {
231
- hashAlgorithm,
232
- encoding,
233
- });
234
- return `${hashAlgorithm}:${checksum}`;
260
+ hashAlgorithm ??= options.digestChecksumHashAlgorithm;
261
+ const checksum = createChecksum(createSaltedValue(value, { checksumSalt }), {
262
+ hashAlgorithm,
263
+ encoding,
264
+ });
265
+ return `${hashAlgorithm}:${checksum}`;
235
266
  };
236
267
  export const createPepperedDigest = (
237
- value,
238
- { hashAlgorithm, encoding, checksumPepper, encryptionKey } = {},
268
+ value,
269
+ { hashAlgorithm, encoding, checksumPepper, encryptionKey } = {},
239
270
  ) => {
240
- hashAlgorithm ??= options.digestChecksumHashAlgorithm;
241
- const checksum = createChecksum(
242
- createPepperedValue(value, { checksumPepper, encryptionKey }),
243
- {
244
- hashAlgorithm,
245
- encoding,
246
- },
247
- );
248
- return `${hashAlgorithm}:${checksum}`;
271
+ hashAlgorithm ??= options.digestChecksumHashAlgorithm;
272
+ const checksum = createChecksum(
273
+ createPepperedValue(value, { checksumPepper, encryptionKey }),
274
+ {
275
+ hashAlgorithm,
276
+ encoding,
277
+ },
278
+ );
279
+ return `${hashAlgorithm}:${checksum}`;
249
280
  };
250
281
  export const createSeasonedDigest = (
251
- value,
252
- { hashAlgorithm, encoding, checksumSalt, checksumPepper, encryptionKey } = {},
282
+ value,
283
+ { hashAlgorithm, encoding, checksumSalt, checksumPepper, encryptionKey } = {},
253
284
  ) => {
254
- hashAlgorithm ??= options.digestChecksumHashAlgorithm;
255
- const checksum = createSeasonedChecksum(value, {
256
- hashAlgorithm,
257
- encoding,
258
- checksumSalt,
259
- checksumPepper,
260
- encryptionKey,
261
- });
262
- return `${hashAlgorithm}:${checksum}`;
285
+ hashAlgorithm ??= options.digestChecksumHashAlgorithm;
286
+ const checksum = createSeasonedChecksum(value, {
287
+ hashAlgorithm,
288
+ encoding,
289
+ checksumSalt,
290
+ checksumPepper,
291
+ encryptionKey,
292
+ });
293
+ return `${hashAlgorithm}:${checksum}`;
263
294
  };
264
295
 
265
296
  // *** Hashing *** //
266
297
  const hashOptions = {
267
- timeCost: 3, // Default 3
268
- memoryCost: 2 ** 15, // Default 2 ** 12 = 4MB
269
- saltLength: 16,
270
- parallelism: 1, // Default 1
271
- outputLen: 64, // hashLength: 128 // Default 32
272
- algorithm: 2, // Default 2 = Argon2id
273
- version: 1, // Default 1 = version 19
298
+ timeCost: 3, // Default 3
299
+ memoryCost: 2 ** 15, // Default 2 ** 12 = 4MB
300
+ parallelism: 1, // Default 1
301
+ saltLength: 16,
302
+ outputLen: 64, // hashLength: 128 // Default 32
303
+ algorithm: 2, // Default 2 = Argon2id
304
+ version: 1, // Default 1 = version 19
274
305
  };
275
306
 
276
307
  export const createSecretHash = async (value, options = hashOptions) => {
277
- return secretHash(value, options);
308
+ return secretHash(value, options);
278
309
  };
279
310
 
280
311
  export const verifySecretHash = async (hash, value) => {
281
- return secretVerify(hash, value);
312
+ return secretVerify(hash, value);
282
313
  };
283
314
 
284
315
  // *** Symmetric Encryption *** //
285
316
  const authTagLength = 16;
286
317
 
287
318
  export const symmetricRandomEncryptionKey = () => {
288
- return randomBytes(32); // 256 bits
319
+ return randomBytes(32); // 256 bits
289
320
  };
290
321
 
291
322
  export const randomIV = () => {
292
- return randomBytes(12); // 96 bits
323
+ return randomBytes(12); // 96 bits
293
324
  };
294
325
 
295
326
  export const symmetricGenerateEncryptionKey = (
296
- sub,
297
- { encryptionKey, signatureSecret } = {},
327
+ sub,
328
+ { encryptionKey, signatureSecret } = {},
298
329
  ) => {
299
- encryptionKey ??= options.symmetricEncryptionKey;
300
- signatureSecret ??= options.symemeticSignatureSecret;
330
+ encryptionKey ??= options.symmetricEncryptionKey;
331
+ signatureSecret ??= options.symemeticSignatureSecret;
301
332
 
302
- const rowEncryptionKey = symmetricRandomEncryptionKey();
303
- const rowEncryptedKey = symmetricEncrypt(rowEncryptionKey, {
304
- encryptionKey,
305
- signatureSecret,
306
- sub,
307
- });
308
- return { encryptionKey: rowEncryptionKey, encryptedKey: rowEncryptedKey };
333
+ const rowEncryptionKey = symmetricRandomEncryptionKey();
334
+ const rowEncryptedKey = symmetricEncrypt(rowEncryptionKey, {
335
+ encryptionKey,
336
+ signatureSecret,
337
+ sub,
338
+ });
339
+ return { encryptionKey: rowEncryptionKey, encryptedKey: rowEncryptedKey };
309
340
  };
310
341
 
311
342
  // sub add context to encryption
312
343
  export const symmetricEncryptFields = (
313
- values,
314
- { encryptedKey, encryptionKey, signatureSecret, sub },
315
- fields = [],
344
+ values,
345
+ { encryptedKey, encryptionKey, signatureSecret, sub },
346
+ fields = [],
316
347
  ) => {
317
- if (encryptedKey) {
318
- encryptionKey ??= symmetricDecryptKey(encryptedKey, {
319
- signatureSecret,
320
- sub,
321
- });
322
- }
323
- if (!encryptionKey) return values;
324
- const encryptedValues = structuredClone(values);
325
- for (const key of fields) {
326
- encryptedValues[key] &&= symmetricEncrypt(encryptedValues[key], {
327
- encryptionKey,
328
- signatureSecret,
329
- sub,
330
- });
331
- }
332
- return encryptedValues;
348
+ if (encryptedKey) {
349
+ encryptionKey ??= symmetricDecryptKey(encryptedKey, {
350
+ signatureSecret,
351
+ sub,
352
+ });
353
+ }
354
+ if (!encryptionKey) return values;
355
+ const encryptedValues = structuredClone(values);
356
+ for (const key of fields) {
357
+ encryptedValues[key] &&= symmetricEncrypt(encryptedValues[key], {
358
+ encryptionKey,
359
+ signatureSecret,
360
+ sub,
361
+ });
362
+ }
363
+ return encryptedValues;
333
364
  };
334
365
 
335
366
  export const symmetricEncrypt = (
336
- data,
337
- { encryptedKey, encryptionKey, signatureSecret, sub, decoding, encoding, iv },
367
+ data,
368
+ { encryptedKey, encryptionKey, signatureSecret, sub, decoding, encoding, iv },
338
369
  ) => {
339
- if (encryptedKey) {
340
- encryptionKey ??= symmetricDecryptKey(encryptedKey, {
341
- signatureSecret,
342
- sub,
343
- });
344
- }
345
- if (!encryptionKey || !data) return data;
346
- decoding ??= "utf8";
347
- encoding ??= options.symmetricEncryptionEncoding;
348
- iv ??= randomIV();
349
- const cipher = createCipheriv(
350
- options.symmetricEncryptionAlgorithm,
351
- encryptionKey,
352
- iv,
353
- {
354
- authTagLength,
355
- },
356
- );
357
- cipher.setAAD(sub);
358
- const encryptedData =
359
- cipher.update(data, decoding, encoding) + cipher.final(encoding);
360
- const authTag = cipher.getAuthTag();
361
-
362
- const encryptedDataPacket =
363
- iv.toString(encoding) + authTag.toString(encoding) + encryptedData;
364
-
365
- // add signature to end
366
- return symmetricSignatureSign(encryptedDataPacket, { signatureSecret });
370
+ if (encryptedKey) {
371
+ encryptionKey ??= symmetricDecryptKey(encryptedKey, {
372
+ signatureSecret,
373
+ sub,
374
+ });
375
+ }
376
+ if (!encryptionKey || !data) return data;
377
+ decoding ??= "utf8";
378
+ encoding ??= options.symmetricEncryptionEncoding;
379
+ iv ??= randomIV();
380
+ const cipher = createCipheriv(
381
+ options.symmetricEncryptionAlgorithm,
382
+ encryptionKey,
383
+ iv,
384
+ {
385
+ authTagLength,
386
+ },
387
+ );
388
+ cipher.setAAD(sub);
389
+ const encryptedData =
390
+ cipher.update(data, decoding, encoding) + cipher.final(encoding);
391
+ const authTag = cipher.getAuthTag();
392
+
393
+ const encryptedDataPacket =
394
+ iv.toString(encoding) + authTag.toString(encoding) + encryptedData;
395
+
396
+ // add signature to end
397
+ return symmetricSignatureSign(encryptedDataPacket, { signatureSecret });
367
398
  };
368
399
 
369
400
  export const symmetricDecryptFields = (
370
- encryptedValues,
371
- { encryptedKey, encryptionKey, signatureSecret, sub },
372
- fields = [],
401
+ encryptedValues,
402
+ { encryptedKey, encryptionKey, signatureSecret, sub },
403
+ fields = [],
373
404
  ) => {
374
- if (encryptedKey) {
375
- encryptionKey ??= symmetricDecryptKey(encryptedKey, {
376
- signatureSecret,
377
- sub,
378
- });
379
- }
380
- if (!encryptionKey) return encryptedValues;
381
- const values = structuredClone(encryptedValues);
382
- for (const key of fields) {
383
- values[key] &&= symmetricDecrypt(values[key], {
384
- encryptionKey,
385
- signatureSecret,
386
- sub,
387
- });
388
- }
389
- return values;
405
+ if (encryptedKey) {
406
+ encryptionKey ??= symmetricDecryptKey(encryptedKey, {
407
+ signatureSecret,
408
+ sub,
409
+ });
410
+ }
411
+ if (!encryptionKey) return encryptedValues;
412
+ const values = structuredClone(encryptedValues);
413
+ for (const key of fields) {
414
+ values[key] &&= symmetricDecrypt(values[key], {
415
+ encryptionKey,
416
+ signatureSecret,
417
+ sub,
418
+ });
419
+ }
420
+ return values;
390
421
  };
391
422
 
392
423
  export const symmetricDecryptKey = (
393
- encryptedKey,
394
- { sub, encryptionKey, signatureSecret } = {},
424
+ encryptedKey,
425
+ { sub, encryptionKey, signatureSecret } = {},
395
426
  ) => {
396
- encryptionKey ??= options.symmetricEncryptionKey;
397
- signatureSecret ??= options.symemeticSignatureSecret;
398
- return Buffer.from(
399
- symmetricDecrypt(encryptedKey, {
400
- encryptionKey,
401
- signatureSecret,
402
- sub,
403
- encoding: options.symmetricEncryptionEncoding,
404
- }),
405
- options.symmetricEncryptionEncoding,
406
- );
427
+ encryptionKey ??= options.symmetricEncryptionKey;
428
+ signatureSecret ??= options.symemeticSignatureSecret;
429
+ return Buffer.from(
430
+ symmetricDecrypt(encryptedKey, {
431
+ encryptionKey,
432
+ signatureSecret,
433
+ sub,
434
+ encoding: options.symmetricEncryptionEncoding,
435
+ }),
436
+ options.symmetricEncryptionEncoding,
437
+ );
407
438
  };
408
439
 
409
440
  export const symmetricDecrypt = (
410
- encryptedDataPacket,
411
- { encryptedKey, encryptionKey, signatureSecret, sub, decoding, encoding },
441
+ signedEncryptedDataPacket,
442
+ { encryptedKey, encryptionKey, signatureSecret, sub, decoding, encoding },
412
443
  ) => {
413
- if (encryptedKey) {
414
- encryptionKey ??= symmetricDecryptKey(encryptedKey, {
415
- signatureSecret,
416
- sub,
417
- });
418
- }
419
- if (!encryptionKey || !encryptedDataPacket) return encryptedDataPacket;
420
- decoding ??= options.symmetricEncryptionEncoding;
421
- encoding ??= "utf8";
422
-
423
- // remove signature when successful
424
- encryptedDataPacket = symmetricSignatureVerify(encryptedDataPacket, {
425
- signatureSecret,
426
- });
427
-
428
- if (encryptedDataPacket === false) {
429
- throw new Error("Signature incorrect");
430
- }
431
- const iv = Buffer.from(
432
- encryptedDataPacket.substring(0, symmetricEncryptionEncodingLengths.iv),
433
- decoding,
434
- );
435
- const authTag = Buffer.from(
436
- encryptedDataPacket.substring(
437
- symmetricEncryptionEncodingLengths.iv,
438
- symmetricEncryptionEncodingLengths.ivAndAuthTag,
439
- ),
440
- decoding,
441
- );
442
- const encryptedData = Buffer.from(
443
- encryptedDataPacket.substring(
444
- symmetricEncryptionEncodingLengths.ivAndAuthTag,
445
- ),
446
- decoding,
447
- );
448
-
449
- const decipher = createDecipheriv(
450
- options.symmetricEncryptionAlgorithm,
451
- encryptionKey,
452
- iv,
453
- {
454
- authTagLength,
455
- },
456
- );
457
- decipher.setAAD(sub);
458
-
459
- decipher.setAuthTag(authTag);
460
- const data =
461
- decipher.update(encryptedData, decoding, encoding) +
462
- decipher.final(encoding);
463
- return data;
444
+ if (encryptedKey) {
445
+ encryptionKey ??= symmetricDecryptKey(encryptedKey, {
446
+ signatureSecret,
447
+ sub,
448
+ });
449
+ }
450
+ if (!encryptionKey || !signedEncryptedDataPacket)
451
+ return signedEncryptedDataPacket;
452
+ decoding ??= options.symmetricEncryptionEncoding;
453
+ encoding ??= "utf8";
454
+
455
+ // remove signature when successful
456
+ const encryptedDataPacket = symmetricSignatureVerify(
457
+ signedEncryptedDataPacket,
458
+ {
459
+ signatureSecret,
460
+ },
461
+ );
462
+
463
+ if (encryptedDataPacket === false) {
464
+ throw new Error("Signature incorrect");
465
+ }
466
+ const iv = Buffer.from(
467
+ encryptedDataPacket.substring(0, symmetricEncryptionEncodingLengths.iv),
468
+ decoding,
469
+ );
470
+ const authTag = Buffer.from(
471
+ encryptedDataPacket.substring(
472
+ symmetricEncryptionEncodingLengths.iv,
473
+ symmetricEncryptionEncodingLengths.ivAndAuthTag,
474
+ ),
475
+ decoding,
476
+ );
477
+ const encryptedData = Buffer.from(
478
+ encryptedDataPacket.substring(
479
+ symmetricEncryptionEncodingLengths.ivAndAuthTag,
480
+ ),
481
+ decoding,
482
+ );
483
+
484
+ const decipher = createDecipheriv(
485
+ options.symmetricEncryptionAlgorithm,
486
+ encryptionKey,
487
+ iv,
488
+ {
489
+ authTagLength,
490
+ },
491
+ );
492
+ decipher.setAAD(sub);
493
+
494
+ decipher.setAuthTag(authTag);
495
+ const data =
496
+ decipher.update(encryptedData, decoding, encoding) +
497
+ decipher.final(encoding);
498
+ return data;
464
499
  };
465
500
 
466
501
  // *** Symmetric Signatures *** //
467
502
  export const symmetricRandomSignatureSecret = () => {
468
- return randomBytes(32); // 256 bits
503
+ return randomBytes(32); // 256 bits
469
504
  };
470
505
 
471
506
  export const symmetricGenerateSignatureSecret = () => {
472
- const signatureSecret = symmetricRandomSignatureSecret();
473
- return { signatureSecret };
507
+ const signatureSecret = symmetricRandomSignatureSecret();
508
+ return { signatureSecret };
474
509
  };
475
510
 
476
511
  export const symmetricSignatureSign = (
477
- data,
478
- { hashAlgorithm, signatureSecret } = {},
512
+ data,
513
+ { hashAlgorithm, signatureSecret } = {},
479
514
  ) => {
480
- signatureSecret ??= options.symmetricSignatureSecret;
481
- hashAlgorithm ??= options.symmetricSignatureHashAlgorithm;
482
- const signature = createHmac(hashAlgorithm, signatureSecret)
483
- .update(data)
484
- .digest(options.symmetricSignatureEncoding)
485
- .replace(/=+$/, "");
515
+ signatureSecret ??= options.symmetricSignatureSecret;
516
+ hashAlgorithm ??= options.symmetricSignatureHashAlgorithm;
517
+ const signature = createHmac(hashAlgorithm, signatureSecret)
518
+ .update(data)
519
+ .digest(options.symmetricSignatureEncoding)
520
+ .replace(/=+$/, "");
486
521
 
487
- const signedData = data + "." + signature;
488
- return signedData;
522
+ const signedData = `${data}.${signature}`;
523
+ return signedData;
489
524
  };
490
525
 
491
526
  export const symmetricSignatureVerify = (
492
- signedData,
493
- { hashAlgorithm, signatureSecret } = {},
527
+ signedData,
528
+ { hashAlgorithm, signatureSecret } = {},
494
529
  ) => {
495
- if (typeof signedData !== "string") return false;
496
- let lastIndexOf = signedData.lastIndexOf(".");
497
- // Test for unsigned
498
- if (lastIndexOf < 0) {
499
- lastIndexOf = signedData.length;
500
- }
501
- const data = signedData.substring(0, lastIndexOf);
502
- const signedDataExpected = symmetricSignatureSign(data, {
503
- hashAlgorithm,
504
- signatureSecret,
505
- });
506
- return safeEqual(signedData, signedDataExpected) && data;
530
+ if (typeof signedData !== "string") return false;
531
+ let lastIndexOf = signedData.lastIndexOf(".");
532
+ // Test for unsigned
533
+ if (lastIndexOf < 0) {
534
+ lastIndexOf = signedData.length;
535
+ }
536
+ const data = signedData.substring(0, lastIndexOf);
537
+ const signedDataExpected = symmetricSignatureSign(data, {
538
+ hashAlgorithm,
539
+ signatureSecret,
540
+ });
541
+ return safeEqual(signedData, signedDataExpected) && data;
507
542
  };
508
543
 
509
544
  // Allow rotation of global encryption key, global signature secret, and row encryption key
510
545
  export const symmetricRotation = (
511
- oldEncryptedValues,
512
- oldOptions, // { encryptionKey, signatureSecret, sub, decoding, encoding }, // old
513
- oldFields = [],
514
- newOptions, // { encryptionKey, signatureSecret, sub, decoding, encoding }, // new
515
- newFields,
516
- transform = (data) => {
517
- return data;
518
- },
546
+ oldEncryptedValues,
547
+ oldOptions, // { encryptionKey, signatureSecret, sub, decoding, encoding }, // old
548
+ oldFields,
549
+ newOptions, // { encryptionKey, signatureSecret, sub, decoding, encoding }, // new
550
+ newFields,
551
+ transform = (data) => {
552
+ return data;
553
+ },
519
554
  ) => {
520
- newOptions ??= oldOptions;
521
- newFields ??= oldFields;
522
-
523
- if (oldOptions.sub !== newOptions.sub) throw new Error("Mismatching `sub`");
524
-
525
- oldEncryptedValues = structuredClone(oldEncryptedValues);
526
- // Don't use structuredClone, converts Buffer to Uint8Array ...
527
- oldOptions = { ...oldOptions };
528
- newOptions = { ...newOptions };
529
-
530
- // decrypt old encryption key
531
- const { encryptionKey: oldEncryptedKey } = oldEncryptedValues;
532
- delete oldEncryptedValues.encryptionKey;
533
-
534
- const oldEncryptionKey = symmetricDecryptKey(oldEncryptedKey, oldOptions);
535
- oldOptions.encryptionKey = oldEncryptionKey;
536
-
537
- // decrypt
538
- const data = transform(
539
- symmetricDecryptFields(
540
- oldEncryptedValues,
541
- { ...oldOptions, encryptionKey: oldEncryptionKey },
542
- oldFields,
543
- ),
544
- );
545
-
546
- // rotate encryptionKey
547
- const { encryptionKey: newEncryptionKey, encryptedKey: newEncryptedKey } =
548
- symmetricGenerateEncryptionKey(newOptions.sub, newOptions);
549
- newOptions.encryptionKey = newEncryptionKey;
550
-
551
- // encrypt
552
- const newEncryptedValues = symmetricEncryptFields(
553
- data,
554
- newOptions,
555
- newFields,
556
- );
557
- newEncryptedValues.encryptionKey = newEncryptedKey;
558
-
559
- return newEncryptedValues;
555
+ if (oldOptions.sub !== newOptions.sub) throw new Error("Mismatching `sub`");
556
+
557
+ const oldEncryptedValuesClone = structuredClone(oldEncryptedValues);
558
+ // Don't use structuredClone, converts Buffer to Uint8Array ...
559
+ const oldOptionsClone = { ...oldOptions };
560
+ const newOptionsClone = { ...(newOptions ?? oldOptions) };
561
+
562
+ // decrypt old encryption key
563
+ const { encryptionKey: oldEncryptedKey } = oldEncryptedValuesClone;
564
+ oldEncryptedValuesClone.encryptionKey = undefined;
565
+
566
+ const oldEncryptionKey = symmetricDecryptKey(
567
+ oldEncryptedKey,
568
+ oldOptionsClone,
569
+ );
570
+ oldOptionsClone.encryptionKey = oldEncryptionKey;
571
+
572
+ // decrypt
573
+ const data = transform(
574
+ symmetricDecryptFields(
575
+ oldEncryptedValuesClone,
576
+ { ...oldOptionsClone, encryptionKey: oldEncryptionKey },
577
+ oldFields,
578
+ ),
579
+ );
580
+
581
+ // rotate encryptionKey
582
+ const { encryptionKey: newEncryptionKey, encryptedKey: newEncryptedKey } =
583
+ symmetricGenerateEncryptionKey(newOptionsClone.sub, newOptionsClone);
584
+ newOptionsClone.encryptionKey = newEncryptionKey;
585
+
586
+ // encrypt
587
+ const newEncryptedValues = symmetricEncryptFields(
588
+ data,
589
+ newOptionsClone,
590
+ newFields ?? oldFields,
591
+ );
592
+ newEncryptedValues.encryptionKey = newEncryptedKey;
593
+
594
+ return newEncryptedValues;
560
595
  };
561
596
 
562
597
  // *** Asymmetric Signatures *** //
563
598
  // asymmetricKeyPairType
564
599
  export const makeAsymmetricKeys = async () => {
565
- const { publicKey, privateKey } = await generateKeyPair("ec", {
566
- namedCurve: options.asymmetricKeyNamedCurve,
567
- paramEncoding: "named",
568
- publicKeyEncoding: {
569
- type: "spki",
570
- format: "pem",
571
- },
572
- privateKeyEncoding: {
573
- type: "sec1",
574
- format: "pem",
575
- // Encryption done at another level for consistency
576
- // cipher: options.asymmetricEncryptionAlgorithm,
577
- // passphrase: encryptionKey,
578
- },
579
- });
580
- return { publicKey, privateKey };
600
+ const { publicKey, privateKey } = await generateKeyPair("ec", {
601
+ namedCurve: options.asymmetricKeyNamedCurve,
602
+ paramEncoding: "named",
603
+ publicKeyEncoding: {
604
+ type: "spki",
605
+ format: "pem",
606
+ },
607
+ privateKeyEncoding: {
608
+ type: "sec1",
609
+ format: "pem",
610
+ // Encryption done at another level for consistency
611
+ // cipher: options.asymmetricEncryptionAlgorithm,
612
+ // passphrase: encryptionKey,
613
+ },
614
+ });
615
+ return { publicKey, privateKey };
581
616
  };
582
617
 
583
618
  export const makeAsymmetricSignature = async (
584
- data,
585
- privateKey,
586
- { hashAlgorithm } = {},
619
+ data,
620
+ privateKey,
621
+ { hashAlgorithm } = {},
587
622
  ) => {
588
- hashAlgorithm ??= options.asymmetricSignatureHashAlgorithm;
589
- return (await sign(hashAlgorithm, Buffer.from(data), privateKey)).toString(
590
- options.asymmetricSignatureEncoding,
591
- );
623
+ hashAlgorithm ??= options.asymmetricSignatureHashAlgorithm;
624
+ return (await sign(hashAlgorithm, Buffer.from(data), privateKey)).toString(
625
+ options.asymmetricSignatureEncoding,
626
+ );
592
627
  };
593
628
  export const verifyAsymmetricSignature = async (
594
- data,
595
- publicKey,
596
- signature,
597
- { hashAlgorithm } = {},
629
+ data,
630
+ publicKey,
631
+ signature,
632
+ { hashAlgorithm } = {},
598
633
  ) => {
599
- hashAlgorithm ??= options.asymmetricSignatureHashAlgorithm;
600
- return await verify(
601
- hashAlgorithm,
602
- Buffer.from(data),
603
- publicKey,
604
- Buffer.from(signature, options.asymmetricSignatureEncoding),
605
- );
634
+ hashAlgorithm ??= options.asymmetricSignatureHashAlgorithm;
635
+ return await verify(
636
+ hashAlgorithm,
637
+ Buffer.from(data),
638
+ publicKey,
639
+ Buffer.from(signature, options.asymmetricSignatureEncoding),
640
+ );
606
641
  };
607
642
 
608
643
  export const safeEqual = (input, expected) => {
609
- const bufferInput = Buffer.from(input);
610
- const bufferExpected = Buffer.from(expected);
611
- return (
612
- bufferInput.length === bufferExpected.length &&
613
- timingSafeEqual(bufferInput, bufferExpected)
614
- );
644
+ const bufferInput = Buffer.from(input);
645
+ const bufferExpected = Buffer.from(expected);
646
+ return (
647
+ bufferInput.length === bufferExpected.length &&
648
+ timingSafeEqual(bufferInput, bufferExpected)
649
+ );
615
650
  };
package/package.json CHANGED
@@ -1,56 +1,56 @@
1
1
  {
2
- "name": "@1auth/crypto",
3
- "version": "0.0.0-alpha.71",
4
- "description": "",
5
- "type": "module",
6
- "engines": {
7
- "node": ">=20"
8
- },
9
- "engineStrict": true,
10
- "publishConfig": {
11
- "access": "public"
12
- },
13
- "main": "./index.js",
14
- "module": "./index.js",
15
- "exports": {
16
- ".": {
17
- "import": {
18
- "types": "./index.d.ts",
19
- "default": "./index.js"
20
- }
21
- }
22
- },
23
- "types": "index.d.ts",
24
- "files": [
25
- "index.js",
26
- "index.d.ts"
27
- ],
28
- "scripts": {
29
- "test": "npm run test:unit",
30
- "test:unit": "node --test"
31
- },
32
- "license": "MIT",
33
- "funding": {
34
- "type": "github",
35
- "url": "https://github.com/sponsors/willfarrell"
36
- },
37
- "keywords": [],
38
- "author": {
39
- "name": "1auth contributors",
40
- "url": "https://github.com/willfarrell/1auth/graphs/contributors"
41
- },
42
- "repository": {
43
- "type": "git",
44
- "url": "git+https://github.com/willfarrell/1auth.git",
45
- "directory": "packages/crypto"
46
- },
47
- "bugs": {
48
- "url": "https://github.com/willfarrell/1auth/issues"
49
- },
50
- "homepage": "https://github.com/willfarrell/1auth",
51
- "gitHead": "7a6c0fbb8ab71d6a2171e678697de9f237568431",
52
- "dependencies": {
53
- "@node-rs/argon2": "2.0.2",
54
- "nanoid": "5.1.5"
55
- }
2
+ "name": "@1auth/crypto",
3
+ "version": "0.0.0-alpha.73",
4
+ "description": "",
5
+ "type": "module",
6
+ "engines": {
7
+ "node": ">=22"
8
+ },
9
+ "engineStrict": true,
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "main": "./index.js",
14
+ "module": "./index.js",
15
+ "exports": {
16
+ ".": {
17
+ "import": {
18
+ "types": "./index.d.ts",
19
+ "default": "./index.js"
20
+ }
21
+ }
22
+ },
23
+ "types": "index.d.ts",
24
+ "files": [
25
+ "index.js",
26
+ "index.d.ts"
27
+ ],
28
+ "scripts": {
29
+ "test": "npm run test:unit",
30
+ "test:unit": "node --test"
31
+ },
32
+ "license": "MIT",
33
+ "funding": {
34
+ "type": "github",
35
+ "url": "https://github.com/sponsors/willfarrell"
36
+ },
37
+ "keywords": [],
38
+ "author": {
39
+ "name": "1auth contributors",
40
+ "url": "https://github.com/willfarrell/1auth/graphs/contributors"
41
+ },
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "git+https://github.com/willfarrell/1auth.git",
45
+ "directory": "packages/crypto"
46
+ },
47
+ "bugs": {
48
+ "url": "https://github.com/willfarrell/1auth/issues"
49
+ },
50
+ "homepage": "https://github.com/willfarrell/1auth",
51
+ "gitHead": "7a6c0fbb8ab71d6a2171e678697de9f237568431",
52
+ "dependencies": {
53
+ "@node-rs/argon2": "2.0.2",
54
+ "nanoid": "5.1.5"
55
+ }
56
56
  }