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.
Use npm or whatever.
$ npm i -S garbados-crypt
First, require the library. Then get to encrypting!
const Crypt = require('garbados-crypt')
const crypt = new Crypt(password)
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, usecrypt.export()
andCrypt.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.
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!
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.
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 theexportString
!exportString
: A string generated bycrypt.export()
.
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!
plaintext
: A string.ciphertext
: A different, encrypted string.
ciphertext
: An encrypted string produced bycrypt.encrypt()
.plaintext
: The decrypted message as a string.
If decryption fails, for example because your password is incorrect, an error will be thrown.
First, get the source:
$ git clone [email protected]: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
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.
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!
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.