Skip to content
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

crypto: add argon2() and argon2Sync() methods #50353

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions benchmark/crypto/argon2.js
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, {
sync: [0, 1],
algorithm: ['ARGON2D', 'ARGON2I', 'ARGON2ID'],
iter: [1, 3],
lanes: [2, 4, 8],
memcost: [2 ** 11, 2 ** 16, 2 ** 21],
n: [50],
});

function measureSync(n, pass, salt, options) {
bench.start();
for (let i = 0; i < n; ++i)
argon2Sync(pass, salt, 64, options);
bench.end(n);
}

function measureAsync(n, pass, salt, 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(pass, salt, 64, options, done);
}

function main({ n, sync, ...options }) {
// pass, salt, secret, ad & output length does not affect performance
const pass = randomBytes(32);
const salt = randomBytes(16);
if (sync)
measureSync(n, pass, salt, options);
else
measureAsync(n, pass, salt, options);
}
4 changes: 4 additions & 0 deletions deps/ncrypto/ncrypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,13 @@ using ECGroupPointer = DeleteFnPtr<EC_GROUP, EC_GROUP_free>;
using ECKeyPointer = DeleteFnPtr<EC_KEY, EC_KEY_free>;
using ECPointPointer = DeleteFnPtr<EC_POINT, EC_POINT_free>;
using EVPKeyCtxPointer = DeleteFnPtr<EVP_PKEY_CTX, EVP_PKEY_CTX_free>;
using EVPKeyPointer = DeleteFnPtr<EVP_PKEY, EVP_PKEY_free>;
using EVPKdfCtxPointer = DeleteFnPtr<EVP_KDF_CTX, EVP_KDF_CTX_free>;
using EVPKdfPointer = DeleteFnPtr<EVP_KDF, EVP_KDF_free>;
using EVPMDCtxPointer = DeleteFnPtr<EVP_MD_CTX, EVP_MD_CTX_free>;
using HMACCtxPointer = DeleteFnPtr<HMAC_CTX, HMAC_CTX_free>;
using NetscapeSPKIPointer = DeleteFnPtr<NETSCAPE_SPKI, NETSCAPE_SPKI_free>;
using OsslLibCtxPointer = DeleteFnPtr<OSSL_LIB_CTX, OSSL_LIB_CTX_free>;
using PKCS8Pointer = DeleteFnPtr<PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO_free>;
using RSAPointer = DeleteFnPtr<RSA, RSA_free>;
using SSLCtxPointer = DeleteFnPtr<SSL_CTX, SSL_CTX_free>;
Expand Down
155 changes: 155 additions & 0 deletions doc/api/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -2960,6 +2960,160 @@ is currently in use. Setting to true requires a FIPS build of Node.js.
This property is deprecated. Please use `crypto.setFips()` and
`crypto.getFips()` instead.

### `crypto.argon2(password, salt, keylen[, options], callback)`

<!-- YAML
added: REPLACEME
-->

* `password` {string|ArrayBuffer|Buffer|TypedArray|DataView}
* `salt` {string|ArrayBuffer|Buffer|TypedArray|DataView}
* `keylen` {number}
* `options` {Object}
* `algorithm` {string} Variant of Argon2, one of "ARGON2D", "ARGON2I" or
"ARGON2ID". **Default:** `"ARGON2ID"`.
* `iter` {number} Number of iterations (passes). **Default:** `3`.
* `lanes` {number} Parallelization parameter (number of threads). **Default:** `4`.
* `memcost` {number} Memory cost in 1KiB blocks. **Default:** `65536`.
* `secret` {string|ArrayBuffer|Buffer|TypedArray|DataView} Random additional
input, similar to the salt, that should **NOT** be stored with the derived
key. Also known as a pepper.
* `ad` {string|ArrayBuffer|Buffer|TypedArray|DataView} Additional data to be
added to the hash, functionally equivalent to salt or secret, but meant for
non-random data.
* `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 `salt` should be as unique as possible. It is recommended that a salt is
random and at least 16 bytes long. See [NIST SP 800-132][] for details.

When passing strings for `password`, `salt`, `secret` or `ad`, 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');

const salt = randomBytes(16);
// Using the factory defaults.
argon2('password', salt, 64, (err, derivedKey) => {
if (err) throw err;
console.log(derivedKey.toString('hex')); // '0de3036...22afcc5'
});
// Using a custom iter parameter.
argon2('password', salt, 64, { iter: 3 }, (err, derivedKey) => {
if (err) throw err;
console.log(derivedKey.toString('hex')); // '0de3036...22afcc5'
});
```

