aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorBen Noordhuis <info@bnoordhuis.nl>2018-05-18 11:05:20 +0200
committerBen Noordhuis <info@bnoordhuis.nl>2018-06-13 15:58:45 +0200
commit371103dae8b97264471e17de1989199ffcd2718e (patch)
tree4fb8517aa00fdbd0a6315719d04f2cd7211b753a /lib
parent58176e352c7b4fe6042fc31283a79d8de4cdb569 (diff)
downloadandroid-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.js6
-rw-r--r--lib/internal/crypto/scrypt.js97
-rw-r--r--lib/internal/errors.js3
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',