Skip to content

Commit bdcab71

Browse files
ranisaltpanvajasnell
authored
crypto: add argon2() and argon2Sync() methods
Co-authored-by: Filip Skokan <panva.ip@gmail.com> Co-authored-by: James M Snell <jasnell@gmail.com> PR-URL: #50353 Reviewed-By: Ethan Arrowood <ethan@arrowood.dev> Reviewed-By: Filip Skokan <panva.ip@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent ef58be6 commit bdcab71

20 files changed

+983
-19
lines changed

benchmark/common.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ class Benchmark {
8181
if (typeof value === 'number') {
8282
if (key === 'dur' || key === 'duration') {
8383
value = 0.05;
84+
} else if (key === 'memory') {
85+
// minimum Argon2 memcost with 1 lane is 8
86+
value = 8;
8487
} else if (value > 1) {
8588
value = 1;
8689
}

benchmark/crypto/argon2.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
'use strict';
2+
3+
const common = require('../common.js');
4+
const { hasOpenSSL } = require('../../test/common/crypto.js');
5+
const assert = require('node:assert');
6+
const {
7+
argon2,
8+
argon2Sync,
9+
randomBytes,
10+
} = require('node:crypto');
11+
12+
if (!hasOpenSSL(3, 2)) {
13+
console.log('Skipping: Argon2 requires OpenSSL >= 3.2');
14+
process.exit(0);
15+
}
16+
17+
const bench = common.createBenchmark(main, {
18+
mode: ['sync', 'async'],
19+
algorithm: ['argon2d', 'argon2i', 'argon2id'],
20+
passes: [1, 3],
21+
parallelism: [2, 4, 8],
22+
memory: [2 ** 11, 2 ** 16, 2 ** 21],
23+
n: [50],
24+
});
25+
26+
function measureSync(n, algorithm, message, nonce, options) {
27+
bench.start();
28+
for (let i = 0; i < n; ++i)
29+
argon2Sync(algorithm, { ...options, message, nonce, tagLength: 64 });
30+
bench.end(n);
31+
}
32+
33+
function measureAsync(n, algorithm, message, nonce, options) {
34+
let remaining = n;
35+
function done(err) {
36+
assert.ifError(err);
37+
if (--remaining === 0)
38+
bench.end(n);
39+
}
40+
bench.start();
41+
for (let i = 0; i < n; ++i)
42+
argon2(algorithm, { ...options, message, nonce, tagLength: 64 }, done);
43+
}
44+
45+
function main({ n, mode, algorithm, ...options }) {
46+
// Message, nonce, secret, associated data & tag length do not affect performance
47+
const message = randomBytes(32);
48+
const nonce = randomBytes(16);
49+
if (mode === 'sync')
50+
measureSync(n, algorithm, message, nonce, options);
51+
else
52+
measureAsync(n, algorithm, message, nonce, options);
53+
}

deps/ncrypto/ncrypto.cc

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@
1010
#include <algorithm>
1111
#include <cstring>
1212
#if OPENSSL_VERSION_MAJOR >= 3
13+
#include <openssl/core_names.h>
14+
#include <openssl/params.h>
1315
#include <openssl/provider.h>
16+
#if OPENSSL_VERSION_NUMBER >= 0x30200000L
17+
#include <openssl/thread.h>
18+
#endif
1419
#endif
1520
#if OPENSSL_WITH_PQC
1621
struct PQCMapping {
@@ -1868,6 +1873,102 @@ DataPointer pbkdf2(const Digest& md,
18681873
return {};
18691874
}
18701875

1876+
#if OPENSSL_VERSION_NUMBER >= 0x30200000L
1877+
#ifndef OPENSSL_NO_ARGON2
1878+
DataPointer argon2(const Buffer<const char>& pass,
1879+
const Buffer<const unsigned char>& salt,
1880+
uint32_t lanes,
1881+
size_t length,
1882+
uint32_t memcost,
1883+
uint32_t iter,
1884+
uint32_t version,
1885+
const Buffer<const unsigned char>& secret,
1886+
const Buffer<const unsigned char>& ad,
1887+
Argon2Type type) {
1888+
ClearErrorOnReturn clearErrorOnReturn;
1889+
1890+
std::string_view algorithm;
1891+
switch (type) {
1892+
case Argon2Type::ARGON2I:
1893+
algorithm = "ARGON2I";
1894+
break;
1895+
case Argon2Type::ARGON2D:
1896+
algorithm = "ARGON2D";
1897+
break;
1898+
case Argon2Type::ARGON2ID:
1899+
algorithm = "ARGON2ID";
1900+
break;
1901+
default:
1902+
// Invalid Argon2 type
1903+
return {};
1904+
}
1905+
1906+
// creates a new library context to avoid locking when running concurrently
1907+
auto ctx = DeleteFnPtr<OSSL_LIB_CTX, OSSL_LIB_CTX_free>{OSSL_LIB_CTX_new()};
1908+
if (!ctx) {
1909+
return {};
1910+
}
1911+
1912+
// required if threads > 1
1913+
if (lanes > 1 && OSSL_set_max_threads(ctx.get(), lanes) != 1) {
1914+
return {};
1915+
}
1916+
1917+
auto kdf = DeleteFnPtr<EVP_KDF, EVP_KDF_free>{
1918+
EVP_KDF_fetch(ctx.get(), algorithm.data(), nullptr)};
1919+
if (!kdf) {
1920+
return {};
1921+
}
1922+
1923+
auto kctx =
1924+
DeleteFnPtr<EVP_KDF_CTX, EVP_KDF_CTX_free>{EVP_KDF_CTX_new(kdf.get())};
1925+
if (!kctx) {
1926+
return {};
1927+
}
1928+
1929+
std::vector<OSSL_PARAM> params;
1930+
params.reserve(9);
1931+
1932+
params.push_back(OSSL_PARAM_construct_octet_string(
1933+
OSSL_KDF_PARAM_PASSWORD,
1934+
const_cast<char*>(pass.len > 0 ? pass.data : ""),
1935+
pass.len));
1936+
params.push_back(OSSL_PARAM_construct_octet_string(
1937+
OSSL_KDF_PARAM_SALT, const_cast<unsigned char*>(salt.data), salt.len));
1938+
params.push_back(OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_THREADS, &lanes));
1939+
params.push_back(
1940+
OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_LANES, &lanes));
1941+
params.push_back(
1942+
OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_MEMCOST, &memcost));
1943+
params.push_back(OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ITER, &iter));
1944+
1945+
if (ad.len != 0) {
1946+
params.push_back(OSSL_PARAM_construct_octet_string(
1947+
OSSL_KDF_PARAM_ARGON2_AD, const_cast<unsigned char*>(ad.data), ad.len));
1948+
}
1949+
1950+
if (secret.len != 0) {
1951+
params.push_back(OSSL_PARAM_construct_octet_string(
1952+
OSSL_KDF_PARAM_SECRET,
1953+
const_cast<unsigned char*>(secret.data),
1954+
secret.len));
1955+
}
1956+
1957+
params.push_back(OSSL_PARAM_construct_end());
1958+
1959+
auto dp = DataPointer::Alloc(length);
1960+
if (dp && EVP_KDF_derive(kctx.get(),
1961+
reinterpret_cast<unsigned char*>(dp.get()),
1962+
length,
1963+
params.data()) == 1) {
1964+
return dp;
1965+
}
1966+
1967+
return {};
1968+
}
1969+
#endif
1970+
#endif
1971+
18711972
// ============================================================================
18721973

