Skip to content

Instantly share code, notes, and snippets.

@emilbayes
Created March 30, 2020 22:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save emilbayes/cd9153c04cfb495e1a8bb8c5f862f486 to your computer and use it in GitHub Desktop.
Save emilbayes/cd9153c04cfb495e1a8bb8c5f862f486 to your computer and use it in GitHub Desktop.
Key encapsulation
const sodium = require('sodium-native')
const assert = require('nanoassert')
const priv = new WeakMap()
class Key {
static BYTES = sodium.crypto_secretbox_KEYBYTES
static NONCEBYTES = crypto.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES
static TAGBYTES = sodium.crypto_aead_xchacha20poly1305_ietf_ABYTES
static create () {
const key = sodium.sodium_malloc(this.BYTES)
sodium.randombytes_buf(key)
return new this(key)
}
static import (key) {
return new this(key)
}
constructor (key) {
assert(key.byteLength === this.BYTES, 'key must be Key.BYTES long')
assert(key.secure, 'key must be secure buffer')
this.destroyed = false
priv.set(this, key)
}
encrypt (plaintext, buf) {
if (this.destroyed) throw new Error('Destroyed')
assert(Buffer.isBuffer(plaintext), 'plaintext must be (secure) buffer')
const outlen = this.encryptLength(plaintext)
if (buf == null) buf = Buffer.alloc(outlen)
assert(buf.byteLength === outlen, 'buf must be encryptLength(plaintext) long')
const nonce = buf.subarray(0, Key.NONCEBYTES)
const ciphertext = buf.subarray(Key.NONCEBYTES)
sodium.randombytes_buf(nonce)
sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(ciphertext, plaintext, null, null, nonce, priv.get(this))
return buf
}
encryptLength (plaintext) {
assert(Buffer.isBuffer(plaintext), 'plaintext must be (secure) buffer')
return Key.NONCEBYTES + plaintext.byteLength + Key.TAGBYTES
}
decrypt (ciphertext, buf) {
if (this.destroyed) throw new Error('Destroyed')
assert(Buffer.isBuffer(ciphertext), 'ciphertext must be buffer')
const outlen = this.decryptLength(ciphertext)
if (buf == null) buf = sodium.sodium_malloc(outlen)
assert(buf.byteLength === outlen, 'buf must be decryptLength(ciphertext) long')
const nonce = buf.subarray(0, Key.NONCEBYTES)
const aead = buf.subarray(Key.NONCEBYTES)
sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(buf, null, aead, null, nonce, priv.get(this))
return buf
}
decryptLength (ciphertext) {
assert(Buffer.isBuffer(ciphertext), 'ciphertext must be buffer')
return -Key.NONCEBYTES + ciphertext.byteLength - Key.TAGBYTES
}
export () {
if (this.destroyed) throw new Error('Destroyed')
return priv.get(this)
}
destroy () {
const key = priv.get(this)
sodium.sodium_memzero(key)
priv.delete(key)
this.destroyed = true
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment