@1auth/crypto 0.0.0-alpha.43 → 0.0.0-alpha.45

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 +146 -100
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -18,28 +18,37 @@ const sign = promisify(signCallback)
18
18
  const verify = promisify(verifyCallback)
19
19
 
20
20
  const defaults = {
21
- // symetricEncryptionKey: randomBytes(32).toString('base64') // 256 bits
22
- symetricEncryptionKey: undefined,
23
- symetricEncryptionMethod: 'chacha20-poly1305', // 2024-05: AES-256 GCM (aes-256-gcm) or ChaCha20-Poly1305 (chacha20-poly1305)
24
- symetricEncryptionEncoding: 'base64', // https://nodejs.org/api/buffer.html#buffers-and-character-encodings
25
- asymetricKey: 'P-384',
21
+ // symmetricEncryptionKey: randomBytes(32).toString('base64') // 256 bits
22
+ symmetricEncryptionKey: undefined,
23
+ symmetricEncryptionMethod: 'chacha20-poly1305', // 2024-05: AES-256 GCM (aes-256-gcm) or ChaCha20-Poly1305 (chacha20-poly1305)
24
+ symmetricEncryptionEncoding: 'base64', // https://nodejs.org/api/buffer.html#buffers-and-character-encodings
25
+ symmetricSignatureAlgorithm: 'sha3-384',
26
+ // symmetricSignatureSecret: randomBytes(32).toString('base64') // 256 bits
27
+ symmetricSignatureSecret: undefined,
28
+ asymmetricKeyNamedCurve: 'P-384', // P-512
29
+ asymmetricSignatureAlgorithm: 'sha3-384',
26
30
  digestAlgorithm: 'sha3-384',
27
- hmacAlgorithm: 'sha256',
31
+ digestEncoding: 'hex',
28
32
  signatureEncoding: 'base64'
29
33
  }
30
- const symetricEncryptionEncodingLengths = {}
34
+ const symmetricEncryptionEncodingLengths = {}
31
35
  const options = {}
