All files / src crypt.js

100% Statements 36/36
100% Branches 19/19
100% Functions 5/5
100% Lines 28/28

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 901x           3051x           3051x           1450x 1449x 1448x 1447x   1446x 1446x 1446x           804x 803x 802x 801x   800x 800x 800x           3051x   3051x     3051x                   3051x               60x     60x   60x 60x 60x     60x                     1x  
const crypto = require('crypto')
 
/* Encrypts a 32-byte buffer using AES-256-ECB with the given 32-byte key */
// Internal use only
function getWebCrypto () {
  const globalWebCrypto =
    typeof globalThis !== 'undefined' &&
    globalThis.crypto &&
    globalThis.crypto.subtle
  // Fallback for Node 16, which does not expose WebCrypto as a global
  /* istanbul ignore next */
  const webCrypto = globalWebCrypto || crypto.webcrypto.subtle
  return webCrypto
}
 
/* Encrypts a 32-byte buffer using AES-256-ECB with the given 32-byte key */
// Internal use only
function encrypt (data, key) {
  if (!Buffer.isBuffer(data)) throw new TypeError('data must be a buffer')
  if (data.length !== 32) throw new RangeError('data must be 32 bytes')
  if (!Buffer.isBuffer(key)) throw new TypeError('key must be a buffer')
  if (key.length !== 32) throw new RangeError('key must be 32 bytes')
 
  const cipher = crypto.createCipheriv('AES-256-ECB', key, '')
  cipher.setAutoPadding(false)
  return Buffer.concat([cipher.update(data), cipher.final()])
}
 
/* Decrypts a 32-byte buffer using AES-256-ECB with the given 32-byte key */
// Internal use only
function decrypt (data, key) {
  if (!Buffer.isBuffer(data)) throw new TypeError('data must be a buffer')
  if (data.length !== 32) throw new RangeError('data must be 32 bytes')
  if (!Buffer.isBuffer(key)) throw new TypeError('key must be a buffer')
  if (key.length !== 32) throw new RangeError('key must be 32 bytes')
 
  const decipher = crypto.createDecipheriv('AES-256-ECB', key, '')
  decipher.setAutoPadding(false)
  return Buffer.concat([decipher.update(data), decipher.final()])
}
 
/* Derives a key using HKDF with the given parameters */
// Internal use only
async function hkdf (hash, key, salt, purpose, size) {
  const webCrypto = getWebCrypto()
 
  const importedKey = await webCrypto.importKey('raw', key, 'HKDF', false, [
    'deriveBits'
  ])
  const bits = await webCrypto.deriveBits(
    {
      name: 'HKDF',
      hash: 'SHA-256',
      salt: Buffer.from(salt),
      info: Buffer.from(purpose)
    },
    importedKey,
    size * 8
  )
  return Buffer.from(bits)
}
 
/* Get a cryptographically secure random integer in range */
/* Inclusive of min, exclusive of max */
// Internal use only
async function random (min, max) {
  // Calculate the range size
  const range = max - min
 
  // Generate random bytes until we get a value in our desired range
  while (true) {
    // Generate random bytes
    const randomArray = new Uint32Array(1)
    globalThis.crypto.getRandomValues(randomArray)
    const randomValue = randomArray[0]
 
    // Calculate the number of complete sets of 'range' in our random value space
    const sets = Math.floor(2 ** 32 / range)
 
    // If the value is within our valid range, return it
    /* istanbul ignore next */
    if (randomValue < sets * range) {
      return min + (randomValue % range)
    }
    // Otherwise, try again to avoid bias
  }
}
 
module.exports = { encrypt, decrypt, hkdf, random }