OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-social / bridge-vault / lib / crypto.js

59 lines · javascript ref:
import sodium from 'sodium-native'
import b4a from 'b4a'

const NONCE_BYTES = sodium.crypto_secretbox_NONCEBYTES
const KEY_BYTES = sodium.crypto_secretbox_KEYBYTES
const MAC_BYTES = sodium.crypto_secretbox_MACBYTES

function generateKey () {
  const key = b4a.allocUnsafe(KEY_BYTES)
  sodium.randombytes_buf(key)
  return key
}

function deriveKey (password, salt) {
  if (!salt) {
    salt = b4a.allocUnsafe(sodium.crypto_pwhash_SALTBYTES)
    sodium.randombytes_buf(salt)
  }
  const key = b4a.allocUnsafe(KEY_BYTES)
  sodium.crypto_pwhash(
    key,
    b4a.from(password),
    salt,
    sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE,
    sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE,
    sodium.crypto_pwhash_ALG_DEFAULT
  )
  return { key, salt }
}

function encrypt (plaintext, key) {
  const nonce = b4a.allocUnsafe(NONCE_BYTES)
  sodium.randombytes_buf(nonce)

  const input = b4a.isBuffer(plaintext) ? plaintext : b4a.from(plaintext)
  const cipher = b4a.allocUnsafe(input.length + MAC_BYTES)

  sodium.crypto_secretbox_easy(cipher, input, nonce, key)

  // Return nonce + ciphertext
  const result = b4a.allocUnsafe(NONCE_BYTES + cipher.length)
  b4a.copy(nonce, result, 0)
  b4a.copy(cipher, result, NONCE_BYTES)
  return result
}

function decrypt (encrypted, key) {
  const nonce = encrypted.subarray(0, NONCE_BYTES)
  const cipher = encrypted.subarray(NONCE_BYTES)

  const plaintext = b4a.allocUnsafe(cipher.length - MAC_BYTES)
  const ok = sodium.crypto_secretbox_open_easy(plaintext, cipher, nonce, key)
  if (!ok) throw new Error('Decryption failed — wrong key or corrupted data')

  return plaintext
}

export { generateKey, deriveKey, encrypt, decrypt, KEY_BYTES }