From 371103dae8b97264471e17de1989199ffcd2718e Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Fri, 18 May 2018 11:05:20 +0200 Subject: [PATCH] crypto: add scrypt() and scryptSync() methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scrypt is a password-based key derivation function that is designed to be expensive both computationally and memory-wise in order to make brute-force attacks unrewarding. OpenSSL has had support for the scrypt algorithm since v1.1.0. Add a Node.js API modeled after `crypto.pbkdf2()` and `crypto.pbkdf2Sync()`. Changes: * Introduce helpers for copying buffers, collecting openssl errors, etc. * Add new infrastructure for offloading crypto to a worker thread. * Add a `AsyncWrap` JS class to simplify pbkdf2(), randomBytes() and scrypt(). Fixes: https://github.com/nodejs/node/issues/8417 PR-URL: https://github.com/nodejs/node/pull/20816 Reviewed-By: Anna Henningsen Reviewed-By: Colin Ihrig Reviewed-By: James M Snell Reviewed-By: Tobias Nießen --- doc/api/crypto.md | 106 +++++++- doc/api/errors.md | 14 + lib/crypto.js | 6 + lib/internal/crypto/scrypt.js | 97 +++++++ lib/internal/errors.js | 3 +- node.gyp | 1 + src/async_wrap.cc | 31 +++ src/async_wrap.h | 1 + src/env.h | 1 + src/node_crypto.cc | 247 ++++++++++++++---- src/node_internals.h | 2 + test/parallel/test-crypto-scrypt.js | 165 ++++++++++++ test/sequential/test-async-wrap-getasyncid.js | 6 + 13 files changed, 620 insertions(+), 60 deletions(-) create mode 100644 lib/internal/crypto/scrypt.js create mode 100644 test/parallel/test-crypto-scrypt.js diff --git a/doc/api/crypto.md b/doc/api/crypto.md index a535f55ea6c..c88f856ed26 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -1361,9 +1361,9 @@ password always creates the same key. The low iteration count and non-cryptographically secure hash algorithm allow passwords to be tested very rapidly. -In line with OpenSSL's recommendation to use PBKDF2 instead of +In line with OpenSSL's recommendation to use a more modern algorithm instead of [`EVP_BytesToKey`][] it is recommended that developers derive a key and IV on -their own using [`crypto.pbkdf2()`][] and to use [`crypto.createCipheriv()`][] +their own using [`crypto.scrypt()`][] and to use [`crypto.createCipheriv()`][] to create the `Cipher` object. Users should not use ciphers with counter mode (e.g. CTR, GCM, or CCM) in `crypto.createCipher()`. A warning is emitted when they are used in order to avoid the risk of IV reuse that causes @@ -1463,9 +1463,9 @@ password always creates the same key. The low iteration count and non-cryptographically secure hash algorithm allow passwords to be tested very rapidly. -In line with OpenSSL's recommendation to use PBKDF2 instead of +In line with OpenSSL's recommendation to use a more modern algorithm instead of [`EVP_BytesToKey`][] it is recommended that developers derive a key and IV on -their own using [`crypto.pbkdf2()`][] and to use [`crypto.createDecipheriv()`][] +their own using [`crypto.scrypt()`][] and to use [`crypto.createDecipheriv()`][] to create the `Decipher` object. ### crypto.createDecipheriv(algorithm, key, iv[, options]) @@ -1801,9 +1801,8 @@ The `iterations` argument must be a number set as high as possible. The higher the number of iterations, the more secure the derived key will be, but will take a longer amount of time to complete. -The `salt` should also be as unique as possible. It is recommended that the -salts are random and their lengths are at least 16 bytes. See -[NIST SP 800-132][] for details. +The `salt` should be as unique as possible. It is recommended that a salt is +random and at least 16 bytes long. See [NIST SP 800-132][] for details. Example: @@ -1867,9 +1866,8 @@ The `iterations` argument must be a number set as high as possible. The higher the number of iterations, the more secure the derived key will be, but will take a longer amount of time to complete. -The `salt` should also be as unique as possible. It is recommended that the -salts are random and their lengths are at least 16 bytes. See -[NIST SP 800-132][] for details. +The `salt` should be as unique as possible. It is recommended that a salt is +random and at least 16 bytes long. See [NIST SP 800-132][] for details. Example: @@ -2143,6 +2141,91 @@ threadpool request. To minimize threadpool task length variation, partition large `randomFill` requests when doing so as part of fulfilling a client request. +### crypto.scrypt(password, salt, keylen[, options], callback) + +- `password` {string|Buffer|TypedArray} +- `salt` {string|Buffer|TypedArray} +- `keylen` {number} +- `options` {Object} + - `N` {number} CPU/memory cost parameter. Must be a power of two greater + than one. **Default:** `16384`. + - `r` {number} Block size parameter. **Default:** `8`. + - `p` {number} Parallelization parameter. **Default:** `1`. + - `maxmem` {number} Memory upper bound. It is an error when (approximately) + `128*N*r > maxmem` **Default:** `32 * 1024 * 1024`. +- `callback` {Function} + - `err` {Error} + - `derivedKey` {Buffer} + +Provides an asynchronous [scrypt][] implementation. Scrypt is a password-based +key derivation function that is designed to be expensive computationally and +memory-wise in order to make brute-force attacks unrewarding. + +The `salt` should be as unique as possible. It is recommended that a salt is +random and at least 16 bytes long. See [NIST SP 800-132][] for details. + +The `callback` function is called with two arguments: `err` and `derivedKey`. +`err` is an exception object when key derivation fails, otherwise `err` is +`null`. `derivedKey` is passed to the callback as a [`Buffer`][]. + +An exception is thrown when any of the input arguments specify invalid values +or types. + +```js +const crypto = require('crypto'); +// Using the factory defaults. +crypto.scrypt('secret', 'salt', 64, (err, derivedKey) => { + if (err) throw err; + console.log(derivedKey.toString('hex')); // '3745e48...08d59ae' +}); +// Using a custom N parameter. Must be a power of two. +crypto.scrypt('secret', 'salt', 64, { N: 1024 }, (err, derivedKey) => { + if (err) throw err; + console.log(derivedKey.toString('hex')); // '3745e48...aa39b34' +}); +``` + +### crypto.scryptSync(password, salt, keylen[, options]) + +- `password` {string|Buffer|TypedArray} +- `salt` {string|Buffer|TypedArray} +- `keylen` {number} +- `options` {Object} + - `N` {number} CPU/memory cost parameter. Must be a power of two greater + than one. **Default:** `16384`. + - `r` {number} Block size parameter. **Default:** `8`. + - `p` {number} Parallelization parameter. **Default:** `1`. + - `maxmem` {number} Memory upper bound. It is an error when (approximately) + `128*N*r > maxmem` **Default:** `32 * 1024 * 1024`. +- Returns: {Buffer} + +Provides a synchronous [scrypt][] implementation. Scrypt is a password-based +key derivation function that is designed to be expensive computationally and +memory-wise in order to make brute-force attacks unrewarding. + +The `salt` should be as unique as possible. It is recommended that a salt is +random and at least 16 bytes long. See [NIST SP 800-132][] for details. + +An exception is thrown when key derivation fails, otherwise the derived key is +returned as a [`Buffer`][]. + +An exception is thrown when any of the input arguments specify invalid values +or types. + +```js +const crypto = require('crypto'); +// Using the factory defaults. +const key1 = crypto.scryptSync('secret', 'salt', 64); +console.log(key1.toString('hex')); // '3745e48...08d59ae' +// Using a custom N parameter. Must be a power of two. +const key2 = crypto.scryptSync('secret', 'salt', 64, { N: 1024 }); +console.log(key2.toString('hex')); // '3745e48...aa39b34' +``` + ### crypto.setEngine(engine[, flags])