first commit
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
# argon2id
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Provides Argon2id password hashing as specified in RFC 9106. Argon2id is the
|
||||
current winner of the Password Hashing Competition and is recommended by OWASP
|
||||
for new applications. It is resistant to both GPU-based brute force (via high
|
||||
memory cost) and time-memory trade-off attacks (via the hybrid id variant).
|
||||
This module enforces non-pathological lower bounds on all four tuning parameters
|
||||
and produces a structured `HashError` with per-field context when they are
|
||||
violated, making it safe for frameworks to call without a separate validation
|
||||
layer.
|
||||
|
||||
## Features
|
||||
|
||||
- `argon2id(password, options)` — Argon2id key derivation
|
||||
- Exported constants: `ARGON2ID_MIN_MEMORY_COST_KIB`, `ARGON2ID_MIN_TIME_COST`, `ARGON2ID_MIN_PARALLELISM`
|
||||
- Validates each parameter individually; error context includes `{ field, value, minimum }`
|
||||
- Throws `HashError("HASH.KDF.INVALID_PARAMS")` for any out-of-range parameter
|
||||
- Deterministic: same inputs always produce the same hash output
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { argon2id, ARGON2ID_MIN_MEMORY_COST_KIB } from "@suaveplan/crypto-hash/kdf";
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const password = encoder.encode("hunter2");
|
||||
const salt = new Uint8Array(16); // use crypto.getRandomValues in production
|
||||
|
||||
const hash = argon2id(password, {
|
||||
salt,
|
||||
timeCost: 3,
|
||||
memoryCost: ARGON2ID_MIN_MEMORY_COST_KIB * 1024, // 8 MiB in KiB
|
||||
parallelism: 1,
|
||||
length: 32,
|
||||
});
|
||||
console.log(hash.length); // 32
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
argon2id,
|
||||
ARGON2ID_MIN_MEMORY_COST_KIB,
|
||||
ARGON2ID_MIN_TIME_COST,
|
||||
ARGON2ID_MIN_PARALLELISM,
|
||||
} from "@suaveplan/crypto-hash/kdf";
|
||||
import { toHex } from "@suaveplan/crypto-hash/encoding";
|
||||
import { HashError } from "@suaveplan/crypto-hash/errors";
|
||||
|
||||
interface PasswordHashConfig {
|
||||
timeCost: number;
|
||||
memoryCostKib: number;
|
||||
parallelism: number;
|
||||
outputBytes: number;
|
||||
}
|
||||
|
||||
function hashPassword(password: string, salt: Uint8Array, cfg: PasswordHashConfig): string {
|
||||
try {
|
||||
const raw = argon2id(new TextEncoder().encode(password), {
|
||||
salt,
|
||||
timeCost: cfg.timeCost,
|
||||
memoryCost: cfg.memoryCostKib,
|
||||
parallelism: cfg.parallelism,
|
||||
length: cfg.outputBytes,
|
||||
});
|
||||
return toHex(raw);
|
||||
} catch (err) {
|
||||
if (err instanceof HashError && err.context?.field) {
|
||||
throw new RangeError(
|
||||
`Argon2id parameter out of range: ${err.context.field} = ${err.context.value} ` +
|
||||
`(minimum: ${err.context.minimum})`
|
||||
);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
const salt = new Uint8Array(16);
|
||||
const hex = hashPassword("secure-password", salt, {
|
||||
timeCost: ARGON2ID_MIN_TIME_COST,
|
||||
memoryCostKib: ARGON2ID_MIN_MEMORY_COST_KIB,
|
||||
parallelism: ARGON2ID_MIN_PARALLELISM,
|
||||
outputBytes: 32,
|
||||
});
|
||||
console.log(hex.length); // 64 hex chars = 32 bytes
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `argon2id(password: Uint8Array, options: Argon2idOptions): Uint8Array`
|
||||
|
||||
Derives `options.length` bytes from `password` using Argon2id.
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `password` | `Uint8Array` | User-supplied password bytes |
|
||||
| `options.salt` | `Uint8Array` | Per-credential random salt (≥ 16 bytes recommended) |
|
||||
| `options.timeCost` | `number` | Integer ≥ `ARGON2ID_MIN_TIME_COST` (number of iterations) |
|
||||
| `options.memoryCost` | `number` | Integer ≥ `ARGON2ID_MIN_MEMORY_COST_KIB` (in KiB) |
|
||||
| `options.parallelism` | `number` | Integer ≥ `ARGON2ID_MIN_PARALLELISM` (lane count) |
|
||||
| `options.length` | `number` | Positive integer byte count for derived output |
|
||||
|
||||
**Returns:** `Uint8Array` of `options.length` bytes.
|
||||
|
||||
**Throws:** `HashError("HASH.KDF.INVALID_PARAMS")` for any validation failure.
|
||||
Error `context` contains `{ field: string, value: number, minimum: number }`.
|
||||
|
||||
---
|
||||
|
||||
### `ARGON2ID_MIN_MEMORY_COST_KIB = 8`
|
||||
|
||||
Minimum memory cost in KiB. Based on RFC 9106 §4 minimum recommendation.
|
||||
|
||||
### `ARGON2ID_MIN_TIME_COST = 1`
|
||||
|
||||
Minimum number of Argon2id iterations. Lower values are pathological.
|
||||
|
||||
### `ARGON2ID_MIN_PARALLELISM = 1`
|
||||
|
||||
Minimum lane count. Lower values are rejected as invalid.
|
||||
|
||||
---
|
||||
|
||||
### `Argon2idOptions`
|
||||
|
||||
```ts
|
||||
interface Argon2idOptions {
|
||||
readonly salt: Uint8Array;
|
||||
readonly timeCost: number;
|
||||
readonly memoryCost: number;
|
||||
readonly parallelism: number;
|
||||
readonly length: number;
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Parameters are validated in order: `timeCost`, `memoryCost`, `parallelism`,
|
||||
`length`. The first failing field short-circuits and throws with that field's
|
||||
context — no partial execution occurs. The implementation delegates to
|
||||
`@noble/hashes/argon2` which is a pure-JavaScript Argon2id implementation
|
||||
reviewed by the cryptographic community. Argon2id does not use the `HashAlgorithm`
|
||||
dispatch pattern used by `hmac`, `hkdf`, and `pbkdf2` — Argon2id's internal
|
||||
structure is fixed and parameterised by `t`, `m`, and `p` rather than a hash
|
||||
constructor. For this reason, `argon2id.ts` does not import from `mac/hmac.ts`.
|
||||
Reference in New Issue
Block a user