Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit c9b4592

Browse filesBrowse files
bnoordhuistargos
authored andcommitted
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: #8417 PR-URL: #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>
1 parent 4957562 commit c9b4592
Copy full SHA for c9b4592

File tree

Expand file treeCollapse file tree

13 files changed

+617
-57
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

13 files changed

+617
-57
lines changed
Open diff view settings
Collapse file

‎doc/api/crypto.md‎

Copy file name to clipboardExpand all lines: doc/api/crypto.md
+95-11Lines changed: 95 additions & 11 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -1356,9 +1356,9 @@ password always creates the same key. The low iteration count and
13561356
non-cryptographically secure hash algorithm allow passwords to be tested very
13571357
rapidly.
13581358

1359-
In line with OpenSSL's recommendation to use PBKDF2 instead of
1359+
In line with OpenSSL's recommendation to use a more modern algorithm instead of
13601360
[`EVP_BytesToKey`][] it is recommended that developers derive a key and IV on
1361-
their own using [`crypto.pbkdf2()`][] and to use [`crypto.createCipheriv()`][]
1361+
their own using [`crypto.scrypt()`][] and to use [`crypto.createCipheriv()`][]
13621362
to create the `Cipher` object. Users should not use ciphers with counter mode
13631363
(e.g. CTR, GCM, or CCM) in `crypto.createCipher()`. A warning is emitted when
13641364
they are used in order to avoid the risk of IV reuse that causes
@@ -1458,9 +1458,9 @@ password always creates the same key. The low iteration count and
14581458
non-cryptographically secure hash algorithm allow passwords to be tested very
14591459
rapidly.
14601460

1461-
In line with OpenSSL's recommendation to use PBKDF2 instead of
1461+
In line with OpenSSL's recommendation to use a more modern algorithm instead of
14621462
[`EVP_BytesToKey`][] it is recommended that developers derive a key and IV on
1463-
their own using [`crypto.pbkdf2()`][] and to use [`crypto.createDecipheriv()`][]
1463+
their own using [`crypto.scrypt()`][] and to use [`crypto.createDecipheriv()`][]
14641464
to create the `Decipher` object.
14651465

14661466
### crypto.createDecipheriv(algorithm, key, iv[, options])
@@ -1796,9 +1796,8 @@ The `iterations` argument must be a number set as high as possible. The
17961796
higher the number of iterations, the more secure the derived key will be,
17971797
but will take a longer amount of time to complete.
17981798

1799-
The `salt` should also be as unique as possible. It is recommended that the
1800-
salts are random and their lengths are at least 16 bytes. See
1801-
[NIST SP 800-132][] for details.
1799+
The `salt` should be as unique as possible. It is recommended that a salt is
1800+
random and at least 16 bytes long. See [NIST SP 800-132][] for details.
18021801

18031802
Example:
18041803

@@ -1862,9 +1861,8 @@ The `iterations` argument must be a number set as high as possible. The
18621861
higher the number of iterations, the more secure the derived key will be,
18631862
but will take a longer amount of time to complete.
18641863

1865-
The `salt` should also be as unique as possible. It is recommended that the
1866-
salts are random and their lengths are at least 16 bytes. See
1867-
[NIST SP 800-132][] for details.
1864+
The `salt` should be as unique as possible. It is recommended that a salt is
1865+
random and at least 16 bytes long. See [NIST SP 800-132][] for details.
18681866

18691867
Example:
18701868

@@ -2138,6 +2136,91 @@ threadpool request. To minimize threadpool task length variation, partition
21382136
large `randomFill` requests when doing so as part of fulfilling a client
21392137
request.
21402138