18731974
EVPKeyPointer::PrivateKeyEncodingConfig::PrivateKeyEncodingConfig(

deps/ncrypto/ncrypto.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1557,6 +1557,23 @@ DataPointer pbkdf2(const Digest& md,
15571557
uint32_t iterations,
15581558
size_t length);
15591559

1560+
#if OPENSSL_VERSION_NUMBER >= 0x30200000L
1561+
#ifndef OPENSSL_NO_ARGON2
1562+
enum class Argon2Type { ARGON2D, ARGON2I, ARGON2ID };
1563+
1564+
DataPointer argon2(const Buffer<const char>& pass,
1565+
const Buffer<const unsigned char>& salt,
1566+
uint32_t lanes,
1567+
size_t length,
1568+
uint32_t memcost,
1569+
uint32_t iter,
1570+
uint32_t version,
1571+
const Buffer<const unsigned char>& secret,
1572+
const Buffer<const unsigned char>& ad,
1573+
Argon2Type type);
1574+
#endif
1575+
#endif
1576+
15601577
// ============================================================================
15611578
// Version metadata
15621579
#define NCRYPTO_VERSION "0.0.1"

doc/api/crypto.md

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2970,6 +2970,171 @@ Does not perform any other validation checks on the certificate.
29702970

29712971
## `node:crypto` module methods and properties
29722972

2973+
### `crypto.argon2(algorithm, parameters, callback)`
2974+
2975+
<!-- YAML
2976+
added: REPLACEME
2977+
-->
2978+
2979+
> Stability: 1.2 - Release candidate
2980+
2981+
* `algorithm` {string} Variant of Argon2, one of `"argon2d"`, `"argon2i"` or `"argon2id"`.
2982+
* `parameters` {Object}
2983+
* `message` {string|ArrayBuffer|Buffer|TypedArray|DataView} REQUIRED, this is the password for password
2984+
hashing applications of Argon2.
2985+
* `nonce` {string|ArrayBuffer|Buffer|TypedArray|DataView} REQUIRED, must be at
2986+
least 8 bytes long. This is the salt for password hashing applications of Argon2.
2987+
* `parallelism` {number} REQUIRED, degree of parallelism determines how many computational chains (lanes)
2988+
can be run. Must be greater than 1 and less than `2**24-1`.
2989+
* `tagLength` {number} REQUIRED, the length of the key to generate. Must be greater than 4 and
2990+
less than `2**32-1`.
2991+
* `memory` {number} REQUIRED, memory cost in 1KiB blocks. Must be greater than
2992+
`8 * parallelism` and less than `2**32-1`. The actual number of blocks is rounded
2993+
down to the nearest multiple of `4 * parallelism`.
2994+
* `passes` {number} REQUIRED, number of passes (iterations). Must be greater than 1 and less
2995+
than `2**32-1`.
2996+
* `secret` {string|ArrayBuffer|Buffer|TypedArray|DataView|undefined} OPTIONAL, Random additional input,
2997+
similar to the salt, that should **NOT** be stored with the derived key. This is known as pepper in
2998+
password hashing applications. If used, must have a length not greater than `2**32-1` bytes.
2999+
* `associatedData` {string|ArrayBuffer|Buffer|TypedArray|DataView|undefined} OPTIONAL, Additional data to
3000+
be added to the hash, functionally equivalent to salt or secret, but meant for
3001+
non-random data. If used, must have a length not greater than `2**32-1` bytes.
3002+
* `callback` {Function}
3003+
* `err` {Error}
3004+
* `derivedKey` {Buffer}
3005+
3006+
Provides an asynchronous [Argon2][] implementation. Argon2 is a password-based
3007+
key derivation function that is designed to be expensive computationally and
3008+
memory-wise in order to make brute-force attacks unrewarding.
3009+
3010+
The `nonce` should be as unique as possible. It is recommended that a nonce is
3011+
random and at least 16 bytes long. See [NIST SP 800-132][] for details.
3012+
3013+
When passing strings for `message`, `nonce`, `secret` or `associatedData`, please
3014+
consider [caveats when using strings as inputs to cryptographic APIs][].
3015+
3016+
The `callback` function is called with two arguments: `err` and `derivedKey`.
3017+
`err` is an exception object when key derivation fails, otherwise `err` is
3018+
`null`. `derivedKey` is passed to the callback as a [`Buffer`][].
3019+
3020+
An exception is thrown when any of the input arguments specify invalid values
3021+
or types.
3022+
3023+
```mjs
3024+
const { argon2, randomBytes } = await import('node:crypto');
3025+
3026+
const parameters = {
3027+
message: 'password',
3028+
nonce: randomBytes(16),
3029+
parallelism: 4,
3030+
tagLength: 64,
3031+
memory: 65536,
3032+
passes: 3,
3033+
};
3034+
3035+
argon2('argon2id', parameters, (err, derivedKey) => {
3036+
if (err) throw err;
3037+
console.log(derivedKey.toString('hex')); // 'af91dad...9520f15'
3038+
});
3039+
```
3040+
3041+
```cjs
3042+
const { argon2, randomBytes } = require('node:crypto');
3043+
3044+
const parameters = {
3045+
message: 'password',
3046+
nonce: randomBytes(16),
3047+
parallelism: 4,
3048+
tagLength: 64,
3049+
memory: 65536,
3050+
passes: 3,
3051+
};
3052+
3053+
argon2('argon2id', parameters, (err, derivedKey) => {
3054+
if (err) throw err;
3055+
console.log(derivedKey.toString('hex')); // 'af91dad...9520f15'
3056+
});
3057+
```
3058+
3059+
### `crypto.argon2Sync(algorithm, parameters)`
3060+
3061+
<!-- YAML
3062+
added: REPLACEME
3063+
-->
3064+
3065+
> Stability: 1.2 - Release candidate
3066+
3067+
* `algorithm` {string} Variant of Argon2, one of `"argon2d"`, `"argon2i"` or `"argon2id"`.
3068+
* `parameters` {Object}
3069+
* `message` {string|ArrayBuffer|Buffer|TypedArray|DataView} REQUIRED, this is the password for password
3070+
hashing applications of Argon2.
3071+
* `nonce` {string|ArrayBuffer|Buffer|TypedArray|DataView} REQUIRED, must be at
3072+
least 8 bytes long. This is the salt for password hashing applications of Argon2.
3073+
* `parallelism` {number} REQUIRED, degree of parallelism determines how many computational chains (lanes)
3074+
can be run. Must be greater than 1 and less than `2**24-1`.
3075+
* `tagLength` {number} REQUIRED, the length of the key to generate. Must be greater than 4 and
3076+
less than `2**32-1`.
3077+
* `memory` {number} REQUIRED, memory cost in 1KiB blocks. Must be greater than
3078+
`8 * parallelism` and less than `2**32-1`. The actual number of blocks is rounded
3079+
down to the nearest multiple of `4 * parallelism`.
3080+
* `passes` {number} REQUIRED, number of passes (iterations). Must be greater than 1 and less
3081+
than `2**32-1`.
3082+
* `secret` {string|ArrayBuffer|Buffer|TypedArray|DataView|undefined} OPTIONAL, Random additional input,
3083+
similar to the salt, that should **NOT** be stored with the derived key. This is known as pepper in
3084+
password hashing applications. If used, must have a length not greater than `2**32-1` bytes.
3085+
* `associatedData` {string|ArrayBuffer|Buffer|TypedArray|DataView|undefined} OPTIONAL, Additional data to
3086+
be added to the hash, functionally equivalent to salt or secret, but meant for
3087+
non-random data. If used, must have a length not greater than `2**32-1` bytes.
3088+
* Returns: {Buffer}
3089+
3090+
Provides a synchronous [Argon2][] implementation. Argon2 is a password-based
3091+
key derivation function that is designed to be expensive computationally and
3092+
memory-wise in order to make brute-force attacks unrewarding.
3093+
3094+
The `nonce` should be as unique as possible. It is recommended that a nonce is
3095+
random and at least 16 bytes long. See [NIST SP 800-132][] for details.
3096+
3097+
When passing strings for `message`, `nonce`, `secret` or `associatedData`, please
3098+
consider [caveats when using strings as inputs to cryptographic APIs][].
3099+
3100+
An exception is thrown when key derivation fails, otherwise the derived key is
3101+
returned as a [`Buffer`][].
3102+
3103+
An exception is thrown when any of the input arguments specify invalid values
3104+
or types.
3105+
3106+
```mjs
3107+
const { argon2Sync, randomBytes } = await import('node:crypto');
3108+
3109+
const parameters = {
3110+
message: 'password',
3111+
nonce: randomBytes(16),
3112+
parallelism: 4,
3113+
tagLength: 64,
3114+
memory: 65536,
3115+
passes: 3,
3116+
};
3117+
3118+
const derivedKey = argon2Sync('argon2id', parameters);
3119+
console.log(derivedKey.toString('hex')); // 'af91dad...9520f15'
3120+
```
3121+
3122+
```cjs
3123+
const { argon2Sync, randomBytes } = require('node:crypto');
3124+
3125+
const parameters = {
3126+
message: 'password',
3127+
nonce: randomBytes(16),
3128+
parallelism: 4,
3129+
tagLength: 64,
3130+
memory: 65536,
3131+
passes: 3,
3132+
};
3133+
3134+
const derivedKey = argon2Sync('argon2id', parameters);
3135+
console.log(derivedKey.toString('hex')); // 'af91dad...9520f15'
3136+
```
3137+
29733138
### `crypto.checkPrime(candidate[, options], callback)`
29743139

29753140
<!-- YAML
@@ -6284,6 +6449,7 @@ See the [list of SSL OP Flags][] for details.
62846449
[`verify.verify()`]: #verifyverifyobject-signature-signatureencoding
62856450
[`x509.fingerprint256`]: #x509fingerprint256
62866451
[`x509.verify(publicKey)`]: #x509verifypublickey
6452+
[argon2]: https://www.rfc-editor.org/rfc/rfc9106.html
62876453
[asymmetric key types]: #asymmetric-key-types
62886454
[caveats when using strings as inputs to cryptographic APIs]: #using-strings-as-inputs-to-cryptographic-apis
62896455
[certificate object]: tls.md#certificate-object

doc/api/errors.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,12 @@ when an error occurs (and is caught) during the creation of the
826826
context, for example, when the allocation fails or the maximum call stack
827827
size is reached when the context is created.
828828

829+
<a id="ERR_CRYPTO_ARGON2_NOT_SUPPORTED"></a>
830+
831+
### `ERR_CRYPTO_ARGON2_NOT_SUPPORTED`
832+
833+
Argon2 is not supported by the current version of OpenSSL being used.
834+
829835
<a id="ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED"></a>
830836

831837
### `ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED`

0 commit comments

Comments
 (0)