32
36
  export default (opt = {}) => {
33
37
  Object.assign(options, defaults, opt)
34
- symetricEncryptionEncodingLengths.iv = randomBytes(12).toString(
35
- options.symetricEncryptionEncoding
38
+ symmetricEncryptionEncodingLengths.iv = randomBytes(12).toString(
39
+ options.symmetricEncryptionEncoding
36
40
  ).length
37
- symetricEncryptionEncodingLengths.ivAndAuthTag =
38
- symetricEncryptionEncodingLengths.iv +
39
- randomBytes(16).toString(options.symetricEncryptionEncoding).length
40
- if (!options.symetricEncryptionKey) {
41
+ symmetricEncryptionEncodingLengths.ivAndAuthTag =
42
+ symmetricEncryptionEncodingLengths.iv +
43
+ randomBytes(16).toString(options.symmetricEncryptionEncoding).length
44
+ if (!options.symmetricEncryptionKey) {
41
45
  console.warn(
42
- "@1auth/crypto symetricEncryptionKey is empty, use a stored secret made from randomBytes(32).toString('base64'). Encryption disabled."
46
+ "@1auth/crypto symmetricEncryptionKey is empty, use a stored secret made from randomBytes(32).toString('base64'). Encryption disabled."
47
+ )
48
+ }
49
+ if (!options.symmetricSignatureSecret) {
50
+ console.warn(
51
+ "@1auth/crypto symmetricSignatureSecret is empty, use a stored secret made from randomBytes(32).toString('base64'). Signature disabled."
43
52
  )
44
53
  }
45
54
  }
@@ -126,10 +135,6 @@ export const randomNumeric = (characterLength) => {
126
135
  return randomCharacters(characterLength, charactersNumeric)
127
136
  }
128
137
 
129
- export const randomSymetricEncryptionKey = () => {
130
- return randomBytes(32).toString('base64') // 256 bits
131
- }
132
-
133
138
  // *** configs *** //
134
139
  export const randomId = {
135
140
  type: 'id',
@@ -140,9 +145,10 @@ export const randomId = {
140
145
  }
141
146
 
142
147
  // *** Digests *** //
143
- export const createChecksum = (value, { algorithm } = {}) => {
148
+ export const createChecksum = (value, { algorithm, encoding } = {}) => {
144
149
  algorithm ??= options.digestAlgorithm
145
- return checksum(algorithm).update(value).digest('hex')
150
+ encoding ??= options.digestEncoding
151
+ return checksum(algorithm).update(value).digest(encoding)
146
152
  }
147
153
  export const createDigest = (value, { algorithm } = {}) => {
148
154
  algorithm ??= options.digestAlgorithm
@@ -153,19 +159,20 @@ export const createDigest = (value, { algorithm } = {}) => {
153
159
  // TODO evaluate non-iv encryption approach?
154
160
  // Use encryption as pepper to allow easier rotation of pepper
155
161
  export const createEncryptedDigest = (value, { algorithm } = {}) => {
162
+ algorithm ??= options.digestAlgorithm
156
163
  const digest = createDigest(value, { algorithm })
157
- // encrypting using the symetricEncryptionKey instread of
164
+ // encrypting using the symmetricEncryptionKey instead of
158
165
  // the row encryptionKey to allow lookup, must have fixed iv
159
- return symetricEncrypt(digest, {
160
- encryptionKey: options.symetricEncryptionKey,
166
+ return symmetricEncrypt(digest, {
167
+ encryptionKey: options.symmetricEncryptionKey,
161
168
  sub: '',
162
- encoding: options.symetricEncryptionEncoding,
169
+ encoding: options.symmetricEncryptionEncoding,
163
170
  iv: Buffer.from(
164
- options.symetricEncryptionKey.substring(
171
+ options.symmetricEncryptionKey.substring(
165
172
  0,
166
- symetricEncryptionEncodingLengths.iv
173
+ symmetricEncryptionEncodingLengths.iv
167
174
  ),
168
- options.symetricEncryptionEncoding
175
+ options.symmetricEncryptionEncoding
169
176
  )
170
177
  })
171
178
  }
@@ -175,19 +182,19 @@ export const rotateDigestEncryption = (
175
182
  encryptionKey,
176
183
  newEncryptionKey
177
184
  ) => {
178
- const digest = symetricDecrypt(encryptedValue, {
185
+ const digest = symmetricDecrypt(encryptedValue, {
179
186
  encryptionKey,
180
187
  sub: '',
181
- encoding: options.symetricEncryptionEncoding
188
+ encoding: options.symmetricEncryptionEncoding
182
189
  })
183
190
 
184
- return symetricEncrypt(digest, {
191
+ return symmetricEncrypt(digest, {
185
192
  encryptionKey: newEncryptionKey,
186
193
  sub: '',
187
- encoding: options.symetricEncryptionEncoding,
194
+ encoding: options.symmetricEncryptionEncoding,
188
195
  iv: Buffer.from(
189
- newEncryptionKey.substring(0, symetricEncryptionEncodingLengths.iv),
190
- options.symetricEncryptionEncoding
196
+ newEncryptionKey.substring(0, symmetricEncryptionEncodingLengths.iv),
197
+ options.symmetricEncryptionEncoding
191
198
  )
192
199
  })
193
200
  }
@@ -211,38 +218,42 @@ export const verifySecretHash = async (hash, value) => {
211
218
  return secretVerify(hash, value)
212
219
  }
213
220
 
214
- // *** Encryption *** //
221
+ // *** Symmetric Encryption *** //
215
222
  const authTagLength = 16
216
223
 
217
- export const makeSymetricKey = (sub) => {
218
- if (!options.symetricEncryptionKey) {
224
+ export const symmetricRandomEncryptionKey = () => {
225
+ return randomBytes(32).toString('base64') // 256 bits
226
+ }
227
+
228
+ export const symmetricGenerateEncryptionKey = (sub) => {
229
+ if (!options.symmetricEncryptionKey) {
219
230
  return { encryptionKey: '', encryptedKey: '' }
220
231
  }
221
- const encryptionKey = randomSymetricEncryptionKey()
222
- const encryptedKey = symetricEncrypt(encryptionKey, {
223
- encryptionKey: options.symetricEncryptionKey,
232
+ const encryptionKey = symmetricRandomEncryptionKey()
233
+ const encryptedKey = symmetricEncrypt(encryptionKey, {
234
+ encryptionKey: options.symmetricEncryptionKey,
224
235
  sub,
225
236
  decoding: 'base64',
226
- encoding: options.symetricEncryptionEncoding
237
+ encoding: options.symmetricEncryptionEncoding
227
238
  })
228
239
  return { encryptionKey, encryptedKey }
229
240
  }
230
241
 
231
242
  // sub add context to encryption
232
- export const symetricEncryptFields = (
243
+ export const symmetricEncryptFields = (
233
244
  values,
234
245
  { encryptedKey, encryptionKey, sub },
235
246
  fields = []
236
247
  ) => {
237
248
  if (encryptedKey) {
238
- encryptionKey ??= symetricDecryptKey(encryptedKey, {
249
+ encryptionKey ??= symmetricDecryptKey(encryptedKey, {
239
250
  sub
240
251
  })
241
252
  }
242
253
  if (!encryptionKey) return values
243
254
  const encryptedValues = structuredClone(values)
244
255
  for (const key of fields) {
245
- encryptedValues[key] &&= symetricEncrypt(encryptedValues[key], {
256
+ encryptedValues[key] &&= symmetricEncrypt(encryptedValues[key], {
246
257
  encryptionKey,
247
258
  sub
248
259
  })
@@ -250,22 +261,22 @@ export const symetricEncryptFields = (
250
261
  return encryptedValues
251
262
  }
252
263
 
253
- export const symetricEncrypt = (
264
+ export const symmetricEncrypt = (
254
265
  data,
255
- { encryptedKey, encryptionKey, sub, decoding, encoding, iv }
266
+ { encryptedKey, encryptionKey, signatureSecret, sub, decoding, encoding, iv }
256
267
  ) => {
257
268
  if (encryptedKey) {
258
- encryptionKey ??= symetricDecryptKey(encryptedKey, {
269
+ encryptionKey ??= symmetricDecryptKey(encryptedKey, {
259
270
  sub
260
271
  })
261
272
  }
262
273
  if (!encryptionKey || !data) return data
263
274
  decoding ??= 'utf8'
264
- encoding ??= options.symetricEncryptionEncoding
275
+ encoding ??= options.symmetricEncryptionEncoding
265
276
  iv ??= randomBytes(12) // 96 bits
266
277
 
267
278
  const cipher = createCipheriv(
268
- options.symetricEncryptionMethod,
279
+ options.symmetricEncryptionMethod,
269
280
  Buffer.from(encryptionKey, 'base64'),
270
281
  iv,
271
282
  {
@@ -280,23 +291,24 @@ export const symetricEncrypt = (
280
291
  const encryptedDataPacket =
281
292
  iv.toString(encoding) + authTag.toString(encoding) + encryptedData
282
293
 
283
- return encryptedDataPacket
294
+ // add signature to end
295
+ return symmetricSignatureSign(encryptedDataPacket, signatureSecret)
284
296
  }
285
297
 
286
298
  export const symmetricDecryptFields = (
287
299
  encryptedValues,
288
- { encryptedKey, encryptionKey, sub },
300
+ { encryptedKey, encryptionKey, signatureSecret, sub },
289
301
  fields = []
290
302
  ) => {
291
303
  if (encryptedKey) {
292
- encryptionKey ??= symetricDecryptKey(encryptedKey, {
304
+ encryptionKey ??= symmetricDecryptKey(encryptedKey, {
293
305
  sub
294
306
  })
295
307
  }
296
308
  if (!encryptionKey) return encryptedValues
297
309
  const values = structuredClone(encryptedValues)
298
310
  for (const key of fields) {
299
- values[key] &&= symetricDecrypt(values[key], {
311
+ values[key] &&= symmetricDecrypt(values[key], {
300
312
  encryptionKey,
301
313
  sub
302
314
  })
@@ -304,48 +316,58 @@ export const symmetricDecryptFields = (
304
316
  return values
305
317
  }
306
318
 
307
- export const symetricDecryptKey = (encryptedKey, { sub } = {}) => {
308
- return symetricDecrypt(encryptedKey, {
309
- encryptionKey: options.symetricEncryptionKey,
319
+ export const symmetricDecryptKey = (encryptedKey, { sub } = {}) => {
320
+ return symmetricDecrypt(encryptedKey, {
321
+ encryptionKey: options.symmetricEncryptionKey,
310
322
  sub,
311
- decoding: options.symetricEncryptionEncoding,
323
+ decoding: options.symmetricEncryptionEncoding,
312
324
  encoding: 'base64'
313
325
  })
314
326
  }
315
327
 
316
- export const symetricDecrypt = (
328
+ export const symmetricDecrypt = (
317
329
  encryptedDataPacket,
318
- { encryptedKey, encryptionKey, sub, decoding, encoding }
330
+ { encryptedKey, encryptionKey, signatureSecret, sub, decoding, encoding }
319
331
  ) => {
320
332
  if (encryptedKey) {
321
- encryptionKey ??= symetricDecryptKey(encryptedKey, {
333
+ encryptionKey ??= symmetricDecryptKey(encryptedKey, {
322
334
  sub
323
335
  })
324
336
  }
325
337
  if (!encryptionKey || !encryptedDataPacket) return encryptedDataPacket
326
- decoding ??= options.symetricEncryptionEncoding
338
+ decoding ??= options.symmetricEncryptionEncoding
327
339
  encoding ??= 'utf8'
328
340
 
341
+ // remove signature when successful
342
+ encryptedDataPacket = symmetricSignatureVerify(
343
+ encryptedDataPacket,
344
+ signatureSecret
345
+ )
346
+
347
+ if (encryptedDataPacket === false) {
348
+ throw new Error('Signature incorrect')
349
+ }
350
+
329
351
  const iv = Buffer.from(
330
- encryptedDataPacket.substring(0, symetricEncryptionEncodingLengths.iv),
352
+ encryptedDataPacket.substring(0, symmetricEncryptionEncodingLengths.iv),
331
353
  decoding
332
354
  )
333
355
  const authTag = Buffer.from(
334
356
  encryptedDataPacket.substring(
335
- symetricEncryptionEncodingLengths.iv,
336
- symetricEncryptionEncodingLengths.ivAndAuthTag
357
+ symmetricEncryptionEncodingLengths.iv,
358
+ symmetricEncryptionEncodingLengths.ivAndAuthTag
337
359
  ),
338
360
  decoding
339
361
  )
340
362
  const encryptedData = Buffer.from(
341
363
  encryptedDataPacket.substring(
342
- symetricEncryptionEncodingLengths.ivAndAuthTag
364
+ symmetricEncryptionEncodingLengths.ivAndAuthTag
343
365
  ),
344
366
  decoding
345
367
  )
346
368
 
347
369
  const decipher = createDecipheriv(
348
- options.symetricEncryptionMethod,
370
+ options.symmetricEncryptionMethod,
349
371
  Buffer.from(encryptionKey, 'base64'),
350
372
  iv,
351
373
  {
@@ -361,11 +383,65 @@ export const symetricDecrypt = (
361
383
  return data
362
384
  }
363
385
 
364
- // *** Signatures *** //
386
+ // *** Symmetric Signatures *** //
387
+ export const symmetricRandomSignatureSecret = () => {
388
+ return randomBytes(32).toString('base64') // 256 bits
389
+ }
390
+
391
+ export const symmetricGenerateSignatureSecret = (sub) => {
392
+ if (!options.symmetricSignatureSecret) {
393
+ return { signatureSecret: '' }
394
+ }
395
+ const signatureSecret = symmetricRandomSignatureSecret()
396
+ return { signatureSecret }
397
+ }
398
+
399
+ export const symmetricSignatureSign = (
400
+ data,
401
+ signatureSecret,
402
+ { algorithm } = {}
403
+ ) => {
404
+ // if (typeof data !== 'string') throw new TypeError('data must me a string')
405
+ signatureSecret ??= options.symmetricSignatureSecret
406
+ algorithm ??= options.symmetricSignatureAlgorithm
407
+ const signature = createHmac(algorithm, signatureSecret)
408
+ .update(data)
409
+ .digest(options.signatureEncoding)
410
+ .replace(/=+$/, '')
411
+ const signedData = data + '.' + signature
412
+ return signedData
413
+ }
414
+
415
+ export const symmetricSignatureVerify = (
416
+ signedData,
417
+ signatureSecret,
418
+ { algorithm } = {}
419
+ ) => {
420
+ if (typeof signedData !== 'string') return false
421
+ const data = signedData.slice(0, signedData.lastIndexOf('.'))
422
+ const signedDataExpected = symmetricSignatureSign(data, signatureSecret, {
423
+ algorithm
424
+ })
425
+
426
+ return safeEqual(signedData, signedDataExpected) && data
427
+ }
428
+
429
+ export const symmetricRotation = (
430
+ encryptedValues,
431
+ oldOptions, // { encryptedKey, encryptionKey, signatureSecret, sub }, // old
432
+ newOptions, // { encryptedKey, encryptionKey, signatureSecret, sub, decoding, encoding, iv }, // new
433
+ fields = []
434
+ ) => {
435
+ const data = symmetricDecryptFields(encryptedValues, oldOptions, fields)
436
+ const newEncryptedData = symmetricEncryptFields(data, newOptions, fields)
437
+ return newEncryptedData
438
+ }
439
+
440
+ // *** Asymmetric Signatures *** //
365
441
  // asymmetricKeyPairType
366
442
  export const makeAsymmetricKeys = async () => {
367
443
  const { publicKey, privateKey } = await generateKeyPair('ec', {
368
- namedCurve: 'P-384', // P-512
444
+ namedCurve: options.asymmetricKeyNamedCurve,
369
445
  paramEncoding: 'named',
370
446
  publicKeyEncoding: {
371
447
  type: 'spki',
@@ -375,7 +451,7 @@ export const makeAsymmetricKeys = async () => {
375
451
  type: 'sec1',
376
452
  format: 'pem'
377
453
  // Encryption done at another level for consistency
378
- // cipher: options.asymetricEncryptionMethod,
454
+ // cipher: options.asymmetricEncryptionMethod,
379
455
  // passphrase: encryptionKey,
380
456
  }
381
457
  })
@@ -387,7 +463,7 @@ export const makeAsymmetricSignature = async (
387
463
  privateKey,
388
464
  { algorithm } = {}
389
465
  ) => {
390
- algorithm ??= options.digestAlgorithm
466
+ algorithm ??= options.asymmetricSignatureAlgorithm
391
467
  return (await sign(algorithm, Buffer.from(data), privateKey)).toString(
392
468
  options.signatureEncoding
393
469
  )
@@ -398,7 +474,7 @@ export const verifyAsymmetricSignature = async (
398
474
  signature,
399
475
  { algorithm } = {}
400
476
  ) => {
401
- algorithm ??= options.digestAlgorithm
477
+ algorithm ??= options.asymmetricSignatureAlgorithm
402
478
  return await verify(
403
479
  algorithm,
404
480
  Buffer.from(data),
@@ -407,36 +483,6 @@ export const verifyAsymmetricSignature = async (
407
483
  )
408
484
  }
409
485
 
410
- export const makeSymmetricSignature = (
411
- data,
412
- encryptionKey,
413
- { algorithm } = {}
414
- ) => {
415
- // if (typeof data !== 'string') throw new TypeError('data must me a string')
416
- encryptionKey ??= options.symetricEncryptionKey
417
- algorithm ??= options.hmacAlgorithm
418
- const signature = createHmac('sha256', encryptionKey)
419
- .update(data)
420
- .digest('base64')
421
- .replace(/=+$/, '')
422
- const signedData = data + '.' + signature
423
- return signedData
424
- }
425
-
426
- export const verifySymmetricSignature = (
427
- signedData,
428
- encryptionKey,
429
- { algorithm } = {}
430
- ) => {
431
- if (typeof signedData !== 'string') return false
432
- const data = signedData.slice(0, signedData.lastIndexOf('.'))
433
- const signedDataExpected = makeSymmetricSignature(data, encryptionKey, {
434
- algorithm
435
- })
436
-
437
- return safeEqual(signedData, signedDataExpected) && data
438
- }
439
-
440
486
  export const safeEqual = (input, expected) => {
441
487
  const bufferInput = Buffer.from(input)
442
488
  const bufferExpected = Buffer.from(expected)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1auth/crypto",
3
- "version": "0.0.0-alpha.43",
3
+ "version": "0.0.0-alpha.45",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "engines": {