2139+
### crypto.scrypt(password, salt, keylen[, options], callback)
2140+
<!-- YAML
2141+
added: REPLACEME
2142+
-->
2143+
- `password` {string|Buffer|TypedArray}
2144+
- `salt` {string|Buffer|TypedArray}
2145+
- `keylen` {number}
2146+
- `options` {Object}
2147+
- `N` {number} CPU/memory cost parameter. Must be a power of two greater
2148+
than one. **Default:** `16384`.
2149+
- `r` {number} Block size parameter. **Default:** `8`.
2150+
- `p` {number} Parallelization parameter. **Default:** `1`.
2151+
- `maxmem` {number} Memory upper bound. It is an error when (approximately)
2152+
`128*N*r > maxmem` **Default:** `32 * 1024 * 1024`.
2153+
- `callback` {Function}
2154+
- `err` {Error}
2155+
- `derivedKey` {Buffer}
2156+
2157+
Provides an asynchronous [scrypt][] implementation. Scrypt is a password-based
2158+
key derivation function that is designed to be expensive computationally and
2159+
memory-wise in order to make brute-force attacks unrewarding.
2160+
2161+
The `salt` should be as unique as possible. It is recommended that a salt is
2162+
random and at least 16 bytes long. See [NIST SP 800-132][] for details.
2163+
2164+
The `callback` function is called with two arguments: `err` and `derivedKey`.
2165+
`err` is an exception object when key derivation fails, otherwise `err` is
2166+
`null`. `derivedKey` is passed to the callback as a [`Buffer`][].
2167+
2168+
An exception is thrown when any of the input arguments specify invalid values
2169+
or types.
2170+
2171+
```js
2172+
const crypto = require('crypto');
2173+
// Using the factory defaults.
2174+
crypto.scrypt('secret', 'salt', 64, (err, derivedKey) => {
2175+
if (err) throw err;
2176+
console.log(derivedKey.toString('hex')); // '3745e48...08d59ae'
2177+
});
2178+
// Using a custom N parameter. Must be a power of two.
2179+
crypto.scrypt('secret', 'salt', 64, { N: 1024 }, (err, derivedKey) => {
2180+
if (err) throw err;
2181+
console.log(derivedKey.toString('hex')); // '3745e48...aa39b34'
2182+
});
2183+
```
2184+
2185+
### crypto.scryptSync(password, salt, keylen[, options])
2186+
<!-- YAML
2187+
added: REPLACEME
2188+
-->
2189+
- `password` {string|Buffer|TypedArray}
2190+
- `salt` {string|Buffer|TypedArray}
2191+
- `keylen` {number}
2192+
- `options` {Object}
2193+
- `N` {number} CPU/memory cost parameter. Must be a power of two greater
2194+
than one. **Default:** `16384`.
2195+
- `r` {number} Block size parameter. **Default:** `8`.
2196+
- `p` {number} Parallelization parameter. **Default:** `1`.
2197+
- `maxmem` {number} Memory upper bound. It is an error when (approximately)
2198+
`128*N*r > maxmem` **Default:** `32 * 1024 * 1024`.
2199+
- Returns: {Buffer}
2200+
2201+
Provides a synchronous [scrypt][] implementation. Scrypt is a password-based
2202+
key derivation function that is designed to be expensive computationally and
2203+
memory-wise in order to make brute-force attacks unrewarding.
2204+
2205+
The `salt` should be as unique as possible. It is recommended that a salt is
2206+
random and at least 16 bytes long. See [NIST SP 800-132][] for details.
2207+
2208+
An exception is thrown when key derivation fails, otherwise the derived key is
2209+
returned as a [`Buffer`][].
2210+
2211+
An exception is thrown when any of the input arguments specify invalid values
2212+
or types.
2213+
2214+
```js
2215+
const crypto = require('crypto');
2216+
// Using the factory defaults.
2217+
const key1 = crypto.scryptSync('secret', 'salt', 64);
2218+
console.log(key1.toString('hex')); // '3745e48...08d59ae'
2219+
// Using a custom N parameter. Must be a power of two.
2220+
const key2 = crypto.scryptSync('secret', 'salt', 64, { N: 1024 });
2221+
console.log(key2.toString('hex')); // '3745e48...aa39b34'
2222+
```
2223+
21412224
### crypto.setEngine(engine[, flags])
21422225
<!-- YAML
21432226
added: v0.11.11
@@ -2645,9 +2728,9 @@ the `crypto`, `tls`, and `https` modules and are generally specific to OpenSSL.
26452728
[`crypto.createVerify()`]: #crypto_crypto_createverify_algorithm_options
26462729
[`crypto.getCurves()`]: #crypto_crypto_getcurves
26472730
[`crypto.getHashes()`]: #crypto_crypto_gethashes
2648-
[`crypto.pbkdf2()`]: #crypto_crypto_pbkdf2_password_salt_iterations_keylen_digest_callback
26492731
[`crypto.randomBytes()`]: #crypto_crypto_randombytes_size_callback
26502732
[`crypto.randomFill()`]: #crypto_crypto_randomfill_buffer_offset_size_callback
2733+
[`crypto.scrypt()`]: #crypto_crypto_scrypt_password_salt_keylen_options_callback
26512734
[`decipher.final()`]: #crypto_decipher_final_outputencoding
26522735
[`decipher.update()`]: #crypto_decipher_update_data_inputencoding_outputencoding
26532736
[`diffieHellman.setPublicKey()`]: #crypto_diffiehellman_setpublickey_publickey_encoding
@@ -2681,5 +2764,6 @@ the `crypto`, `tls`, and `https` modules and are generally specific to OpenSSL.
26812764
[RFC 3610]: https://www.rfc-editor.org/rfc/rfc3610.txt
26822765
[RFC 4055]: https://www.rfc-editor.org/rfc/rfc4055.txt
26832766
[initialization vector]: https://en.wikipedia.org/wiki/Initialization_vector
2767+
[scrypt]: https://en.wikipedia.org/wiki/Scrypt
26842768
[stream-writable-write]: stream.html#stream_writable_write_chunk_encoding_callback
26852769
[stream]: stream.html
Collapse file

