Password strength checking, policy validation, pattern detection, hashing, and secure generation
gem install philiprehberger-passwordPassword strength checking, policy validation, pattern detection, hashing, and secure generation
Add to your Gemfile:
gem "philiprehberger-password"
Or install directly:
gem install philiprehberger-password
require "philiprehberger/password"
result = Philiprehberger::Password.strength("MyP@ssw0rd!")
result[:score] # => 3
result[:label] # => :strong
result[:entropy] # => 72.08
Estimated password entropy in bits — length * log2(pool_size) where the pool is inferred from the character classes present:
Philiprehberger::Password.entropy("aaaaaa") # => 28.20
Philiprehberger::Password.entropy("MyP@ssw0rd!") # => 72.08
Philiprehberger::Password.entropy("") # => 0.0
Return the 0-4 integer score without the full strength hash:
Philiprehberger::Password.score("password") # => 0
Philiprehberger::Password.score("MyP@ssw0rd!") # => 4
Philiprehberger::Password.strong?("password") # => false
Philiprehberger::Password.strong?("MyP@ssw0rd!") # => true
Philiprehberger::Password.strong?("MyP@ssw0rd!", threshold: 4) # => true / false
Default threshold: is 3 (the "strong" tier on the 0-4 scale: terrible/weak/fair/strong/excellent). Raise it to 4 for stricter gating.
Philiprehberger::Password.common?("password") # => true
Philiprehberger::Password.common?("xK9#mZ2!pQ") # => false
policy = Philiprehberger::Password::Policy.new(
min_length: 12,
require_uppercase: true,
require_digit: true,
require_symbol: true,
reject_common: true,
custom_passwords: ["companyname", "internalpass"]
)
result = policy.validate("short")
result.valid? # => false
result.errors # => ["must be at least 12 characters", ...]
result.score # => 0
policy = Philiprehberger::Password::Policy.new
result = policy.validate("johndoe2024!", context: {
username: "johndoe",
email: "johndoe@example.com",
app_name: "myapp"
})
result.valid? # => false
result.errors # => ["must not contain your username", "must not contain your email username"]
patterns = Philiprehberger::Password.keyboard_patterns("qwertyaaa123456")
# => [
# { type: :keyboard_row, token: "qwerty", start: 0, length: 6, direction: :forward },
# { type: :repeated, token: "aaa", start: 6, length: 3, repeated_char: "a" },
# { type: :sequence, token: "123456", start: 9, length: 6, sequence_type: :numeric, direction: :ascending }
# ]
# Requires bcrypt gem: gem install bcrypt
hash = Philiprehberger::Password.hash("my-secret-password", cost: 12)
# => "$2a$12$..."
Philiprehberger::Password.verify("my-secret-password", hash)
# => true
Philiprehberger::Password.verify("wrong-password", hash)
# => false
# Random password
Philiprehberger::Password.generate(length: 20)
# => "kX9#mZ2!pQ7@wR4bN5&j"
# Passphrase (200+ word list)
Philiprehberger::Password.generate(style: :passphrase, words: 4, separator: "-")
# => "correct-horse-battery-staple"
# PIN
Philiprehberger::Password.generate(style: :pin, length: 6)
# => "482917"
result = Philiprehberger::Password.zxcvbn("p@ssw0rd123")
result[:score] # => 1
result[:crack_time_display] # => "minutes"
result[:patterns] # => [{ type: :leet, token: "p@ssw0rd", ... }, ...]
Grade many passwords in one call. Useful for password audits across user
lists or seeded test fixtures. Results are returned in input order; each
element is coerced via to_s so non-string entries don't raise.
results = Philiprehberger::Password.batch_strength([
"hunter2",
"P@ssw0rd!",
"C0rr3ctH0rseB4tt3ryStapl3"
])
results.map { |r| r[:score] }
# => [0, 2, 4]
Philiprehberger::Password.mask("hunter2") # => "*******"
Philiprehberger::Password.mask("hunter2", visible: 2) # => "*****r2"
Philiprehberger::Password.mask("hunter2", mask: "•") # => "•••••••"
Philiprehberger::Password| Method | Description |
|---|---|
.common?(password) | Returns true if password is in the common password dictionary |
.strength(password) | Returns hash with :score (0-4), :label, :entropy |
.batch_strength(passwords) | Returns array of strength hashes, one per password, in input order |
.entropy(password) | Estimated entropy in bits (Float) |
.score(password) | Strength score as integer 0-4 |
.strong?(password, threshold: 3) | Returns true when score >= threshold |
.generate(**options) | Generate a password (see options below) |
.keyboard_patterns(password) | Returns array of detected keyboard/sequence/repeat patterns |
.hash(password, cost: 12) | Hash password with bcrypt (requires bcrypt gem) |
.verify(password, hash) | Verify password against bcrypt hash (requires bcrypt gem) |
.zxcvbn(password) | Returns hash with :score (0-4), :patterns, :crack_time_display |
.mask(password, visible: 0, mask: '*') | Redact password for display; reveals trailing visible characters |
| Option | Default | Description |
|---|---|---|
length | 16 | Password length |
uppercase | true | Include uppercase letters |
lowercase | true | Include lowercase letters |
digits | true | Include digits |
symbols | true | Include symbols |
style | nil | :passphrase or :pin for alternative styles |
words | 4 | Word count for passphrase style |
separator | "-" | Separator for passphrase style |
Philiprehberger::Password::Policy| Method | Description |
|---|---|
.new(**options) | Create policy (min_length, max_length, require_uppercase, require_lowercase, require_digit, require_symbol, reject_common, custom_passwords) |
#validate(password, context: {}) | Returns Result with .valid?, .errors, .score. Context accepts :username, :email, :app_name |
| Score | Label | Entropy |
|---|---|---|
| 0 | :terrible | < 28 bits |
| 1 | :weak | < 36 bits |
| 2 | :fair | < 60 bits |
| 3 | :strong | < 80 bits |
| 4 | :excellent | >= 80 bits |
| Type | Description |
|---|---|
:dictionary | Common password or known word detected |
:leet | L33t-speak substitution of a known word |
:spatial | QWERTY keyboard adjacency pattern |
:date | Date pattern (yyyy, mm/dd/yyyy, etc.) |
:sequence | Alphabetic or numeric sequence |
:repeated | Repeated characters |
bundle install
bundle exec rspec
bundle exec rubocop
If you find this project useful: