# Changelog All notable changes to `@notifycode/hash-it` are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). --- ## [1.0.1] — 2026-07-20 — Security & Robustness Hardening This release corrects a mislabeled cryptographic primitive, closes a real token-verification bug, and adds non-blocking APIs for production use under load. **All existing password hashes and tokens continue to verify** — nothing issued by 1.0.0 is invalidated by this release. ### Security - **`hashit.password` default algorithm changed from a mislabeled construction to real `scrypt`.** 1.0.0 shipped a hand-rolled PBKDF2 chain labeled `$hashit-argon2id$` that claimed OWASP-equivalent Argon2id security. It was not real Argon2id — it had no genuine memory-hard access pattern. The new default, `scrypt` (RFC 7914), is a real, standardized, memory-hard KDF built natively into `crypto` on both Node.js and Bun — zero dependencies either way. - `hashPassword()` / `hashit.password.hash()` now produce `$hashit-scrypt$...` hashes by default. - The old construction is preserved, byte-for-byte, **verify-only** — existing stored hashes keep validating. `needsRehash()` now always returns `true` for them, so the next successful login transparently migrates the user to scrypt. No forced resets, no downtime. - Added `algorithm: 'pbkdf2'` (unchanged, real PBKDF2-HMAC-SHA512 — always honestly labeled) and `algorithm: 'argon2id'` (real Argon2id, via Bun's native `Bun.password` — zero dependencies, Bun runtime only; throws a clear `UNSUPPORTED_RUNTIME` error when explicitly requested on Node rather than silently downgrading). - **Fixed: API token prefix parsing broke for any prefix longer than 8 characters** (a fully documented, supported option). `verifyApiToken` used a positional heuristic (first `_` within 8 chars) that silently rejected otherwise-valid tokens. Tokens are now `.
..` — the `.` is a real, unambiguous delimiter, not a guess. v1.0.0-format tokens (no delimiter) still verify via an explicit legacy fallback. - **Fixed: AES-256-CBC mode reused the raw input password as the HMAC integrity key**, the same secret used (via PBKDF2) to derive the encryption key. Encryption and MAC keys are now derived independently via distinct domain-separated PBKDF2 contexts (key separation). GCM and ChaCha20-Poly1305 were never affected. **Ciphertexts previously sealed with `aes-256-cbc` will need re-encryption** — GCM/ChaCha ciphertexts are unaffected. - **Added: key-type/algorithm consistency check in `verifyToken`.** Verification now confirms the actual public key type (EC vs. RSA) matches what the declared algorithm requires, instead of implicitly trusting the token header alone — defense-in-depth against key/algorithm confusion. - **Changed: token header `typ` is now `"hash-it"`**, not `"JWT"`. hash-it tokens are structurally JWT-like but are an intentionally closed, proprietary format — this prevents any accidental cross-acceptance by generic JWT/JOSE verifiers, or vice versa. This does not affect verification of tokens already issued (`typ` was never used as a security check). - Rejected: empty-string `kid` in `generateKeyPair()` / `generateKeyPairAsync()`. ### Added - `hashit.password.hash()` / `.verify()` are now genuinely non-blocking — backed by libuv-threadpool scrypt/PBKDF2 and Bun's worker-thread-backed Argon2id, instead of a synchronous call wrapped in a resolved Promise. New named exports: `hashPasswordAsync`, `verifyPasswordAsync`. - `hashit.keys.generateAsync()` — non-blocking key-pair generation. RSA-4096 generation via the synchronous `generateKeyPair()` can take real wall-clock time and blocks the event loop; prefer the async version in a live server. ### Fixed - `needsRehash()` now also accounts for scrypt cost-parameter drift and always flags the deprecated pre-1.0.1 construction. --- ## [1.0.0] — 2025-05-10 ### Added - **`hashit.password`** — Argon2id password hashing with OWASP-recommended parameters - PBKDF2-SHA512 fallback for constrained environments - Automatic upgrade detection via `needsRehash()` - DoS protection: passwords >1024 chars pre-hashed with SHA-512 - Constant-time verification via `timingSafeEqual` - **`hashit.token`** — Mastercard-style asymmetric token signing and verification - ECDSA P-256/P-384/P-521 support (`ES256`, `ES384`, `ES512`) - RSA-4096 support (`RS256`, `RS512`, `PS256`) - JWT-compatible compact token format - Full claims validation: `exp`, `nbf`, `iss`, `aud`, `jti` - Clock skew tolerance (configurable, default 30s) - **`hashit.keys`** — Asymmetric key pair management - Key pair generation for all supported algorithms - `PublicKeySet` for multi-key rotation support - Key lookup by `kid` for efficient rotation - **`hashit.session`** — Session token pair management - Access + refresh token pair creation - Refresh token rotation - Type-safe session verification - **`hashit.apiToken`** — Opaque API token generation - Embedded cryptographic claims (like GitHub's `ghp_`, Stripe's `sk_`) - Configurable prefix - Token masking for safe log output - **`hashit.encrypt`** — Symmetric authenticated encryption - AES-256-GCM (default — AEAD, tamper-evident) - AES-256-CBC with Encrypt-then-MAC - ChaCha20-Poly1305 (where supported) - PBKDF2-derived keys from passwords - **`hashit.utils`** — Security utilities - `randomBytes()` — CSPRNG random bytes - `safeEqual()` — constant-time string comparison - `parseDuration()` — human-readable duration parsing - `fingerprint()` — HMAC-based token fingerprinting - **Dual CJS/ESM build** — full Node.js and Bun support - **Zero runtime dependencies** — Node.js `crypto` module only - **155 tests** — 96%+ coverage - **Full TypeScript types** — strict mode, all public APIs documented