-
-
Notifications
You must be signed in to change notification settings - Fork 33.7k
crypto: add argon2() and argon2Sync() methods #50353
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
f05b81e
0d8e079
30f4e8c
c9baa80
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
Co-authored-by: Filip Skokan <[email protected]> Co-authored-by: James M Snell <[email protected]>
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| 'use strict'; | ||
|
|
||
| const common = require('../common.js'); | ||
| const assert = require('node:assert'); | ||
| const { | ||
| argon2, | ||
| argon2Sync, | ||
| randomBytes, | ||
| } = require('node:crypto'); | ||
|
|
||
| const bench = common.createBenchmark(main, { | ||
| mode: ['sync', 'async'], | ||
| algorithm: ['argon2d', 'argon2i', 'argon2id'], | ||
| passes: [1, 3], | ||
| parallelism: [2, 4, 8], | ||
| memory: [2 ** 11, 2 ** 16, 2 ** 21], | ||
| n: [50], | ||
| }); | ||
|
|
||
| function measureSync(n, algorithm, message, nonce, options) { | ||
| bench.start(); | ||
| for (let i = 0; i < n; ++i) | ||
| argon2Sync(algorithm, { ...options, message, nonce, tagLength: 64 }); | ||
| bench.end(n); | ||
| } | ||
|
|
||
| function measureAsync(n, algorithm, message, nonce, options) { | ||
| let remaining = n; | ||
| function done(err) { | ||
| assert.ifError(err); | ||
| if (--remaining === 0) | ||
| bench.end(n); | ||
| } | ||
| bench.start(); | ||
| for (let i = 0; i < n; ++i) | ||
| argon2(algorithm, { ...options, message, nonce, tagLength: 64 }, done); | ||
| } | ||
|
|
||
| function main({ n, mode, algorithm, ...options }) { | ||
| // Message, nonce, secret, associated data & tag length do not affect performance | ||
| const message = randomBytes(32); | ||
| const nonce = randomBytes(16); | ||
| if (mode === 'sync') | ||
| measureSync(n, algorithm, message, nonce, options); | ||
| else | ||
| measureAsync(n, algorithm, message, nonce, options); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,7 +10,12 @@ | |
| #include <algorithm> | ||
| #include <cstring> | ||
| #if OPENSSL_VERSION_MAJOR >= 3 | ||
| #include <openssl/core_names.h> | ||
| #include <openssl/params.h> | ||
| #include <openssl/provider.h> | ||
| #if OPENSSL_VERSION_MINOR >= 2 | ||
| #include <openssl/thread.h> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @codebytere ... can you confirm if there's any guards needed here for boring? |
||
| #endif | ||
| #endif | ||
| #if OPENSSL_WITH_PQC | ||
| struct PQCMapping { | ||
|
|
@@ -1868,6 +1873,100 @@ DataPointer pbkdf2(const Digest& md, | |
| return {}; | ||
| } | ||
|
|
||
| #if OPENSSL_VERSION_PREREQ(3, 2) | ||
| #ifndef OPENSSL_NO_ARGON2 | ||
| DataPointer argon2(const Buffer<const char>& pass, | ||
| const Buffer<const unsigned char>& salt, | ||
| uint32_t lanes, | ||
| size_t length, | ||
| uint32_t memcost, | ||
| uint32_t iter, | ||
| uint32_t version, | ||
| const Buffer<const unsigned char>& secret, | ||
| const Buffer<const unsigned char>& ad, | ||
| Argon2Type type) { | ||
| ClearErrorOnReturn clearErrorOnReturn; | ||
|
|
||
| std::string_view algorithm; | ||
| switch (type) { | ||
| case Argon2Type::ARGON2I: | ||
| algorithm = "ARGON2I"; | ||
| break; | ||
| case Argon2Type::ARGON2D: | ||
| algorithm = "ARGON2D"; | ||
| break; | ||
| case Argon2Type::ARGON2ID: | ||
| algorithm = "ARGON2ID"; | ||
| break; | ||
| default: | ||
| // Invalid Argon2 type | ||
| return {}; | ||
| } | ||
|
|
||
| // creates a new library context to avoid locking when running concurrently | ||
| auto ctx = DeleteFnPtr<OSSL_LIB_CTX, OSSL_LIB_CTX_free>{OSSL_LIB_CTX_new()}; | ||
| if (!ctx) { | ||
| return {}; | ||
| } | ||
|
|
||
| // required if threads > 1 | ||
| if (lanes > 1 && OSSL_set_max_threads(ctx.get(), lanes) != 1) { | ||
| return {}; | ||
| } | ||
|
|
||
| auto kdf = DeleteFnPtr<EVP_KDF, EVP_KDF_free>{ | ||
| EVP_KDF_fetch(ctx.get(), algorithm.data(), nullptr)}; | ||
| if (!kdf) { | ||
| return {}; | ||
| } | ||
|
|
||
| auto kctx = | ||
| DeleteFnPtr<EVP_KDF_CTX, EVP_KDF_CTX_free>{EVP_KDF_CTX_new(kdf.get())}; | ||
| if (!kctx) { | ||
| return {}; | ||
| } | ||
|
|
||
| std::vector<OSSL_PARAM> params; | ||
| params.reserve(9); | ||
|
|
||
| params.push_back(OSSL_PARAM_construct_octet_string( | ||
| OSSL_KDF_PARAM_PASSWORD, const_cast<char*>(pass.data), pass.len)); | ||
panva marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| params.push_back(OSSL_PARAM_construct_octet_string( | ||
| OSSL_KDF_PARAM_SALT, const_cast<unsigned char*>(salt.data), salt.len)); | ||
| params.push_back(OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_THREADS, &lanes)); | ||
| params.push_back( | ||
| OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_LANES, &lanes)); | ||
| params.push_back( | ||
| OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_MEMCOST, &memcost)); | ||
| params.push_back(OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ITER, &iter)); | ||
|
|
||
| if (ad.len != 0) { | ||
| params.push_back(OSSL_PARAM_construct_octet_string( | ||
| OSSL_KDF_PARAM_ARGON2_AD, const_cast<unsigned char*>(ad.data), ad.len)); | ||
| } | ||
|
|
||
| if (secret.len != 0) { | ||
| params.push_back(OSSL_PARAM_construct_octet_string( | ||
| OSSL_KDF_PARAM_SECRET, | ||
| const_cast<unsigned char*>(secret.data), | ||
| secret.len)); | ||
| } | ||
|
|
||
| params.push_back(OSSL_PARAM_construct_end()); | ||
|
|
||
| auto dp = DataPointer::Alloc(length); | ||
| if (dp && EVP_KDF_derive(kctx.get(), | ||
| reinterpret_cast<unsigned char*>(dp.get()), | ||
| length, | ||
| params.data()) == 1) { | ||
| return dp; | ||
| } | ||
|
|
||
| return {}; | ||
| } | ||
| #endif | ||
| #endif | ||
|
|
||
| // ============================================================================ | ||
|
|
||
| EVPKeyPointer::PrivateKeyEncodingConfig::PrivateKeyEncodingConfig( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2970,6 +2970,171 @@ Does not perform any other validation checks on the certificate. | |
|
|
||
| ## `node:crypto` module methods and properties | ||
|
|
||
| ### `crypto.argon2(algorithm, parameters, callback)` | ||
|
|
||
| <!-- YAML | ||
| added: REPLACEME | ||
Ethan-Arrowood marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| --> | ||
|
|
||
| > Stability: 1.2 - Release candidate | ||
|
|
||
| * `algorithm` {string} Variant of Argon2, one of `"argon2d"`, `"argon2i"` or `"argon2id"`. | ||
| * `parameters` {Object} | ||
| * `message` {string|ArrayBuffer|Buffer|TypedArray|DataView} REQUIRED, this is the password for password | ||
| hashing applications of Argon2. | ||
| * `nonce` {string|ArrayBuffer|Buffer|TypedArray|DataView} REQUIRED, must be at | ||
| least 8 bytes long. This is the salt for password hashing applications of Argon2. | ||
| * `parallelism` {number} REQUIRED, degree of parallelism determines how many computational chains (lanes) | ||
| can be run. Must be greater than 1 and less than `2**24-1`. | ||
| * `tagLength` {number} REQUIRED, the length of the key to generate. Must be greater than 4 and | ||
| less than `2**32-1`. | ||
| * `memory` {number} REQUIRED, memory cost in 1KiB blocks. Must be greater than | ||
| `8 * parallelism` and less than `2**32-1`. The actual number of blocks is rounded | ||
| down to the nearest multiple of `4 * parallelism`. | ||
| * `passes` {number} REQUIRED, number of passes (iterations). Must be greater than 1 and less | ||
| than `2**32-1`. | ||
| * `secret` {string|ArrayBuffer|Buffer|TypedArray|DataView|undefined} OPTIONAL, Random additional input, | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just found out about WICG/webcrypto-modern-algos and their specification for Is that something we want to follow, so that the implementations are closer?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, this implementation so far ignores the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cool, that looks a lot like the generic KDF proposal that was mentioned very early in the history of this PR. If you need a second pair of hands for anything let me know, I'm eager to participate 😄 This PR is otherwise complete, I believe. |
||
| similar to the salt, that should **NOT** be stored with the derived key. This is known as pepper in | ||
| password hashing applications. If used, must have a length not greater than `2**32-1` bytes. | ||
| * `associatedData` {string|ArrayBuffer|Buffer|TypedArray|DataView|undefined} OPTIONAL, Additional data to | ||
| be added to the hash, functionally equivalent to salt or secret, but meant for | ||
| non-random data. If used, must have a length not greater than `2**32-1` bytes. | ||
| * `callback` {Function} | ||
| * `err` {Error} | ||
| * `derivedKey` {Buffer} | ||
|
|
||
| Provides an asynchronous [Argon2][] implementation. Argon2 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 `nonce` should be as unique as possible. It is recommended that a nonce is | ||
| random and at least 16 bytes long. See [NIST SP 800-132][] for details. | ||
|
|
||
| When passing strings for `message`, `nonce`, `secret` or `associatedData`, please | ||
| consider [caveats when using strings as inputs to cryptographic APIs][]. | ||
|
|
||
| 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. | ||
|
|
||
| ```mjs | ||
| const { argon2, randomBytes } = await import('node:crypto'); | ||
Ethan-Arrowood marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| const parameters = { | ||
| message: 'password', | ||
| nonce: randomBytes(16), | ||
| parallelism: 4, | ||
| tagLength: 64, | ||
| memory: 65536, | ||
| passes: 3, | ||
| }; | ||
|
|
||
| argon2('argon2id', parameters, (err, derivedKey) => { | ||
| if (err) throw err; | ||
| console.log(derivedKey.toString('hex')); // 'af91dad...9520f15' | ||
| }); | ||
| ``` | ||
|
|
||
| ```cjs | ||
| const { argon2, randomBytes } = require('node:crypto'); | ||
|
|
||
| const parameters = { | ||
| message: 'password', | ||
| nonce: randomBytes(16), | ||
| parallelism: 4, | ||
| tagLength: 64, | ||
| memory: 65536, | ||
| passes: 3, | ||
| }; | ||
|
|
||
| argon2('argon2id', parameters, (err, derivedKey) => { | ||
| if (err) throw err; | ||
| console.log(derivedKey.toString('hex')); // 'af91dad...9520f15' | ||
| }); | ||
| ``` | ||
|
|
||
| ### `crypto.argon2Sync(algorithm, parameters)` | ||
|
|
||
| <!-- YAML | ||
| added: REPLACEME | ||
| --> | ||
|
|
||
| > Stability: 1.2 - Release candidate | ||
|
|
||
| * `algorithm` {string} Variant of Argon2, one of `"argon2d"`, `"argon2i"` or `"argon2id"`. | ||
| * `parameters` {Object} | ||
| * `message` {string|ArrayBuffer|Buffer|TypedArray|DataView} REQUIRED, this is the password for password | ||
| hashing applications of Argon2. | ||
| * `nonce` {string|ArrayBuffer|Buffer|TypedArray|DataView} REQUIRED, must be at | ||
| least 8 bytes long. This is the salt for password hashing applications of Argon2. | ||
| * `parallelism` {number} REQUIRED, degree of parallelism determines how many computational chains (lanes) | ||
| can be run. Must be greater than 1 and less than `2**24-1`. | ||
| * `tagLength` {number} REQUIRED, the length of the key to generate. Must be greater than 4 and | ||
| less than `2**32-1`. | ||
| * `memory` {number} REQUIRED, memory cost in 1KiB blocks. Must be greater than | ||
| `8 * parallelism` and less than `2**32-1`. The actual number of blocks is rounded | ||
| down to the nearest multiple of `4 * parallelism`. | ||
| * `passes` {number} REQUIRED, number of passes (iterations). Must be greater than 1 and less | ||
| than `2**32-1`. | ||
| * `secret` {string|ArrayBuffer|Buffer|TypedArray|DataView|undefined} OPTIONAL, Random additional input, | ||
| similar to the salt, that should **NOT** be stored with the derived key. This is known as pepper in | ||
| password hashing applications. If used, must have a length not greater than `2**32-1` bytes. | ||
| * `associatedData` {string|ArrayBuffer|Buffer|TypedArray|DataView|undefined} OPTIONAL, Additional data to | ||
| be added to the hash, functionally equivalent to salt or secret, but meant for | ||
| non-random data. If used, must have a length not greater than `2**32-1` bytes. | ||
| * Returns: {Buffer} | ||
|
|
||
| Provides a synchronous [Argon2][] implementation. Argon2 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 `nonce` should be as unique as possible. It is recommended that a nonce is | ||
| random and at least 16 bytes long. See [NIST SP 800-132][] for details. | ||
|
|
||
| When passing strings for `message`, `nonce`, `secret` or `associatedData`, please | ||
| consider [caveats when using strings as inputs to cryptographic APIs][]. | ||
|
|
||
| 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. | ||
|
|
||
| ```mjs | ||
| const { argon2Sync, randomBytes } = await import('node:crypto'); | ||
|
|
||
| const parameters = { | ||
| message: 'password', | ||
| nonce: randomBytes(16), | ||
| parallelism: 4, | ||
| tagLength: 64, | ||
| memory: 65536, | ||
| passes: 3, | ||
| }; | ||
|
|
||
| const derivedKey = argon2Sync('argon2id', parameters); | ||
| console.log(derivedKey.toString('hex')); // 'af91dad...9520f15' | ||
| ``` | ||
|
|
||
| ```cjs | ||
| const { argon2Sync, randomBytes } = require('node:crypto'); | ||
|
|
||
| const parameters = { | ||
| message: 'password', | ||
| nonce: randomBytes(16), | ||
| parallelism: 4, | ||
| tagLength: 64, | ||
| memory: 65536, | ||
| passes: 3, | ||
| }; | ||
|
|
||
| const derivedKey = argon2Sync('argon2id', parameters); | ||
| console.log(derivedKey.toString('hex')); // 'af91dad...9520f15' | ||
| ``` | ||
|
|
||
| ### `crypto.checkPrime(candidate[, options], callback)` | ||
|
|
||
| <!-- YAML | ||
|
|
@@ -6284,6 +6449,7 @@ See the [list of SSL OP Flags][] for details. | |
| [`verify.verify()`]: #verifyverifyobject-signature-signatureencoding | ||
| [`x509.fingerprint256`]: #x509fingerprint256 | ||
| [`x509.verify(publicKey)`]: #x509verifypublickey | ||
| [argon2]: https://www.rfc-editor.org/rfc/rfc9106.html | ||
| [asymmetric key types]: #asymmetric-key-types | ||
| [caveats when using strings as inputs to cryptographic APIs]: #using-strings-as-inputs-to-cryptographic-apis | ||
| [certificate object]: tls.md#certificate-object | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.