crypto: add argon2() and argon2Sync() methods · nodejs/node@d3afc63 · GitHub
Skip to content

Commit d3afc63

Browse files
ranisaltpanvajasnell
authored andcommitted
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 e6a6cdb commit d3afc63

20 files changed

Lines changed: 983 additions & 19 deletions

benchmark/common.js

Lines changed: 3 additions & 0 deletions

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

0 commit comments

Comments
 (0)