‎doc/api/errors.md‎

Copy file name to clipboardExpand all lines: doc/api/errors.md
+14Lines changed: 14 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,18 @@ An invalid [crypto digest algorithm][] was specified.
739739
A crypto method was used on an object that was in an invalid state. For
740740
instance, calling [`cipher.getAuthTag()`][] before calling `cipher.final()`.
741741

742+
<a id="ERR_CRYPTO_SCRYPT_INVALID_PARAMETER"></a>
743+
### ERR_CRYPTO_SCRYPT_INVALID_PARAMETER
744+
745+
One or more [`crypto.scrypt()`][] or [`crypto.scryptSync()`][] parameters are
746+
outside their legal range.
747+
748+
<a id="ERR_CRYPTO_SCRYPT_NOT_SUPPORTED"></a>
749+
### ERR_CRYPTO_SCRYPT_NOT_SUPPORTED
750+
751+
Node.js was compiled without `scrypt` support. Not possible with the official
752+
release binaries but can happen with custom builds, including distro builds.
753+
742754
<a id="ERR_CRYPTO_SIGN_KEY_REQUIRED"></a>
743755
### ERR_CRYPTO_SIGN_KEY_REQUIRED
744756

@@ -1750,6 +1762,8 @@ Creation of a [`zlib`][] object failed due to incorrect configuration.
17501762
[`child_process`]: child_process.html
17511763
[`cipher.getAuthTag()`]: crypto.html#crypto_cipher_getauthtag
17521764
[`Class: assert.AssertionError`]: assert.html#assert_class_assert_assertionerror
1765+
[`crypto.scrypt()`]: crypto.html#crypto_crypto_scrypt_password_salt_keylen_options_callback
1766+
[`crypto.scryptSync()`]: crypto.html#crypto_crypto_scryptSync_password_salt_keylen_options
17531767
[`crypto.timingSafeEqual()`]: crypto.html#crypto_crypto_timingsafeequal_a_b
17541768
[`dgram.createSocket()`]: dgram.html#dgram_dgram_createsocket_options_callback
17551769
[`ERR_INVALID_ARG_TYPE`]: #ERR_INVALID_ARG_TYPE
Collapse file

‎lib/crypto.js‎

