@zudojs/crypto 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/dist/cryptoConstants/cryptoConstants.security.d.ts +16 -4
- package/dist/cryptoConstants/cryptoConstants.security.js +16 -4
- package/dist/cryptoPassword/cryptoPassword.core.js +3 -1
- package/dist/cryptoPassword/cryptoPassword.helper.d.ts +11 -0
- package/dist/cryptoPassword/cryptoPassword.helper.js +16 -1
- package/dist/node/nodeCryptoProvider/operations/nodeCryptoProvider.password.d.ts +4 -3
- package/dist/node/nodeCryptoProvider/operations/nodeCryptoProvider.password.js +7 -4
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Cryptographic primitives for hashing, authenticated encryption, password hashing, digital signatures, key derivation, opaque tokens, and secure random generation. Everything is backed by `node:crypto` through a swappable provider.
|
|
4
4
|
|
|
5
|
+
<!-- zudo-docs:start -->
|
|
6
|
+
|
|
7
|
+
**Documentation:** [zudojs.oyinlola.site/docs/packages-crypto](https://zudojs.oyinlola.site/docs/packages-crypto) · **For AI agents:** [Markdown version](https://zudojs.oyinlola.site/docs/packages-crypto.md), [llms.txt](https://zudojs.oyinlola.site/llms.txt)
|
|
8
|
+
|
|
9
|
+
<!-- zudo-docs:end -->
|
|
10
|
+
|
|
5
11
|
## Installation
|
|
6
12
|
|
|
7
13
|
```bash
|
|
@@ -42,7 +48,7 @@ const sessionToken = await generateToken({ bytes: 32, prefix: "sess_" });
|
|
|
42
48
|
|
|
43
49
|
- Hashing: SHA-256/384/512 and SHA3-256/384/512, HMAC (keys of at least 16 bytes)
|
|
44
50
|
- Authenticated encryption: AES-256-GCM with strict IV (12 bytes) and tag (16 bytes) validation, plus a versioned string envelope
|
|
45
|
-
- Password hashing: scrypt (default, OWASP
|
|
51
|
+
- Password hashing: scrypt (default N=2^14, r=8, p=5 — the OWASP row for that N) and PBKDF2-HMAC (provider level), versioned self-describing encoding, bounded parameters on verification. New hashes must use a cost of at least `PASSWORD_HASH.SCRYPT.MIN_COST` (16 384); older stored hashes with a smaller cost still verify. Key derivation (`deriveScrypt`) keeps p=1 by default, so derived keys do not change
|
|
46
52
|
- Key derivation: PBKDF2 (sha256/384/512, 600 000 iterations by default) and scrypt (cost, block size, parallelization, memory bound); every work factor and the output length are capped by `PASSWORD_HASH.LIMITS`, so a value read from configuration cannot request unbounded CPU or memory
|
|
47
53
|
- Digital signatures: Ed25519, RSA-SHA256/384/512, ECDSA-SHA256/384/512; the algorithm label is bound to the key type
|
|
48
54
|
- Secure random: unbiased integers up to 2^48, UUID v4, bytes, alphabets, numeric codes
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Password hashing constants.
|
|
3
3
|
*
|
|
4
|
-
* Defaults follow
|
|
5
|
-
*
|
|
6
|
-
* iterations. `LIMITS` bounds parameters
|
|
7
|
-
* hashes so that a tampered or foreign
|
|
4
|
+
* Defaults follow the OWASP Password Storage Cheat Sheet: scrypt N=2^14,
|
|
5
|
+
* r=8, p=5 (OWASP's N=2^14 row; p=1 is only adequate at N=2^17) and
|
|
6
|
+
* PBKDF2-HMAC-SHA256 with 600 000 iterations. `LIMITS` bounds parameters
|
|
7
|
+
* that are read back from stored hashes so that a tampered or foreign
|
|
8
|
+
* record cannot force unbounded work.
|
|
9
|
+
*
|
|
10
|
+
* New hashes are held to `SCRYPT.MIN_COST`; verification still accepts
|
|
11
|
+
* older stored hashes down to `LIMITS.MIN_SCRYPT_COST`.
|
|
8
12
|
*/
|
|
9
13
|
export declare const PASSWORD_HASH: Readonly<{
|
|
10
14
|
readonly SALT_BYTES: 16;
|
|
@@ -12,7 +16,15 @@ export declare const PASSWORD_HASH: Readonly<{
|
|
|
12
16
|
readonly SCRYPT: Readonly<{
|
|
13
17
|
COST: 16384;
|
|
14
18
|
BLOCK_SIZE: 8;
|
|
19
|
+
/**
|
|
20
|
+
* Default `p` for key derivation (`deriveScrypt`). Left at 1: changing
|
|
21
|
+
* it would silently change every key derived with default options.
|
|
22
|
+
*/
|
|
15
23
|
PARALLELIZATION: 1;
|
|
24
|
+
/** Default `p` for new *password hashes* (OWASP N=2^14, r=8, p=5). */
|
|
25
|
+
PASSWORD_PARALLELIZATION: 5;
|
|
26
|
+
/** Floor on the cost of *new* hashes; `LIMITS.MIN_SCRYPT_COST` bounds verification. */
|
|
27
|
+
MIN_COST: 16384;
|
|
16
28
|
}>;
|
|
17
29
|
readonly PBKDF2: Readonly<{
|
|
18
30
|
ITERATIONS: 600000;
|
|
@@ -2,10 +2,14 @@ import { TimeMs } from "@zudojs/constants";
|
|
|
2
2
|
/**
|
|
3
3
|
* Password hashing constants.
|
|
4
4
|
*
|
|
5
|
-
* Defaults follow
|
|
6
|
-
*
|
|
7
|
-
* iterations. `LIMITS` bounds parameters
|
|
8
|
-
* hashes so that a tampered or foreign
|
|
5
|
+
* Defaults follow the OWASP Password Storage Cheat Sheet: scrypt N=2^14,
|
|
6
|
+
* r=8, p=5 (OWASP's N=2^14 row; p=1 is only adequate at N=2^17) and
|
|
7
|
+
* PBKDF2-HMAC-SHA256 with 600 000 iterations. `LIMITS` bounds parameters
|
|
8
|
+
* that are read back from stored hashes so that a tampered or foreign
|
|
9
|
+
* record cannot force unbounded work.
|
|
10
|
+
*
|
|
11
|
+
* New hashes are held to `SCRYPT.MIN_COST`; verification still accepts
|
|
12
|
+
* older stored hashes down to `LIMITS.MIN_SCRYPT_COST`.
|
|
9
13
|
*/
|
|
10
14
|
export const PASSWORD_HASH = Object.freeze({
|
|
11
15
|
SALT_BYTES: 16,
|
|
@@ -13,7 +17,15 @@ export const PASSWORD_HASH = Object.freeze({
|
|
|
13
17
|
SCRYPT: Object.freeze({
|
|
14
18
|
COST: 16_384,
|
|
15
19
|
BLOCK_SIZE: 8,
|
|
20
|
+
/**
|
|
21
|
+
* Default `p` for key derivation (`deriveScrypt`). Left at 1: changing
|
|
22
|
+
* it would silently change every key derived with default options.
|
|
23
|
+
*/
|
|
16
24
|
PARALLELIZATION: 1,
|
|
25
|
+
/** Default `p` for new *password hashes* (OWASP N=2^14, r=8, p=5). */
|
|
26
|
+
PASSWORD_PARALLELIZATION: 5,
|
|
27
|
+
/** Floor on the cost of *new* hashes; `LIMITS.MIN_SCRYPT_COST` bounds verification. */
|
|
28
|
+
MIN_COST: 16_384,
|
|
17
29
|
}),
|
|
18
30
|
PBKDF2: Object.freeze({
|
|
19
31
|
ITERATIONS: 600_000,
|
|
@@ -3,6 +3,7 @@ import { CryptoAlgorithm } from "../cryptoConstants/cryptoConstants.type.js";
|
|
|
3
3
|
import { PASSWORD_HASH } from "../cryptoConstants/cryptoConstants.security.js";
|
|
4
4
|
import { assertPassword, validateParameters, } from "./cryptoPassword.validate.js";
|
|
5
5
|
import { decodePasswordHash, PASSWORD_FORMAT_VERSION, } from "./cryptoPassword.codec.js";
|
|
6
|
+
import { assertNewHashCost } from "./cryptoPassword.helper.js";
|
|
6
7
|
/**
|
|
7
8
|
* Hashes a password using scrypt.
|
|
8
9
|
*
|
|
@@ -16,7 +17,8 @@ export async function hashPassword(password, options = {}) {
|
|
|
16
17
|
const keyBytes = options.keyBytes ?? PASSWORD_HASH.KEY_BYTES;
|
|
17
18
|
const cost = options.cost ?? PASSWORD_HASH.SCRYPT.COST;
|
|
18
19
|
const blockSize = options.blockSize ?? PASSWORD_HASH.SCRYPT.BLOCK_SIZE;
|
|
19
|
-
const parallelization = options.parallelization ?? PASSWORD_HASH.SCRYPT.
|
|
20
|
+
const parallelization = options.parallelization ?? PASSWORD_HASH.SCRYPT.PASSWORD_PARALLELIZATION;
|
|
21
|
+
assertNewHashCost(cost);
|
|
20
22
|
validateParameters({
|
|
21
23
|
saltBytes,
|
|
22
24
|
keyBytes,
|
|
@@ -11,4 +11,15 @@ export declare function isPasswordHash(encoded: string): boolean;
|
|
|
11
11
|
* Returns whether a password meets the basic requirements.
|
|
12
12
|
*/
|
|
13
13
|
export declare function isValidPassword(password: string, minimumLength?: number): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Refuses an scrypt cost below `PASSWORD_HASH.SCRYPT.MIN_COST` for a *new*
|
|
16
|
+
* password hash.
|
|
17
|
+
*
|
|
18
|
+
* `LIMITS.MIN_SCRYPT_COST` (2) only bounds what verification accepts, so
|
|
19
|
+
* older stored hashes stay verifiable; minting a new hash with `cost: 2`
|
|
20
|
+
* produced a hash that is effectively free to crack.
|
|
21
|
+
*
|
|
22
|
+
* @throws {RangeError} when `cost` is below the floor.
|
|
23
|
+
*/
|
|
24
|
+
export declare function assertNewHashCost(cost: number): void;
|
|
14
25
|
//# sourceMappingURL=cryptoPassword.helper.d.ts.map
|
|
@@ -10,7 +10,7 @@ export function getDefaultPasswordHashOptions() {
|
|
|
10
10
|
keyBytes: PASSWORD_HASH.KEY_BYTES,
|
|
11
11
|
cost: PASSWORD_HASH.SCRYPT.COST,
|
|
12
12
|
blockSize: PASSWORD_HASH.SCRYPT.BLOCK_SIZE,
|
|
13
|
-
parallelization: PASSWORD_HASH.SCRYPT.
|
|
13
|
+
parallelization: PASSWORD_HASH.SCRYPT.PASSWORD_PARALLELIZATION,
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
@@ -31,4 +31,19 @@ export function isPasswordHash(encoded) {
|
|
|
31
31
|
export function isValidPassword(password, minimumLength = PASSWORD_MINIMUM_DEFAULT_LENGTH) {
|
|
32
32
|
return typeof password === "string" && password.length >= minimumLength;
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Refuses an scrypt cost below `PASSWORD_HASH.SCRYPT.MIN_COST` for a *new*
|
|
36
|
+
* password hash.
|
|
37
|
+
*
|
|
38
|
+
* `LIMITS.MIN_SCRYPT_COST` (2) only bounds what verification accepts, so
|
|
39
|
+
* older stored hashes stay verifiable; minting a new hash with `cost: 2`
|
|
40
|
+
* produced a hash that is effectively free to crack.
|
|
41
|
+
*
|
|
42
|
+
* @throws {RangeError} when `cost` is below the floor.
|
|
43
|
+
*/
|
|
44
|
+
export function assertNewHashCost(cost) {
|
|
45
|
+
if (typeof cost === "number" && cost < PASSWORD_HASH.SCRYPT.MIN_COST) {
|
|
46
|
+
throw new RangeError(`scrypt cost for a new password hash must be at least ${PASSWORD_HASH.SCRYPT.MIN_COST}.`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
34
49
|
//# sourceMappingURL=cryptoPassword.helper.js.map
|
|
@@ -3,9 +3,10 @@ import type { CryptoInput, PasswordHashProviderOptions } from "../../../cryptoPr
|
|
|
3
3
|
* Hashes a password with scrypt (default) or PBKDF2 and returns the
|
|
4
4
|
* self-describing, versioned encoding produced by `encodePasswordHash`.
|
|
5
5
|
*
|
|
6
|
-
* The encoded string is validated against `PASSWORD_HASH.LIMITS`,
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* The encoded string is validated against `PASSWORD_HASH.LIMITS`, and a
|
|
7
|
+
* scrypt cost below `PASSWORD_HASH.SCRYPT.MIN_COST` (or PBKDF2 iterations
|
|
8
|
+
* below `PBKDF2.MIN_ITERATIONS`) is refused, so the provider does not mint
|
|
9
|
+
* hashes with short salts or tiny work factors.
|
|
9
10
|
*/
|
|
10
11
|
export declare function hashPassword(password: CryptoInput, options?: PasswordHashProviderOptions): Promise<string>;
|
|
11
12
|
/**
|
|
@@ -4,15 +4,17 @@ import { timingSafeEqual } from "../../../compare/compare.helper.js";
|
|
|
4
4
|
import { CryptoAlgorithm } from "../../../cryptoConstants/cryptoConstants.type.js";
|
|
5
5
|
import { PASSWORD_HASH } from "../../../cryptoConstants/cryptoConstants.security.js";
|
|
6
6
|
import { PASSWORD_FORMAT_VERSION, decodePasswordHash, encodePasswordHash, pbkdf2PasswordAlgorithm, } from "../../../cryptoPassword/cryptoPassword.codec.js";
|
|
7
|
+
import { assertNewHashCost } from "../../../cryptoPassword/cryptoPassword.helper.js";
|
|
7
8
|
import { deriveKey } from "./nodeCryptoProvider.derivation.js";
|
|
8
9
|
import { keyDerivationError } from "../../../cryptoErrors/cryptoErrors.helper.js";
|
|
9
10
|
/**
|
|
10
11
|
* Hashes a password with scrypt (default) or PBKDF2 and returns the
|
|
11
12
|
* self-describing, versioned encoding produced by `encodePasswordHash`.
|
|
12
13
|
*
|
|
13
|
-
* The encoded string is validated against `PASSWORD_HASH.LIMITS`,
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* The encoded string is validated against `PASSWORD_HASH.LIMITS`, and a
|
|
15
|
+
* scrypt cost below `PASSWORD_HASH.SCRYPT.MIN_COST` (or PBKDF2 iterations
|
|
16
|
+
* below `PBKDF2.MIN_ITERATIONS`) is refused, so the provider does not mint
|
|
17
|
+
* hashes with short salts or tiny work factors.
|
|
16
18
|
*/
|
|
17
19
|
export async function hashPassword(password, options) {
|
|
18
20
|
const algorithm = options?.algorithm ?? "scrypt";
|
|
@@ -23,7 +25,8 @@ export async function hashPassword(password, options) {
|
|
|
23
25
|
case "scrypt": {
|
|
24
26
|
const cost = options?.memoryCost ?? PASSWORD_HASH.SCRYPT.COST;
|
|
25
27
|
const blockSize = options?.blockSize ?? PASSWORD_HASH.SCRYPT.BLOCK_SIZE;
|
|
26
|
-
const parallelization = options?.parallelism ?? PASSWORD_HASH.SCRYPT.
|
|
28
|
+
const parallelization = options?.parallelism ?? PASSWORD_HASH.SCRYPT.PASSWORD_PARALLELIZATION;
|
|
29
|
+
assertNewHashCost(cost);
|
|
27
30
|
// Validate before deriving so invalid parameters fail fast.
|
|
28
31
|
const placeholder = new Uint8Array(keyBytes);
|
|
29
32
|
encodePasswordHash({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/crypto",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Cryptographic primitives for hashing, encryption, tokens, and secure random generation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"node": ">=24.0.0"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@zudojs/constants": "1.0
|
|
27
|
-
"@zudojs/errors": "1.0
|
|
26
|
+
"@zudojs/constants": "1.1.0",
|
|
27
|
+
"@zudojs/errors": "1.1.0"
|
|
28
28
|
},
|
|
29
29
|
"license": "MIT",
|
|
30
30
|
"author": {
|