Overview
Salt and pepper represent two distinct approaches to protecting password hashes from precomputation attacks. A salt adds a random value to each password before hashing, ensuring identical passwords produce different hash outputs. A pepper adds a secret value to passwords before hashing, providing an additional layer of protection even if the password database is compromised.
Rainbow tables and dictionary attacks pose significant threats to password databases. Without additional protections, attackers who obtain password hashes can use precomputed tables mapping common passwords to their hash values. Salt defeats these attacks by introducing uniqueness per password. Pepper provides defense in depth by introducing a secret that must be obtained separately from the database.
The fundamental difference between salt and pepper lies in storage and purpose. Salt values store alongside password hashes in the database, visible to anyone who gains database access. Each user requires a unique salt. Pepper values remain separate from the database, typically stored in application configuration or environment variables. The entire system shares a single pepper value.
# Without salt or pepper - vulnerable
hash = Digest::SHA256.hexdigest("password123")
# => "ef92b778bafe771e89245b89ecbc08a44a4e166c06659911881f383d4473e94f"
# With salt - same password, different hash
salt = SecureRandom.hex(16)
hash_with_salt = Digest::SHA256.hexdigest(salt + "password123")
# => Result varies due to random salt
# With pepper - secret value added
pepper = ENV['PASSWORD_PEPPER']
hash_with_pepper = Digest::SHA256.hexdigest("password123" + pepper)
# => Result depends on secret pepper value
Modern password hashing requires salt as a mandatory component. The bcrypt, scrypt, and Argon2 algorithms automatically generate and manage salts. Pepper remains optional but provides additional protection in high-security environments.
Key Principles
Salt operates as a cryptographic nonce - a value used once per password that prevents identical passwords from generating identical hashes. The salt generation process uses cryptographically secure random number generators to ensure unpredictability. Minimum recommended salt length is 16 bytes (128 bits), though 32 bytes provides additional margin.
The salt concatenates with the password before the hashing operation. Three common concatenation patterns exist: prepending the salt, appending the salt, or interleaving salt and password. Prepending represents the most common approach due to its simplicity and compatibility with existing implementations.
# Salt prepending
salted_password = salt + password
hash = hash_function(salted_password)
# Salt appending
salted_password = password + salt
hash = hash_function(salted_password)
Salt storage occurs directly in the password hash record. Most password hashing formats encode both salt and hash into a single string. The bcrypt format exemplifies this approach:
$2b$12$EXAMPLESALTxxxxxxxxxxOeKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK
└─┘ └┘ └────────────────────┘└──────────────────────────┘
│ │ Salt Hash value
│ Cost factor
Algorithm identifier
Pepper functions as a secret key mixed into password hashes. Unlike salt, which differs per user, a single pepper value applies across the entire system. The pepper never stores in the database. Common storage locations include environment variables, configuration management systems, hardware security modules, or key management services.
Pepper concatenation follows similar patterns to salt but typically occurs after salt addition. The pattern becomes: hash(salt + password + pepper). This ordering provides optimal security characteristics because attackers who obtain both password and salt still lack the pepper.
The cryptographic strength of pepper depends on its entropy and secrecy. Minimum pepper length should match the hash output size - 256 bits for SHA-256, 512 bits for SHA-512. Pepper generation requires cryptographically secure random number generators, not standard random functions.
Key rotation poses a significant challenge for pepper values. Changing the pepper invalidates all existing password hashes. Systems supporting pepper rotation maintain multiple pepper values identified by version numbers, rehashing passwords opportunistically during user login.
# Pepper rotation structure
peppers = {
1 => "original_pepper_value_here",
2 => "new_pepper_value_here",
3 => "current_pepper_value_here"
}
# Hash storage includes pepper version
hash_record = {
hash: computed_hash,
salt: salt_value,
pepper_version: 3
}
# Verification uses the correct pepper version
verification_pepper = peppers[hash_record[:pepper_version]]
Security Implications
Salt and pepper address different attack vectors in password security. Understanding the specific threats each technique mitigates enables appropriate security architecture decisions.
Rainbow table attacks precompute hash values for common passwords. An attacker generates hashes for millions of passwords, stores them in a lookup table, then compares stolen password hashes against the table. Salt defeats rainbow tables by introducing uniqueness per password. An attacker must generate a separate rainbow table for each possible salt value - computationally infeasible when salts contain sufficient entropy.
Dictionary attacks operate similarly but compute hashes on demand rather than precomputing. The attacker iterates through password dictionaries, hashing each candidate password and comparing to the target hash. Salt increases the computational cost linearly with the number of salted passwords. An attack against one password does not benefit attacks against other passwords.
Database compromise represents a critical failure mode. Attackers who gain read access to the password database obtain all password hashes and salts. Without pepper, attackers can immediately begin offline attacks against the hashes. Pepper requires attackers to also compromise the application server or configuration system storing the pepper value.
The security benefit of pepper depends on separation of concerns. The database server and application server must maintain distinct security boundaries. If a single compromise exposes both database and application configuration, pepper provides no additional protection. Organizations with strong operational security can maintain this separation effectively.
Timing attacks attempt to extract information by measuring operation duration. Password verification timing can leak information about password correctness. Constant-time comparison functions prevent timing attacks by ensuring all comparisons take identical time regardless of where differences occur.
# Vulnerable to timing attacks
def verify_password_unsafe(input, stored_hash)
computed_hash = hash_password(input)
computed_hash == stored_hash # Comparison short-circuits on first difference
end
# Protected against timing attacks
def verify_password_safe(input, stored_hash)
computed_hash = hash_password(input)
Rack::Utils.secure_compare(computed_hash, stored_hash)
end
Side-channel attacks exploit information leakage through physical properties like power consumption or electromagnetic radiation. Hardware security modules protect pepper values against side-channel attacks by performing cryptographic operations within tamper-resistant hardware. Applications handling highly sensitive authentication should consider HSM storage for pepper values.
Pepper rotation introduces temporary vulnerability windows. During rotation, some passwords use old pepper values while new passwords use updated values. The system must maintain both pepper versions until all passwords have been rehashed. A compromise during this transition period exposes passwords hashed with different pepper values to different attack scenarios.
Backup and disaster recovery procedures must account for pepper values. Database backups contain salted password hashes but lack pepper values. Restoring a database backup without the corresponding pepper value renders all password hashes unverifiable. Pepper values require separate secure backup and synchronization with database backups.
Key management complexity increases with pepper adoption. Organizations must implement secure key generation, distribution, rotation, and destruction procedures. Cloud key management services provide these capabilities but introduce dependencies on external services. The cost-benefit analysis weighs increased security against operational complexity.
Ruby Implementation
Ruby provides multiple approaches for implementing salt and pepper in password hashing systems. The bcrypt-ruby gem offers the most common implementation, handling salt generation and management automatically.
require 'bcrypt'
# BCrypt automatically generates and manages salt
class User
attr_accessor :password_hash
def password=(new_password)
# Cost factor 12 provides good security/performance balance
@password_hash = BCrypt::Password.create(new_password, cost: 12)
end
def authenticate(password)
BCrypt::Password.new(@password_hash) == password
end
end
user = User.new
user.password = "secret_password"
# password_hash contains: $2a$12$[22-character-salt][31-character-hash]
user.authenticate("secret_password") # => true
user.authenticate("wrong_password") # => false
The BCrypt::Password class encapsulates both salt and hash in a single string. The salt generates automatically using SecureRandom under the hood. Manual salt specification remains possible but should be avoided in production code.
Implementing pepper with BCrypt requires mixing the pepper value before passing the password to BCrypt. This approach maintains compatibility with standard BCrypt while adding the pepper layer.
require 'bcrypt'
class User
PEPPER = ENV.fetch('PASSWORD_PEPPER')
attr_accessor :password_hash
def password=(new_password)
peppered_password = new_password + PEPPER
@password_hash = BCrypt::Password.create(peppered_password, cost: 12)
end
def authenticate(password)
peppered_password = password + PEPPER
BCrypt::Password.new(@password_hash) == peppered_password
end
end
Manual salt implementation using Ruby's digest libraries demonstrates the underlying mechanics. This approach provides complete control but requires careful attention to security details.
require 'securerandom'
require 'digest'
class PasswordHasher
HASH_ALGORITHM = Digest::SHA256
SALT_LENGTH = 32 # bytes
def self.hash_password(password)
salt = SecureRandom.bytes(SALT_LENGTH)
hash = HASH_ALGORITHM.digest(salt + password)
# Encode salt and hash for storage
{
salt: Base64.strict_encode64(salt),
hash: Base64.strict_encode64(hash)
}
end
def self.verify_password(password, salt_b64, hash_b64)
salt = Base64.strict_decode64(salt_b64)
stored_hash = Base64.strict_decode64(hash_b64)
computed_hash = HASH_ALGORITHM.digest(salt + password)
# Constant-time comparison
Rack::Utils.secure_compare(computed_hash, stored_hash)
end
end
# Usage
credentials = PasswordHasher.hash_password("user_password")
# => {salt: "base64_encoded_salt", hash: "base64_encoded_hash"}
PasswordHasher.verify_password("user_password",
credentials[:salt],
credentials[:hash])
# => true
Advanced implementations combine salt and pepper with key derivation functions. The PBKDF2 algorithm applies a pseudorandom function repeatedly to derive keys from passwords.
require 'openssl'
require 'securerandom'
class SecurePasswordHasher
ITERATIONS = 100_000
HASH_LENGTH = 32 # bytes
SALT_LENGTH = 32 # bytes
PEPPER = ENV.fetch('PASSWORD_PEPPER')
def self.hash_password(password)
salt = SecureRandom.bytes(SALT_LENGTH)
peppered_password = password + PEPPER
hash = OpenSSL::PKCS5.pbkdf2_hmac(
peppered_password,
salt,
ITERATIONS,
HASH_LENGTH,
OpenSSL::Digest::SHA256.new
)
{
salt: Base64.strict_encode64(salt),
hash: Base64.strict_encode64(hash),
iterations: ITERATIONS
}
end
def self.verify_password(password, salt_b64, hash_b64, iterations)
salt = Base64.strict_decode64(salt_b64)
stored_hash = Base64.strict_decode64(hash_b64)
peppered_password = password + PEPPER
computed_hash = OpenSSL::PKCS5.pbkdf2_hmac(
peppered_password,
salt,
iterations,
HASH_LENGTH,
OpenSSL::Digest::SHA256.new
)
Rack::Utils.secure_compare(computed_hash, stored_hash)
end
end
Pepper rotation requires maintaining multiple pepper versions and tracking which version hashed each password. The implementation stores a pepper version identifier alongside each password hash.
class MultiPepperHasher
PEPPERS = {
1 => ENV.fetch('PASSWORD_PEPPER_V1'),
2 => ENV.fetch('PASSWORD_PEPPER_V2'),
3 => ENV.fetch('PASSWORD_PEPPER_V3')
}
CURRENT_PEPPER_VERSION = 3
def self.hash_password(password)
pepper = PEPPERS[CURRENT_PEPPER_VERSION]
peppered_password = password + pepper
{
hash: BCrypt::Password.create(peppered_password, cost: 12),
pepper_version: CURRENT_PEPPER_VERSION
}
end
def self.verify_password(password, hash_data)
pepper = PEPPERS[hash_data[:pepper_version]]
unless pepper
raise "Unknown pepper version: #{hash_data[:pepper_version]}"
end
peppered_password = password + pepper
BCrypt::Password.new(hash_data[:hash]) == peppered_password
end
def self.needs_rehash?(hash_data)
hash_data[:pepper_version] != CURRENT_PEPPER_VERSION
end
def self.rehash_password(password)
hash_password(password)
end
end
# Usage with opportunistic rehashing
def authenticate_user(username, password)
user = find_user(username)
return false unless user
if MultiPepperHasher.verify_password(password, user.password_data)
# Rehash with current pepper if needed
if MultiPepperHasher.needs_rehash?(user.password_data)
user.password_data = MultiPepperHasher.rehash_password(password)
user.save
end
return true
end
false
end
The argon2 gem provides the most modern password hashing algorithm, winner of the Password Hashing Competition. Argon2 includes built-in salt management and memory-hard properties that resist GPU-based attacks.
require 'argon2'
class Argon2Hasher
PEPPER = ENV.fetch('PASSWORD_PEPPER')
# Configure Argon2 parameters
HASHER = Argon2::Password.new(
t_cost: 2, # Number of iterations
m_cost: 16, # Memory in mebibytes (2^16 KiB)
p_cost: 1, # Parallelism factor
salt_len: 16 # Salt length in bytes
)
def self.hash_password(password)
peppered_password = password + PEPPER
HASHER.create(peppered_password)
end
def self.verify_password(password, hash)
peppered_password = password + PEPPER
HASHER.verify_password(peppered_password, hash)
end
end
Common Pitfalls
Using insufficient salt length compromises security. Salts shorter than 16 bytes reduce the search space for attackers attempting to generate rainbow tables per salt value. A 64-bit salt requires only 2^64 precomputed tables - feasible for well-funded attackers. Always use minimum 128-bit (16-byte) salts.
# Insufficient salt length - vulnerable
short_salt = SecureRandom.hex(4) # Only 8 bytes
# Proper salt length
proper_salt = SecureRandom.hex(16) # 32 bytes
Storing pepper values in the database defeats the purpose of pepper. Pepper must remain separate from password hashes to provide defense in depth. Developers occasionally store pepper in a database configuration table, eliminating the security benefit.
Reusing the same salt across multiple passwords creates the same vulnerability as no salt. Each password must receive a unique salt value. Global salts or per-application salts provide no protection against precomputation attacks.
# Wrong - reusing salt
GLOBAL_SALT = SecureRandom.hex(16)
hash1 = Digest::SHA256.hexdigest(GLOBAL_SALT + "password1")
hash2 = Digest::SHA256.hexdigest(GLOBAL_SALT + "password2")
# Correct - unique salt per password
salt1 = SecureRandom.hex(16)
hash1 = Digest::SHA256.hexdigest(salt1 + "password1")
salt2 = SecureRandom.hex(16)
hash2 = Digest::SHA256.hexdigest(salt2 + "password2")
Using weak hash functions undermines salt and pepper benefits. MD5 and SHA-1 compute too quickly, allowing attackers to test billions of passwords per second. Modern password hashing requires computationally expensive algorithms like bcrypt, scrypt, or Argon2.
Concatenating salt and password incorrectly can introduce vulnerabilities. Certain concatenation patterns enable length extension attacks against some hash functions. The standard pattern places salt first, followed by password: hash(salt || password).
Failing to use cryptographically secure random number generators for salt generation introduces predictability. Ruby's Random class provides adequate randomness for general purposes but SecureRandom must be used for cryptographic operations.
# Wrong - predictable salt
require 'digest'
salt = Random.new.bytes(16) # Not cryptographically secure
# Correct - unpredictable salt
require 'securerandom'
salt = SecureRandom.bytes(16) # Cryptographically secure
Hardcoding pepper values in source code exposes them through version control systems. Source code repositories often replicate across multiple systems and backup locations. Pepper values should load from environment variables or secure configuration management.
Missing constant-time comparison during password verification enables timing attacks. The standard equality operator (==) short-circuits on the first byte difference, leaking information about hash correctness through timing differences.
Implementing custom password hashing instead of using established libraries introduces numerous security risks. Password hashing requires careful attention to subtle security properties. Developers should use bcrypt, scrypt, or Argon2 through established libraries rather than implementing their own schemes.
Storing salt and hash in separate database fields without proper encoding can cause parsing issues. Binary salt and hash values require base64 or hex encoding before storage in text fields. Improper encoding leads to data corruption and authentication failures.
Forgetting to include pepper during password verification creates authentication failures. The verification code must apply the exact same transformations as the hashing code. A common mistake involves hashing with pepper but verifying without pepper.
# Incorrect - hashing with pepper but verifying without
def set_password(password)
peppered = password + PEPPER
@hash = BCrypt::Password.create(peppered)
end
def authenticate(password)
# Missing pepper - will always fail
BCrypt::Password.new(@hash) == password
end
# Correct - consistent pepper application
def authenticate(password)
peppered = password + PEPPER
BCrypt::Password.new(@hash) == peppered
end
Practical Examples
A complete user authentication system demonstrates salt and pepper integration with a web application. This example uses BCrypt with pepper and implements secure registration and login flows.
require 'bcrypt'
require 'securerandom'
class User
PEPPER = ENV.fetch('PASSWORD_PEPPER', 'development-pepper-value')
BCRYPT_COST = ENV.fetch('BCRYPT_COST', 12).to_i
attr_accessor :id, :username, :password_hash, :created_at
def self.register(username, password)
return nil if password.length < 8
user = User.new
user.id = SecureRandom.uuid
user.username = username
user.password = password
user.created_at = Time.now
user
end
def password=(new_password)
peppered_password = apply_pepper(new_password)
@password_hash = BCrypt::Password.create(peppered_password, cost: BCRYPT_COST)
end
def authenticate(password_attempt)
peppered_attempt = apply_pepper(password_attempt)
BCrypt::Password.new(@password_hash) == peppered_attempt
rescue BCrypt::Errors::InvalidHash
false
end
private
def apply_pepper(password)
password + PEPPER
end
end
# Registration flow
user = User.register("alice", "secure_password_123")
# user.password_hash => "$2a$12$[salt-and-hash-data]"
# Authentication flow
if user.authenticate("secure_password_123")
puts "Authentication successful"
else
puts "Authentication failed"
end
Migrating from unsalted hashes to salted hashes requires careful planning. This example shows opportunistic migration during user login without forcing password resets.
class UserMigration
attr_accessor :username, :old_hash, :new_hash, :salt, :uses_salt
def self.migrate_password(username, password)
user = find_user(username)
# Check if already migrated
return false if user.uses_salt
# Verify using old unsalted hash
old_hash = Digest::SHA256.hexdigest(password)
return false unless user.old_hash == old_hash
# Generate new salted hash
salt = SecureRandom.hex(16)
new_hash = Digest::SHA256.hexdigest(salt + password)
# Update database
user.salt = salt
user.new_hash = new_hash
user.uses_salt = true
user.save
true
end
def authenticate(password)
if uses_salt
computed_hash = Digest::SHA256.hexdigest(salt + password)
Rack::Utils.secure_compare(computed_hash, new_hash)
else
# Try migration on successful authentication
computed_hash = Digest::SHA256.hexdigest(password)
if Rack::Utils.secure_compare(computed_hash, old_hash)
# Migrate to salted hash
self.class.migrate_password(username, password)
true
else
false
end
end
end
end
A production-ready password system combines salt, pepper, and adaptive cost factors. This implementation adjusts bcrypt cost based on server performance and includes monitoring.
require 'bcrypt'
require 'benchmark'
class AdaptivePasswordHasher
PEPPER = ENV.fetch('PASSWORD_PEPPER')
TARGET_HASH_TIME = 0.5 # Target 500ms per hash
MIN_COST = 10
MAX_COST = 16
# Determine appropriate cost factor
def self.calibrate_cost
cost = MIN_COST
while cost <= MAX_COST
time = Benchmark.realtime do
BCrypt::Password.create("test_password", cost: cost)
end
return cost if time >= TARGET_HASH_TIME
cost += 1
end
MAX_COST
end
def self.hash_password(password, cost: nil)
cost ||= calibrate_cost
peppered = password + PEPPER
{
hash: BCrypt::Password.create(peppered, cost: cost),
cost: cost,
created_at: Time.now
}
end
def self.verify_password(password, password_data)
peppered = password + PEPPER
result = BCrypt::Password.new(password_data[:hash]) == peppered
# Log verification timing for monitoring
log_verification(password_data[:cost], result)
result
end
def self.needs_rehash?(password_data)
current_cost = calibrate_cost
password_data[:cost] < current_cost
end
private
def self.log_verification(cost, success)
# Monitor authentication patterns
puts "Password verification: cost=#{cost} success=#{success}"
end
end
Handling pepper rotation in a production system requires careful coordination. This example implements versioned pepper with gradual migration.
class PepperRotationSystem
PEPPERS = {
1 => ENV.fetch('PASSWORD_PEPPER_V1'),
2 => ENV.fetch('PASSWORD_PEPPER_V2'),
3 => ENV.fetch('PASSWORD_PEPPER_V3')
}
CURRENT_VERSION = 3
MINIMUM_VERSION = 1 # Versions below this are invalid
def self.hash_password(password)
pepper = PEPPERS[CURRENT_VERSION]
peppered = password + pepper
{
hash: BCrypt::Password.create(peppered, cost: 12),
pepper_version: CURRENT_VERSION,
updated_at: Time.now
}
end
def self.verify_and_upgrade(password, password_data)
version = password_data[:pepper_version]
# Reject outdated pepper versions
return nil if version < MINIMUM_VERSION
# Get pepper for this version
pepper = PEPPERS[version]
return nil unless pepper
# Verify with original pepper
peppered = password + pepper
is_valid = BCrypt::Password.new(password_data[:hash]) == peppered
return nil unless is_valid
# Opportunistic upgrade to current version
if version < CURRENT_VERSION
new_password_data = hash_password(password)
return { success: true, upgrade: new_password_data }
end
{ success: true, upgrade: nil }
end
def self.force_upgrade_user(user_id, password)
# Force upgrade during password change or reset
user = find_user(user_id)
# Verify current password with any valid pepper version
(MINIMUM_VERSION..CURRENT_VERSION).each do |version|
pepper = PEPPERS[version]
next unless pepper
peppered = password + pepper
if BCrypt::Password.new(user.password_hash) == peppered
# Upgrade to current version
user.password_data = hash_password(password)
user.save
return true
end
end
false
end
def self.rotation_status
users_per_version = {}
User.find_each do |user|
version = user.password_data[:pepper_version] || 1
users_per_version[version] ||= 0
users_per_version[version] += 1
end
{
current_version: CURRENT_VERSION,
distribution: users_per_version,
outdated_count: users_per_version.select { |v, _| v < CURRENT_VERSION }
.values.sum
}
end
end
# Usage during authentication
def authenticate_user(username, password)
user = find_user(username)
return false unless user
result = PepperRotationSystem.verify_and_upgrade(password, user.password_data)
return false unless result && result[:success]
# Apply upgrade if available
if result[:upgrade]
user.password_data = result[:upgrade]
user.save
puts "Upgraded user #{username} to pepper version #{PepperRotationSystem::CURRENT_VERSION}"
end
true
end
Reference
Salt and Pepper Characteristics
| Property | Salt | Pepper |
|---|---|---|
| Storage Location | Database with hash | Environment variable or key store |
| Uniqueness | Unique per password | Shared across system |
| Visibility | Public within database | Secret, not in database |
| Length Requirement | Minimum 16 bytes | Matches hash output size |
| Purpose | Prevent rainbow tables | Defense in depth against database compromise |
| Rotation Frequency | Never (generated once) | Occasional (requires rehashing) |
| Generation Method | Cryptographically secure random | Cryptographically secure random |
Hash Algorithm Selection
| Algorithm | Work Factor | Memory Usage | Security Status | Ruby Gem |
|---|---|---|---|---|
| bcrypt | High (tunable) | Low | Recommended | bcrypt |
| scrypt | High (tunable) | High (tunable) | Recommended | scrypt |
| Argon2 | High (tunable) | High (tunable) | Most secure | argon2 |
| PBKDF2 | Medium (tunable) | Low | Acceptable | OpenSSL |
| SHA-256 | None | Low | Not recommended | Digest |
| MD5 | None | Low | Broken | Digest |
Common Implementation Patterns
| Pattern | Salt Placement | Pepper Placement | Use Case |
|---|---|---|---|
| Basic Salt | Prepend to password | None | Minimum security requirement |
| Salt with Pepper | Prepend to password | Append after password | High security applications |
| Versioned Pepper | Prepend to password | Append with version | Systems requiring pepper rotation |
| HSM Pepper | Prepend to password | Applied in HSM | Maximum security environments |
Security Checklist
| Check | Requirement | Status |
|---|---|---|
| Salt length | Minimum 16 bytes | Required |
| Salt uniqueness | Unique per password | Required |
| Salt generation | SecureRandom or equivalent | Required |
| Hash algorithm | bcrypt, scrypt, or Argon2 | Required |
| Pepper length | Matches hash output size | Optional |
| Pepper storage | Separate from database | Required if used |
| Constant-time comparison | Rack::Utils.secure_compare | Required |
| Password minimum length | 8 characters minimum | Recommended |
| Work factor | 50-500ms hash time | Recommended |
BCrypt Configuration
| Parameter | Default | Recommended | Description |
|---|---|---|---|
| cost | 10 | 12-14 | Number of iterations (2^cost) |
| salt_len | 16 bytes | 16 bytes | Salt length in bytes |
| hash_len | 23 bytes | 23 bytes | Output hash length |
Argon2 Configuration
| Parameter | Type | Recommended | Description |
|---|---|---|---|
| t_cost | Iterations | 2-4 | Number of iterations |
| m_cost | Memory | 65536 KiB | Memory usage in KiB |
| p_cost | Parallelism | 1-4 | Parallelism degree |
| salt_len | Bytes | 16 | Salt length in bytes |
| hash_len | Bytes | 32 | Output hash length |
Error Messages
| Error | Cause | Resolution |
|---|---|---|
| BCrypt::Errors::InvalidHash | Corrupted hash format | Regenerate password hash |
| ArgumentError: invalid base64 | Incorrect salt encoding | Use Base64.strict_encode64 |
| Timing attack detected | Non-constant-time comparison | Use Rack::Utils.secure_compare |
| Pepper version not found | Missing pepper configuration | Add pepper to environment |
| Unknown pepper version | Outdated pepper version | Implement version migration |