```cjs
const {
argon2,
randomBytes,
} = require('node:crypto');

// Using the factory defaults.
randomBytes(16, (err, salt) => {
if (err) throw err;
argon2('password', salt, 64, (err, derivedKey) => {
if (err) throw err;
console.log(derivedKey.toString('hex')); // '0de3036...22afcc5'
});
});
// Using a custom iter parameter.
randomBytes(16, (err, salt) => {
if (err) throw err;
argon2('password', salt, 64, { iter: 3 }, (err, derivedKey) => {
if (err) throw err;
console.log(derivedKey.toString('hex')); // '0de3036...22afcc5'
});
});
```

### `crypto.argon2Sync(password, salt, keylen[, options])`

<!-- YAML
added: REPLACEME
-->

* `password` {string|Buffer|TypedArray|DataView}
* `salt` {string|Buffer|TypedArray|DataView}
* `keylen` {number}
* `options` {Object}
* `algorithm` {string} Variant of Argon2, one of "ARGON2D", "ARGON2I" or
"ARGON2ID". **Default:** `"ARGON2ID"`.
* `iter` {number} Number of iterations (passes). **Default:** `3`.
* `lanes` {number} Parallelization parameter (number of threads). **Default:** `4`.
* `memcost` {number} Memory cost in 1KiB blocks. **Default:** `65536`.
* `secret` {string|Buffer|TypedArray|DataView} Random additional input,
similar to the salt, that should **NOT** be stored with the derived key.
Also known as a pepper.
* `ad` {string|Buffer|TypedArray|DataView} Additional data to be added to the
hash, functionally equivalent to salt or secret, but meant for non-random
data.
* 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 `salt` should be as unique as possible. It is recommended that a salt is
random and at least 16 bytes long. See [NIST SP 800-132][] for details.

When passing strings for `password`, `salt`, `secret` or `ad`, 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');
// Using the factory defaults.

const salt = randomBytes(16);
const key1 = argon2Sync('password', salt, 64);
console.log(key1.toString('hex')); // '3745e48...08d59ae'
// Using a custom iter parameter.
const key2 = argon2Sync('password', salt, 64, { iter: 3 });
console.log(key2.toString('hex')); // '3745e48...aa39b34'
```

```cjs
const {
argon2Sync,
randomBytes,
} = require('node:crypto');
// Using the factory defaults.

const salt = randomBytes(16);
const key1 = argon2Sync('password', salt, 64);
console.log(key1.toString('hex')); // '3745e48...08d59ae'
// Using a custom iter parameter.
const key2 = argon2Sync('password', salt, 64, { iter: 3 });
console.log(key2.toString('hex')); // '3745e48...aa39b34'
```

### `crypto.checkPrime(candidate[, options], callback)`

<!-- YAML
Expand Down Expand Up @@ -6182,6 +6336,7 @@ See the [list of SSL OP Flags][] for details.
[`verify.update()`]: #verifyupdatedata-inputencoding
[`verify.verify()`]: #verifyverifyobject-signature-signatureencoding
[`x509.fingerprint256`]: #x509fingerprint256
[argon2]: https://en.wikipedia.org/wiki/Argon2
[caveats when using strings as inputs to cryptographic APIs]: #using-strings-as-inputs-to-cryptographic-apis
[certificate object]: tls.md#certificate-object
[encoding]: buffer.md#buffers-and-character-encodings
Expand Down
6 changes: 6 additions & 0 deletions lib/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ const {
randomInt,
randomUUID,
} = require('internal/crypto/random');
const {
argon2,
argon2Sync,
} = require('internal/crypto/argon2');
const {
pbkdf2,
pbkdf2Sync,
Expand Down Expand Up @@ -173,6 +177,8 @@ function createVerify(algorithm, options) {

module.exports = {
// Methods
argon2,
argon2Sync,
checkPrime,
checkPrimeSync,
createCipheriv,
Expand Down
Loading