# Hashing -- bcrypt, Argon2, BLAKE2b & PHP-compatible passwords Modern password hashing in pure JavaScript, zero dependencies, same API in the browser and Node.js -- with hashes that PHP's `password_hash()` / `password_verify()` understand out of the box. ## Overview The `extensions/hashing/*` modules implement the algorithms that today's backends use for passwords -- **bcrypt** (`$2y$`), **Argon2i / Argon2id** (RFC 9106) -- plus **BLAKE2b** as a fast general-purpose hash. Everything is implemented in plain JavaScript (64-bit arithmetic is emulated with 32-bit pairs, so no BigInt and no WebAssembly), which keeps the framework's promise: no build step, no `node_modules`, works wherever `ApplicationBuilder` runs. On top of the algorithms sits `extensions/hashing/password`, a drop-in equivalent of PHP's password API: `password_hash`, `password_verify`, `password_get_info`, `password_needs_rehash`, `password_algos` with the same option names, defaults and return shapes. ## Modules | Module | Purpose | Key Feature | |--------|---------|-------------| | [`extensions/hashing/password`](password.md) | PHP-compatible `password_*()` API | `$2y$` / `$argon2i$` / `$argon2id$` hashes interchangeable with PHP | | [`extensions/hashing/bcrypt`](bcrypt.md) | bcrypt hashing & verification | `$2a$` / `$2b$` / `$2y$`, cost 4..31, async & sync | | [`extensions/hashing/argon2`](argon2.md) | Argon2d / Argon2i / Argon2id | RFC 9106, secret & associated data, raw or encoded output | | [`extensions/hashing/blake2b`](blake2b.md) | BLAKE2b hash / keyed MAC | 1..64 byte digests, streaming API | | [`extensions/hashing/phc`](phc.md) | PHC string parser / serializer | `$id$v=..$k=v,..$salt$hash` | | [`extensions/hashing/utils`](utils.md) | Shared helpers | UTF-8, Base64, hex, secure random, constant-time compare, time-slicing | | `extensions/hashing` | Aggregator | Loads all of the above at once | ## Advantages - **Zero dependencies** -- no `hash-wasm`, no `bcryptjs`, no native addons; the hashes are computed by the framework's own modules - **PHP interoperable** -- verified against PHP 8.3: hashes created here verify in PHP and vice versa, for bcrypt, Argon2i and Argon2id (including `threads > 1`) - **Standards compliant** -- passes the OpenBSD bcrypt test vectors, the RFC 9106 Argon2 vectors and matches Node's `crypto` BLAKE2b output - **UI-friendly** -- the asynchronous API is *time-sliced*: it works for `timeSlice` milliseconds (default 50), yields to the event loop, then continues -- a high bcrypt cost or a 64 MiB Argon2 hash doesn't freeze the page - **Sync when you need it** -- every function has a `...Sync` twin for Node scripts and workers - **Safe defaults** -- cryptographically secure random salts (`crypto.getRandomValues` / Node `crypto`, never `Math.random()`), constant-time verification, `verify()` never throws on malformed input ## Getting Started ```js // @run App.require('extensions/hashing/password').then(function (password) { // hash with PHP's default algorithm (bcrypt, $2y$, cost 10) password.password_hash('correct horse battery staple').then(function (hash) { console.log(hash); // $2y$10$N9qo8uLOickgx2ZMRZoMye... // ...store it, later: return password.password_verify('correct horse battery staple', hash); }).then(function (ok) { console.log(ok); // true }); }); ``` Or load everything at once: ```js // @run App.require('extensions/hashing').then(function (hashing) { hashing.bcrypt.hash('secret', 12).then(console.log); // $2b$12$... hashing.argon2.hash('secret', { memoryCost: 16384 }).then(console.log); // $argon2id$v=19$m=16384,t=3,p=4$... console.log(hashing.blake2b('secret', { encoding: 'hex' })); // 128 hex chars }); ``` ## Choosing an Algorithm | Use case | Recommendation | |----------|----------------| | Talking to a PHP backend | `extensions/hashing/password` with `PASSWORD_DEFAULT` (bcrypt) or `PASSWORD_ARGON2ID` -- whatever the PHP side uses | | New project, server-side (Node.js) | `extensions/hashing/argon2` (Argon2id) -- memory-hard, the current recommendation | | Hashing in the browser | bcrypt cost 10-12, or Argon2id with a modest `memoryCost` (16-32 MiB) -- see performance below | | Checksums, content addressing, MACs | `extensions/hashing/blake2b` -- much faster than the password hashes and not meant for passwords | ## Performance Pure JavaScript is roughly 5-10x slower than native code. Approximate timings on a desktop-class CPU (Node 18; browsers are similar, phones slower): | Operation | Time | |-----------|------| | bcrypt cost 10 (PHP <= 8.3 default) | ~90 ms | | bcrypt cost 12 (PHP 8.4 default) | ~350 ms | | Argon2id m=4096 KiB, t=2, p=2 | ~100 ms | | Argon2id m=16384 KiB, t=2, p=1 | ~400 ms | | Argon2id m=65536 KiB, t=1, p=1 | ~750 ms | | Argon2id m=65536 KiB, t=4, p=1 (PHP defaults) | ~3 s | | BLAKE2b, 1 MiB input | ~70 ms | Password hashes are *supposed* to be slow -- that is what makes them resistant to brute force. Use the async API (`hash` / `verify`) so the UI stays responsive, and choose the cost parameters for the slowest device you need to support. For hashing that must be fast, use `blake2b`. ## Security Notes - **Where to hash** -- normally a password is hashed on the *server*. Hashing in the browser is useful for client-side key derivation, offline apps, or "zero knowledge" designs where the server never sees the clear password; in the latter case the server must still hash the received value again. - **bcrypt limits** -- only the first 72 bytes of the password are used (PHP behaves the same) and NUL bytes are rejected; `bcrypt.truncates(password)` tells you when a password would be cut. - **Peppers** -- `extensions/hashing/argon2` accepts a `secret` (kept out of the database) and `associatedData`; they are mixed into the hash and must be supplied again to `verify()`. - **Rehashing** -- when you raise the cost parameters, use `password_needs_rehash()` (or `argon2.needsRehash()`) at login time to upgrade stored hashes transparently. ## Live Demo Hash and verify passwords in your browser -- the modules are loaded on demand and the self-test panel checks the official test vectors. [Open in a new tab](examples/hashing/index.html ':ignore :target=_blank') | [All live examples](../../examples/index.md) ## Related Modules - [Extensions / Prototype](../index.md) -- SHA-1, SHA-256, MD5, AES and Base64 string methods (for integrity checks and encryption, not for passwords) - [Browser Session](../../storage/index.md) -- store derived keys or hashes client-side - [Request](../../networking/request.md) -- send hashes to your API