diff options
author | Ben Noordhuis <info@bnoordhuis.nl> | 2018-05-18 11:05:20 +0200 |
---|---|---|
committer | Ben Noordhuis <info@bnoordhuis.nl> | 2018-06-13 15:58:45 +0200 |
commit | 371103dae8b97264471e17de1989199ffcd2718e (patch) | |
tree | 4fb8517aa00fdbd0a6315719d04f2cd7211b753a /lib | |
parent | 58176e352c7b4fe6042fc31283a79d8de4cdb569 (diff) | |
download | android-node-v8-371103dae8b97264471e17de1989199ffcd2718e.tar.gz android-node-v8-371103dae8b97264471e17de1989199ffcd2718e.tar.bz2 android-node-v8-371103dae8b97264471e17de1989199ffcd2718e.zip |
crypto: add scrypt() and scryptSync() methods
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 <anna@addaleax.net>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/crypto.js | 6 | ||||
-rw-r--r-- | lib/internal/crypto/scrypt.js | 97 | ||||
-rw-r--r-- | lib/internal/errors.js | 3 |
3 files changed, 105 insertions, 1 deletions
diff --git a/lib/crypto.js b/lib/crypto.js index d22ffea970..281a30c2cf 100644 --- a/lib/crypto.js +++ b/lib/crypto.js @@ -53,6 +53,10 @@ const { pbkdf2Sync } = require('internal/crypto/pbkdf2'); const { + scrypt, + scryptSync +} = require('internal/crypto/scrypt'); +const { DiffieHellman, DiffieHellmanGroup, ECDH @@ -163,6 +167,8 @@ module.exports = exports = { randomFill, randomFillSync, rng: randomBytes, + scrypt, + scryptSync, setEngine, timingSafeEqual, getFips: !fipsMode ? getFipsDisabled : diff --git a/lib/internal/crypto/scrypt.js b/lib/internal/crypto/scrypt.js new file mode 100644 index 0000000000..09771455ac --- /dev/null +++ b/lib/internal/crypto/scrypt.js @@ -0,0 +1,97 @@ +'use strict'; + +const { AsyncWrap, Providers } = process.binding('async_wrap'); +const { Buffer } = require('buffer'); +const { scrypt: _scrypt } = process.binding('crypto'); +const { + ERR_CRYPTO_SCRYPT_INVALID_PARAMETER, + ERR_CRYPTO_SCRYPT_NOT_SUPPORTED, + ERR_INVALID_CALLBACK, +} = require('internal/errors').codes; +const { + checkIsArrayBufferView, + checkIsUint, + getDefaultEncoding, +} = require('internal/crypto/util'); + +const defaults = { + N: 16384, + r: 8, + p: 1, + maxmem: 32 << 20, // 32 MB, matches SCRYPT_MAX_MEM. +}; + +function scrypt(password, salt, keylen, options, callback = defaults) { + if (callback === defaults) { + callback = options; + options = defaults; + } + + options = check(password, salt, keylen, options); + const { N, r, p, maxmem } = options; + ({ password, salt, keylen } = options); + + if (typeof callback !== 'function') + throw new ERR_INVALID_CALLBACK(); + + const encoding = getDefaultEncoding(); + const keybuf = Buffer.alloc(keylen); + + const wrap = new AsyncWrap(Providers.SCRYPTREQUEST); + wrap.ondone = (ex) => { // Retains keybuf while request is in flight. + if (ex) return callback.call(wrap, ex); + if (encoding === 'buffer') return callback.call(wrap, null, keybuf); + callback.call(wrap, null, keybuf.toString(encoding)); + }; + + handleError(keybuf, password, salt, N, r, p, maxmem, wrap); +} + +function scryptSync(password, salt, keylen, options = defaults) { + options = check(password, salt, keylen, options); + const { N, r, p, maxmem } = options; + ({ password, salt, keylen } = options); + const keybuf = Buffer.alloc(keylen); + handleError(keybuf, password, salt, N, r, p, maxmem); + const encoding = getDefaultEncoding(); + if (encoding === 'buffer') return keybuf; + return keybuf.toString(encoding); +} + +function handleError(keybuf, password, salt, N, r, p, maxmem, wrap) { + const ex = _scrypt(keybuf, password, salt, N, r, p, maxmem, wrap); + + if (ex === undefined) + return; + + if (ex === null) + throw new ERR_CRYPTO_SCRYPT_INVALID_PARAMETER(); // Bad N, r, p, or maxmem. + + throw ex; // Scrypt operation failed, exception object contains details. +} + +function check(password, salt, keylen, options, callback) { + if (_scrypt === undefined) + throw new ERR_CRYPTO_SCRYPT_NOT_SUPPORTED(); + + password = checkIsArrayBufferView('password', password); + salt = checkIsArrayBufferView('salt', salt); + keylen = checkIsUint('keylen', keylen); + + let { N, r, p, maxmem } = defaults; + if (options && options !== defaults) { + if (options.hasOwnProperty('N')) N = checkIsUint('N', options.N); + if (options.hasOwnProperty('r')) r = checkIsUint('r', options.r); + if (options.hasOwnProperty('p')) p = checkIsUint('p', options.p); + if (options.hasOwnProperty('maxmem')) + maxmem = checkIsUint('maxmem', options.maxmem); + if (N === 0) N = defaults.N; + if (r === 0) r = defaults.r; + if (p === 0) p = defaults.p; + if (maxmem === 0) maxmem = defaults.maxmem; + } + + return { password, salt, keylen, N, r, p, maxmem }; +} + +module.exports = { scrypt, scryptSync }; diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 54201d0d1e..af844692d7 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -500,7 +500,8 @@ E('ERR_CRYPTO_HASH_FINALIZED', 'Digest already called', Error); E('ERR_CRYPTO_HASH_UPDATE_FAILED', 'Hash update failed', Error); E('ERR_CRYPTO_INVALID_DIGEST', 'Invalid digest: %s', TypeError); E('ERR_CRYPTO_INVALID_STATE', 'Invalid state for operation %s', Error); - +E('ERR_CRYPTO_SCRYPT_INVALID_PARAMETER', 'Invalid scrypt parameter', Error); +E('ERR_CRYPTO_SCRYPT_NOT_SUPPORTED', 'Scrypt algorithm not supported', Error); // Switch to TypeError. The current implementation does not seem right. E('ERR_CRYPTO_SIGN_KEY_REQUIRED', 'No key provided to sign', Error); E('ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH', |