Copy file name to clipboardExpand all lines: lib/crypto.js
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ const {
5252
pbkdf2,
5353
pbkdf2Sync
5454
} = require('internal/crypto/pbkdf2');
55+
const {
56+
scrypt,
57+
scryptSync
58+
} = require('internal/crypto/scrypt');
5559
const {
5660
DiffieHellman,
5761
DiffieHellmanGroup,
@@ -163,6 +167,8 @@ module.exports = exports = {
163167
randomFill,
164168
randomFillSync,
165169
rng: randomBytes,
170+
scrypt,
171+
scryptSync,
166172
setEngine,
167173
timingSafeEqual,
168174
getFips: !fipsMode ? getFipsDisabled :
Collapse file

‎lib/internal/crypto/scrypt.js‎

Copy file name to clipboard
+97Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
'use strict';
2+
3+
const { AsyncWrap, Providers } = process.binding('async_wrap');
4+
const { Buffer } = require('buffer');
5+
const { scrypt: _scrypt } = process.binding('crypto');
6+
const {
7+
ERR_CRYPTO_SCRYPT_INVALID_PARAMETER,
8+
ERR_CRYPTO_SCRYPT_NOT_SUPPORTED,
9+
ERR_INVALID_CALLBACK,
10+
} = require('internal/errors').codes;
11+
const {
12+
checkIsArrayBufferView,
13+
checkIsUint,
14+
getDefaultEncoding,
15+
} = require('internal/crypto/util');
16+
17+
const defaults = {
18+
N: 16384,
19+
r: 8,
20+
p: 1,
21+
maxmem: 32 << 20, // 32 MB, matches SCRYPT_MAX_MEM.
22+
};
23+
24+
function scrypt(password, salt, keylen, options, callback = defaults) {
25+
if (callback === defaults) {
26+
callback = options;
27+
options = defaults;
28+
}
29+
30+
options = check(password, salt, keylen, options);
31+
const { N, r, p, maxmem } = options;
32+
({ password, salt, keylen } = options);
33+
34+
if (typeof callback !== 'function')
35+
throw new ERR_INVALID_CALLBACK();
36+
37+
const encoding = getDefaultEncoding();
38+
const keybuf = Buffer.alloc(keylen);
39+
40+
const wrap = new AsyncWrap(Providers.SCRYPTREQUEST);
41+
wrap.ondone = (ex) => { // Retains keybuf while request is in flight.
42+
if (ex) return callback.call(wrap, ex);
43+
if (encoding === 'buffer') return callback.call(wrap, null, keybuf);
44+
callback.call(wrap, null, keybuf.toString(encoding));
45+
};
46+
47+
handleError(keybuf, password, salt, N, r, p, maxmem, wrap);
48+
}
49+
50+
function scryptSync(password, salt, keylen, options = defaults) {
51+
options = check(password, salt, keylen, options);
52+
const { N, r, p, maxmem } = options;
53+
({ password, salt, keylen } = options);
54+
const keybuf = Buffer.alloc(keylen);
55+
handleError(keybuf, password, salt, N, r, p, maxmem);
56+
const encoding = getDefaultEncoding();
57+
if (encoding === 'buffer') return keybuf;
58+
return keybuf.toString(encoding);
59+
}
60+
61+
function handleError(keybuf, password, salt, N, r, p, maxmem, wrap) {
62+
const ex = _scrypt(keybuf, password, salt, N, r, p, maxmem, wrap);
63+
64+
if (ex === undefined)
65+
return;
66+
67+
if (ex === null)
68+
throw new ERR_CRYPTO_SCRYPT_INVALID_PARAMETER(); // Bad N, r, p, or maxmem.
69+
70+
throw ex; // Scrypt operation failed, exception object contains details.
71+
}
72+
73+
function check(password, salt, keylen, options, callback) {
74+
if (_scrypt === undefined)
75+
throw new ERR_CRYPTO_SCRYPT_NOT_SUPPORTED();
76+
77+
password = checkIsArrayBufferView('password', password);
78+
salt = checkIsArrayBufferView('salt', salt);
79+
keylen = checkIsUint('keylen', keylen);
80+
81+
let { N, r, p, maxmem } = defaults;
82+
if (options && options !== defaults) {
83+
if (options.hasOwnProperty('N')) N = checkIsUint('N', options.N);
84+
if (options.hasOwnProperty('r')) r = checkIsUint('r', options.r);
85+
if (options.hasOwnProperty('p')) p = checkIsUint('p', options.p);
86+
if (options.hasOwnProperty('maxmem'))
87+
maxmem = checkIsUint('maxmem', options.maxmem);
88+
if (N === 0) N = defaults.N;
89+
if (r === 0) r = defaults.r;
90+
if (p === 0) p = defaults.p;
91+
if (maxmem === 0) maxmem = defaults.maxmem;
92+
}
93+
94+
return { password, salt, keylen, N, r, p, maxmem };
95+
}
96+
97+
module.exports = { scrypt, scryptSync };
Collapse file

‎lib/internal/errors.js‎

Copy file name to clipboardExpand all lines: lib/internal/errors.js
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,8 @@ E('ERR_CRYPTO_HASH_FINALIZED', 'Digest already called', Error);
503503
E('ERR_CRYPTO_HASH_UPDATE_FAILED', 'Hash update failed', Error);
504504
E('ERR_CRYPTO_INVALID_DIGEST', 'Invalid digest: %s', TypeError);
505505
E('ERR_CRYPTO_INVALID_STATE', 'Invalid state for operation %s', Error);
506-
506+
E('ERR_CRYPTO_SCRYPT_INVALID_PARAMETER', 'Invalid scrypt parameter', Error);
507+
E('ERR_CRYPTO_SCRYPT_NOT_SUPPORTED', 'Scrypt algorithm not supported', Error);
507508
// Switch to TypeError. The current implementation does not seem right.
508509
E('ERR_CRYPTO_SIGN_KEY_REQUIRED', 'No key provided to sign', Error);
509510
E('ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH',
Collapse file

‎node.gyp‎

Copy file name to clipboardExpand all lines: node.gyp
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
'lib/internal/crypto/hash.js',
9898
'lib/internal/crypto/pbkdf2.js',
9999
'lib/internal/crypto/random.js',
100+
'lib/internal/crypto/scrypt.js',
100101
'lib/internal/crypto/sig.js',
101102
'lib/internal/crypto/util.js',
102103
'lib/internal/constants.js',
Collapse file

‎src/async_wrap.cc‎

Copy file name to clipboardExpand all lines: src/async_wrap.cc
+31Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ using v8::PromiseHookType;
4545
using v8::PropertyCallbackInfo;
4646
using v8::RetainedObjectInfo;
4747
using v8::String;
48+
using v8::Uint32;
4849
using v8::Undefined;
4950
using v8::Value;
5051

@@ -133,6 +134,23 @@ RetainedObjectInfo* WrapperInfo(uint16_t class_id, Local<Value> wrapper) {
133134
// end RetainedAsyncInfo
134135

135136

137+
struct AsyncWrapObject : public AsyncWrap {
138+
static inline void New(const FunctionCallbackInfo<Value>& args) {
139+
Environment* env = Environment::GetCurrent(args);
140+
CHECK(args.IsConstructCall());
141+
CHECK(env->async_wrap_constructor_template()->HasInstance(args.This()));
142+
CHECK(args[0]->IsUint32());
143+
auto type = static_cast<ProviderType>(args[0].As<Uint32>()->Value());
144+
new AsyncWrapObject(env, args.This(), type);
145+
}
146+
147+
inline AsyncWrapObject(Environment* env, Local<Object> object,
148+
ProviderType type) : AsyncWrap(env, object, type) {}
149+
150+
inline size_t self_size() const override { return sizeof(*this); }
151+
};
152+
153+
136154
static void DestroyAsyncIdsCallback(Environment* env, void* data) {
137155
Local<Function> fn = env->async_hooks_destroy_function();
138156

@@ -569,6 +587,19 @@ void AsyncWrap::Initialize(Local<Object> target,
569587
env->set_async_hooks_destroy_function(Local<Function>());
570588
env->set_async_hooks_promise_resolve_function(Local<Function>());
571589
env->set_async_hooks_binding(target);
590+
591+
{
592+
auto class_name = FIXED_ONE_BYTE_STRING(env->isolate(), "AsyncWrap");
593+
auto function_template = env->NewFunctionTemplate(AsyncWrapObject::New);
594+
function_template->SetClassName(class_name);
595+
AsyncWrap::AddWrapMethods(env, function_template);
596+
auto instance_template = function_template->InstanceTemplate();
597+
instance_template->SetInternalFieldCount(1);
598+
auto function =
599+
function_template->GetFunction(env->context()).ToLocalChecked();
600+
target->Set(env->context(), class_name, function).FromJust();
601+
env->set_async_wrap_constructor_template(function_template);
602+
}
572603
}
573604

574605

Collapse file

‎src/async_wrap.h‎

Copy file name to clipboardExpand all lines: src/async_wrap.h
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ namespace node {
7575
#define NODE_ASYNC_CRYPTO_PROVIDER_TYPES(V) \
7676
V(PBKDF2REQUEST) \
7777
V(RANDOMBYTESREQUEST) \
78+
V(SCRYPTREQUEST) \
7879
V(TLSWRAP)
7980
#else
8081
#define NODE_ASYNC_CRYPTO_PROVIDER_TYPES(V)
Collapse file

‎src/env.h‎

Copy file name to clipboardExpand all lines: src/env.h
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ struct PackageConfig {
319319
V(async_hooks_destroy_function, v8::Function) \
320320
V(async_hooks_init_function, v8::Function) \
321321
V(async_hooks_promise_resolve_function, v8::Function) \
322+
V(async_wrap_constructor_template, v8::FunctionTemplate) \
322323
V(buffer_prototype_object, v8::Object) \
323324
V(context, v8::Context) \
324325
V(domain_callback, v8::Function) \

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.