A Ruby C extension wrapping the yescrypt password hashing algorithm.
yescrypt is the default password hash in modern Linux distributions (glibc 2.36+). It extends scrypt with pwxform for stronger time-memory tradeoff resistance, making it significantly more costly for attackers using GPUs or custom hardware.
- High-level
create/verifyAPI modeled after bcrypt-ruby - Low-level
kdfaccess for custom use cases - Constant-time hash comparison via
OpenSSL.fixed_length_secure_compare - Thread-safe: releases the GVL during hashing so other Ruby threads can run
- GC-compaction safe with pinned string references
- Sensitive buffers are zeroed after use
- Supports all three yescrypt flavors: WORM, RW, and DEFAULTS
- Ruby >= 2.7.2
- A C99-compatible compiler (gcc, clang, etc.)
Add to your Gemfile:
gem "yescrypt"Then run:
bundle install
Or install directly:
gem install yescrypt
The C extension compiles automatically during installation. No external libraries are needed -- yescrypt, SHA-256, HMAC, and PBKDF2 are all bundled.
require "yescrypt"
# Hash a password (uses secure defaults: N=2^12, r=32, p=1)
hash = Yescrypt.create("my secret password")
# => "$y$j..."
# Verify a password against a stored hash
Yescrypt.verify("my secret password", hash) # => true
Yescrypt.verify("wrong password", hash) # => false
# Check if a hash needs rehashing (e.g., after changing cost parameters)
Yescrypt.cost_matches?(hash) # => true (matches current defaults)
Yescrypt.cost_matches?(hash, n_log2: 14, r: 32, p: 1) # => falseHash a password and return an encoded string suitable for storage.
hash = Yescrypt.create("password")
hash = Yescrypt.create("password", n_log2: 14, r: 32, p: 1, t: 0, flags: Yescrypt::DEFAULTS)Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
| password | String | (required) | The plaintext password |
| n_log2: | Integer | 12 |
Log2 of the block count (memory cost). N = 2^n_log2 |
| r: | Integer | 32 |
Block size parameter (affects memory per block) |
| p: | Integer | 1 |
Parallelism parameter |
| t: | Integer | 0 |
Time parameter (extra mixing rounds) |
| flags: | Integer | Yescrypt::DEFAULTS (2) |
Flavor flags (see Flavors) |
Returns: A frozen, US-ASCII encoded string starting with $y$.
A random 16-byte salt is generated automatically using SecureRandom.
Verify a plaintext password against an encoded hash.
Yescrypt.verify("password", hash) # => true or falseReturns false for any invalid input (wrong types, malformed hash, wrong password) rather than raising exceptions. Uses constant-time comparison to prevent timing attacks.
Check whether an existing hash was generated with the given parameters. Useful for determining if a password needs rehashing after a cost parameter change.
if Yescrypt.verify(password, stored_hash)
unless Yescrypt.cost_matches?(stored_hash, n_log2: 14)
# Rehash with new cost parameters
new_hash = Yescrypt.create(password, n_log2: 14)
save_hash(new_hash)
end
endParameters: Same keyword arguments as create. Omitted parameters default to the current defaults.
Returns: true if all parameters (n_log2, r, p, t, flags) match, false otherwise.
Returns a frozen hash of the current default parameters:
Yescrypt.default_params
# => { n_log2: 12, r: 32, p: 1, t: 0, flags: 2 }Parse the parameters from an encoded yescrypt hash string.
params = Yescrypt.decode_params(hash)
# => { n_log2: 12, r: 32, p: 1, t: 0, flags: 2 }Returns: A frozen hash with :n_log2, :r, :p, :t, :flags keys, or nil if the string is not a valid yescrypt hash.
Generate a setting/salt string for low-level use. You typically don't need this directly -- create calls it internally.
salt = SecureRandom.random_bytes(16)
setting = Yescrypt.gensalt(n_log2: 12, r: 32, p: 1, t: 0, flags: Yescrypt::DEFAULTS, salt: salt)The salt: keyword is required and must be a binary String (typically 16 random bytes).
Low-level KDF function that returns raw hash bytes. Unlike create, this gives you direct control over all parameters and returns unencoded binary output.
raw = Yescrypt.kdf("password", "salt" * 4,
n: 4096, r: 32, p: 1, flags: Yescrypt::DEFAULTS, t: 0, outlen: 32)
raw.bytesize # => 32Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
| password | String | (required) | The plaintext password |
| salt | String | (required) | Salt bytes (any length) |
| n: | Integer | (required) | Block count (must be a power of 2, >= 2) |
| r: | Integer | (required) | Block size parameter |
| p: | Integer | (required) | Parallelism parameter |
| flags: | Integer | (required) | Flavor flags |
| t: | Integer | 0 |
Time parameter |
| outlen: | Integer | 32 |
Output length in bytes (1..1024) |
Returns: A binary String of outlen bytes.
Note: n here is the actual block count (e.g., 4096), not the log2 value used in create (where you'd use n_log2: 12).
yescrypt supports three operational flavors:
| Constant | Value | Description |
|---|---|---|
Yescrypt::WORM |
0 | Classic scrypt-compatible mode. Read-only memory access pattern. |
Yescrypt::RW |
1 | yescrypt with read-write memory access. Stronger against TMTO attacks. |
Yescrypt::DEFAULTS |
2 | Recommended mode. Includes RW access plus pwxform strengthening. |
DEFAULTS is recommended for new applications. WORM is useful if you need scrypt compatibility.
The cost parameters control the trade-off between security and performance:
- n_log2 (memory cost): Each increment doubles memory usage and time. Start with
12(4096 blocks) and increase if your server can handle it.14(16384 blocks) is a good choice for higher security. - r (block size): Controls memory per block (128 * r bytes). The default
32(4 KB per block) is well-suited for modern CPUs. Increasingrincreases memory bandwidth requirements. - p (parallelism): Number of independent mixing operations. Keep at
1unless you specifically need parallel computation. Increasingpscales CPU time linearly without increasing memory. - t (time): Extra mixing rounds. Each increment of
tadds N more mixing iterations. Keep at0unless you want to increase CPU time without increasing memory.
Approximate memory per hash operation: 128 * r * N bytes, where N = 2^n_log2.
| n_log2 | r | Memory |
|---|---|---|
| 10 | 8 | 1 MB |
| 12 | 32 | 16 MB |
| 14 | 32 | 64 MB |
| 16 | 32 | 256 MB |
Test different parameters to find the right trade-off for your application:
require "benchmark"
[10, 12, 14].each do |n|
time = Benchmark.realtime { Yescrypt.create("test", n_log2: n, r: 32, p: 1) }
puts "n_log2=#{n}: #{(time * 1000).round}ms"
endyescrypt releases the Ruby GVL (Global VM Lock) during the CPU-intensive hashing computation. This means other Ruby threads can execute while a hash is being computed:
threads = 4.times.map do |i|
Thread.new { Yescrypt.create("password_#{i}", n_log2: 4, r: 1, p: 1) }
end
hashes = threads.map(&:value) # all 4 run concurrentlyEach call allocates its own working memory, so there is no shared mutable state between threads.
The encoded hash string follows the crypt(3) modular format:
$y$j<params>$<salt>$<hash>
$y$-- yescrypt identifier prefixj-- flavor indicator<params>-- base64-encoded N_log2, r, p, t, and flags<salt>-- base64-encoded salt<hash>-- base64-encoded 256-bit derived key
Example:
$y$jD5.7C....$aBcDeFgHiJkLmNoPqR..$xYzAbCdEfGhIjKlMnOpQrStUvWxYz01234567890AB
The gem defines a single exception class:
Yescrypt::Error < StandardErrorRaised by low-level functions (kdf, gensalt, _hash_password) when the C library reports a failure (e.g., memory allocation failure, invalid setting string).
The high-level verify method never raises -- it returns false on any error. The high-level create method raises TypeError if the password is not a String, and may raise Yescrypt::Error on internal failures.
| Feature | yescrypt | bcrypt-ruby | scrypt |
|---|---|---|---|
| Memory-hard | Yes | No | Yes |
| TMTO resistance | Strong (pwxform) | N/A | Moderate |
| Linux default (2024+) | Yes | No | No |
| GVL released | Yes | Yes | Yes |
| Constant-time verify | Yes | Yes | Yes |
# Clone the repository
git clone https://github.com/msuliq/yescrypt.git
cd yescrypt
# Install dependencies
bundle install
# Compile the C extension
bundle exec rake compile
# Run tests
bundle exec rake test
# Run both (compile + test)
bundle exec rakeyescrypt/
ext/yescrypt/ # C extension source
extconf.rb # Build configuration
yescrypt_ext.c # Ruby/C bridge
yescrypt-opt.c # Core yescrypt algorithm (smix, salsa20, pwxform)
yescrypt-common.c # Encoding, decoding, salt generation, yescrypt_r
yescrypt.h # Public C API header
sha256.c # SHA-256 / HMAC / PBKDF2 implementation
sha256.h # SHA-256 header
insecure_memzero.h # Secure memory zeroing
lib/
yescrypt.rb # High-level Ruby API (create, verify, cost_matches?)
yescrypt/version.rb # Version constant
test/
yescrypt_test.rb # Minitest test suite
test_helper.rb # Test setup
MIT License. See LICENSE for details.
The bundled yescrypt C implementation is based on work by Colin Percival and Alexander Peslyak, used under a permissive BSD-style license.