3.0.0-beta • Published 3 years ago

garbados-crypt v3.0.0-beta

Weekly downloads
-
License
Apache-2.0
Repository
github
Last release
3 years ago

garbados-crypt

CI Coverage Status Stability NPM Version JS Standard Style

Easy password-based encryption, by garbados.

This library attempts to reflect informed opinions while respecting realities like resource constraints, tech debt, and so on. The idea is to provide some very simple methods that just do the hard thing for you.

For example:

const Crypt = require('garbados-crypt')

const crypt = new Crypt(password)
const encrypted = await crypt.encrypt('hello world')
console.log(encrypted)
> "O/z1zXHQ+..."
const decrypted = await crypt.decrypt(encrypted)
console.log(decrypted)
> "hello world"

Crypt only works with plaintext, so remember to use JSON.stringify() on objects before encryption and JSON.parse() after decryption. For classes and the like, you'll need to choose your own encoding / decoding approach.

Install

Use npm or whatever.

$ npm i -S garbados-crypt

Usage

First, require the library. Then get to encrypting!

const Crypt = require('garbados-crypt')

const crypt = new Crypt(password)

new Crypt(password, [salt, opts])

  • password: A string. Make sure it's good! Or not.
  • salt: A salt, either as a byte array or a string. If omitted or falsy, a random salt is generated. Rather than bother carrying this with you, use crypt.export() and Crypt.import() to transport your credentials!
  • opts: Options!
  • opts.iterations: The number of iterations to use to hash your password via Argon2. Defaults to 100.
  • opts.saltLength: The length of the salt to be generated, in bytes. Defaults to 16.
  • opts.memorySize: The number of kilobytes of RAM to use to generate a cryptographic key from a password. Defaults to 4096 KB. Must be a power of 2.
  • opts.parallelism: The number of threads to use when generating a cryptographic key. Defaults to 1, as Crypt assumes it operates in a single-threaded environment.

A note on modifying default settings

Crypt's defaults have been selected to afford a cryptographic strength that does not impose significant performance penalties to applications doing a lot of encrypted reads and writes, such as those using crypto-pouch. If you are facing an attacker with significant resources, such as state actors, consider increasing the iterations and memorySize options. This will impose notable performance penalties, but as a rule slower cryptography means slower cracking. You should only modify these settings if you know what you are doing!

async Crypt.new(password, [salt, opts])

An asynchronous version of Crypt's constructor. Unlike the synchronous constructor, using .new() awaits Crypt's setup phase, so that you can explicitly await any problems during setup rather than wait for them to surface during encryption.

async Crypt.import(password, exportString) => new Crypt

Instantiates a new Crypt instance using an encoded string generated by crypt.export(). Use this method to import credentials generated on another device!

  • password: A string, the same string password you used with the Crypt instance that generated the exportString!
  • exportString: A string generated by crypt.export().

async crypt.export() => string

Exports a string you can use to create a new Crypt instance with Crypt.import(). Rather than bother carrying around your password and salt, use this to transport credentials across devices!

async crypt.encrypt(plaintext) => ciphertext

  • plaintext: A string.
  • ciphertext: A different, encrypted string.

async crypt.decrypt(ciphertext) => plaintext

  • ciphertext: An encrypted string produced by crypt.encrypt().
  • plaintext: The decrypted message as a string.

If decryption fails, for example because your password is incorrect, an error will be thrown.

Development

First, get the source:

$ git clone git@github.com:garbados/crypt.git garbados-crypt
$ cd garbados-crypt
$ npm i

Use the test suite:

$ npm test

The test suite includes a small benchmarking test, which runs on the server and in the browser, in case you're curious about performance.

To see test coverage:

$ npm run cov

Regarding passwords

Passwords should be generally considered a form of vulnerability. An attacker that manages to solve your encryption, such as by exfiltrating encrypted values and then brute-forcing the decryption key, may gain access to the password you used to encrypt those values. As a result, I highly advise deriving a strong passcode from a memorable passphrase in a non-reversible way, such as by using Argon2 or another derivation function yourself. By using this passcode only for a specific app, you ensure that an attacker will not be able to discover your passphrase even if they crack the passcode.

You can even do this with Crypt itself:

const globalCrypt = await Crypt.new('your_passphrase')
const passcode = await globalCrypt.encrypt('some_context_phrase') // like the name of the associated app or service
const contextCrypt = await Crypt.new(passcode)
// now you can use contextCrypt to encrypt your data
// with strong guarantees that even if an attacker cracks your crypto,
// they will not obtain your passphrase.

How To Securely Store A Password

For a password-based encryption system, it makes sense to have a good reference on how to store passwords in a database. To this effect I have written this gist to demonstrate safe password obfuscation and verification. If you have any issue with the advice offered there, leave a comment!

Why TweetNaCl.js?

This library uses tweetnacl rather than native crypto. You might have feelings about this.

I chose it because it's fast on NodeJS, bundles conveniently (33kb!), uses top-shelf algorithms, and has undergone a reasonable audit.

That said, I'm open to PRs that replace it with native crypto while retaining Crypt's API.

License

Apache-2.0