passlib 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +726 -0
- data/lib/passlib/argon2.rb +48 -0
- data/lib/passlib/balloon.rb +36 -0
- data/lib/passlib/bcrypt.rb +48 -0
- data/lib/passlib/bcrypt_sha256.rb +82 -0
- data/lib/passlib/configuration/context.rb +121 -0
- data/lib/passlib/configuration/passlib.rb +17 -0
- data/lib/passlib/configuration.rb +187 -0
- data/lib/passlib/context.rb +76 -0
- data/lib/passlib/internal/dependency.rb +39 -0
- data/lib/passlib/internal/dsl.rb +59 -0
- data/lib/passlib/internal/register.rb +10 -0
- data/lib/passlib/internal.rb +22 -0
- data/lib/passlib/ldap_digest.rb +106 -0
- data/lib/passlib/md5_crypt.rb +152 -0
- data/lib/passlib/password.rb +169 -0
- data/lib/passlib/pbkdf2.rb +133 -0
- data/lib/passlib/phpass.rb +127 -0
- data/lib/passlib/scrypt.rb +89 -0
- data/lib/passlib/sha1_crypt.rb +108 -0
- data/lib/passlib/sha2_crypt.rb +172 -0
- data/lib/passlib/version.rb +7 -0
- data/lib/passlib/yescrypt.rb +31 -0
- data/lib/passlib.rb +91 -0
- metadata +160 -0
data/README.md
ADDED
|
@@ -0,0 +1,726 @@
|
|
|
1
|
+
# Passlib
|
|
2
|
+
|
|
3
|
+
A Ruby password hashing library inspired by Python's [Passlib](https://passlib.readthedocs.io). It provides a unified interface for creating and verifying password hashes across a wide range of algorithms and auto-detects the algorithm from any stored hash string, making it easy to support multiple formats or migrate between them.
|
|
4
|
+
|
|
5
|
+
Currently supported algorithms include argon2, balloon hashing, bcrypt, bcrypt-sha256, LDAP-style digests (salted and unsalted MD5, SHA-1, SHA-256, SHA-512, SHA3-256, and SHA3-512), MD5-crypt (including Apache variant), phpass, PBKDF2 (with SHA1, SHA256, SHA512, SHA3-256, and SHA3-512), scrypt, SHA1-crypt, SHA2-crypt (SHA-256 and SHA-512 variants), and yescrypt. The library is designed to be extensible, so support for additional algorithms can be added in the future.
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Passlib](#passlib)
|
|
10
|
+
- [Table of Contents](#table-of-contents)
|
|
11
|
+
- [Installation](#installation)
|
|
12
|
+
- [Features](#features)
|
|
13
|
+
- [Create and verify hashes](#create-and-verify-hashes)
|
|
14
|
+
- [Recreating hashes](#recreating-hashes)
|
|
15
|
+
- [Isolated contexts](#isolated-contexts)
|
|
16
|
+
- [Upgrading password hashes](#upgrading-password-hashes)
|
|
17
|
+
- [Check algorithm availability at runtime](#check-algorithm-availability-at-runtime)
|
|
18
|
+
- [Integration with other tools, libraries, and frameworks](#integration-with-other-tools-libraries-and-frameworks)
|
|
19
|
+
- [Devise](#devise)
|
|
20
|
+
- [Configuration](#configuration)
|
|
21
|
+
- [Global configuration](#global-configuration)
|
|
22
|
+
- [Preferred scheme](#preferred-scheme)
|
|
23
|
+
- [Default setting](#default-setting)
|
|
24
|
+
- [Per-algorithm settings](#per-algorithm-settings)
|
|
25
|
+
- [Configuration inheritance](#configuration-inheritance)
|
|
26
|
+
- [Thread safety](#thread-safety)
|
|
27
|
+
- [Supported hash formats and algorithms](#supported-hash-formats-and-algorithms)
|
|
28
|
+
- [`argon2`](#argon2)
|
|
29
|
+
- [`balloon`](#balloon)
|
|
30
|
+
- [`bcrypt`](#bcrypt)
|
|
31
|
+
- [`bcrypt_sha256`](#bcrypt_sha256)
|
|
32
|
+
- [`ldap_digest`](#ldap_digest)
|
|
33
|
+
- [`md5_crypt`](#md5_crypt)
|
|
34
|
+
- [`pbkdf2`](#pbkdf2)
|
|
35
|
+
- [`phpass`](#phpass)
|
|
36
|
+
- [`scrypt`](#scrypt)
|
|
37
|
+
- [`sha1_crypt`](#sha1_crypt)
|
|
38
|
+
- [`sha2_crypt`](#sha2_crypt)
|
|
39
|
+
- [`yescrypt`](#yescrypt)
|
|
40
|
+
- [License](#license)
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
The `passlib` gem includes built-in support for all hash formats that only require OpenSSL (`md5_crypt`, `sha1_crypt`, `sha2_crypt`, `pbkdf2`, `ldap_digest`). Algorithms backed by an external gem are optional and loaded on demand, so you can install only the ones you need.
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
gem install passlib
|
|
48
|
+
|
|
49
|
+
# Optional algorithm dependencies (install any combination):
|
|
50
|
+
gem install bcrypt
|
|
51
|
+
gem install argon2
|
|
52
|
+
gem install scrypt
|
|
53
|
+
gem install balloon_hashing
|
|
54
|
+
gem install yescrypt
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
To install `passlib` together with all optional dependencies at once, use `passlib-all`:
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
gem install passlib-all
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**With Bundler**, add only what you need:
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
# Gemfile
|
|
67
|
+
gem "passlib"
|
|
68
|
+
|
|
69
|
+
# Optional, add any combination:
|
|
70
|
+
gem "bcrypt"
|
|
71
|
+
gem "argon2"
|
|
72
|
+
gem "scrypt"
|
|
73
|
+
gem "balloon_hashing"
|
|
74
|
+
gem "yescrypt"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Or pull in everything at once:
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
# Gemfile
|
|
81
|
+
gem "passlib-all"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Features
|
|
85
|
+
|
|
86
|
+
### Create and verify hashes
|
|
87
|
+
|
|
88
|
+
All hash classes share the same interface. Call `.create` to hash a new password, `.load` to parse a stored hash string, and `#verify` to verify a plaintext against a loaded hash.
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
# Hash a new password (uses the configured default algorithm)
|
|
92
|
+
hash = Passlib.create("hunter2")
|
|
93
|
+
hash.to_s # => "$argon2id$..."
|
|
94
|
+
hash.verify("hunter2") # => true
|
|
95
|
+
hash.verify("wrong") # => false
|
|
96
|
+
|
|
97
|
+
# Load a stored hash and verify
|
|
98
|
+
hash = Passlib.load("$argon2id$...")
|
|
99
|
+
hash.verify("hunter2") # => true
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`Passlib.load` auto-detects the algorithm from the hash string, so you can verify hashes without knowing which algorithm produced them:
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
Passlib.load("$2a$12$...").verify("hunter2") # bcrypt
|
|
106
|
+
Passlib.load("$argon2id$...").verify("hunter2") # argon2
|
|
107
|
+
Passlib.load("{SSHA512}...").verify("hunter2") # LDAP
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
For the common case of verifying a stored hash, `Passlib.verify` combines load and match in one call:
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
Passlib.verify("hunter2", "$argon2id$...") # => true
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The `verify` methods are also aliased as `matches?`, `valid_password?`, `valid_secret?`, and on Password instances as `===`, which allows usage in case statements:
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
hash = Passlib.load("$argon2id$...")
|
|
120
|
+
|
|
121
|
+
case "hunter2"
|
|
122
|
+
when hash then puts "Password is correct"
|
|
123
|
+
else puts "Password is incorrect"
|
|
124
|
+
end
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Recreating hashes
|
|
128
|
+
|
|
129
|
+
Most algorithms also support `#create_comparable`, which re-hashes a plaintext using the same parameters (salt, rounds, etc.) as an existing hash. Not all algorithms implement it, so check with `respond_to?` before calling it directly. Note that comparing the resulting strings with `==` is vulnerable to timing attacks; use `Passlib.secure_compare` instead:
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
if hash.respond_to?(:create_comparable)
|
|
133
|
+
rehashed = hash.create_comparable("hunter2")
|
|
134
|
+
Passlib.secure_compare(rehashed, hash) # => true if passwords match
|
|
135
|
+
end
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Isolated contexts
|
|
139
|
+
|
|
140
|
+
`Passlib::Context` lets you create isolated configuration contexts, each with its own preferred algorithm and settings, without touching the global `Passlib` configuration. This is useful in multi-tenant applications or libraries that want to avoid interfering with the host app's configuration.
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
context = Passlib::Context.new(preferred_scheme: :bcrypt)
|
|
144
|
+
hash = context.create("hunter2")
|
|
145
|
+
hash.class # => Passlib::BCrypt
|
|
146
|
+
context.verify("hunter2", hash) # => true
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
A context can inherit from another context or configuration object, so you can share a base setup and override only what you need:
|
|
150
|
+
|
|
151
|
+
```ruby
|
|
152
|
+
base = Passlib::Context.new(preferred_scheme: :bcrypt)
|
|
153
|
+
context = Passlib::Context.new(base)
|
|
154
|
+
context.create("hunter2").class # => Passlib::BCrypt
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
You can also reconfigure a context after creation using `configure`:
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
context = Passlib::Context.new
|
|
161
|
+
context.configure { |c| c.preferred_scheme = :bcrypt }
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
A context exposes the same interface as the `Passlib` module.
|
|
165
|
+
|
|
166
|
+
### Upgrading password hashes
|
|
167
|
+
|
|
168
|
+
As hashing standards evolve, stored hashes may need to be migrated to a stronger algorithm or higher cost parameters. `Passlib.upgrade?` checks whether a hash needs upgrading, and `Passlib.upgrade` performs the upgrade during login when the plaintext password is available.
|
|
169
|
+
|
|
170
|
+
```ruby
|
|
171
|
+
Passlib.config.preferred_scheme = :bcrypt
|
|
172
|
+
Passlib.config.bcrypt.cost = 12
|
|
173
|
+
|
|
174
|
+
# Check whether a hash is outdated
|
|
175
|
+
Passlib.upgrade?("$2a$04$...") # => true (cost 4 is below the configured 12)
|
|
176
|
+
Passlib.upgrade?("$2a$12$...") # => false (already at cost 12)
|
|
177
|
+
Passlib.upgrade?("{SSHA512}...") # => true (wrong algorithm)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
`upgrade` combines verification and re-hashing in one step, returning a new hash when an upgrade is needed or `nil` when no change is required. Call it at login time and, when it returns a new hash, persist it in place of the old one:
|
|
181
|
+
|
|
182
|
+
```ruby
|
|
183
|
+
def login(user, password)
|
|
184
|
+
return false unless Passlib.verify(password, user.password_hash)
|
|
185
|
+
|
|
186
|
+
if new_hash = Passlib.upgrade(password, user.password_hash)
|
|
187
|
+
user.update(password_hash: new_hash.to_s)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
true
|
|
191
|
+
end
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Or a simple one-line upgrade:
|
|
195
|
+
|
|
196
|
+
```ruby
|
|
197
|
+
hash = Passlib.upgrade(password, hash) || hash
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
`upgrade` verifies the password before re-hashing by default. Pass `verify: false` to skip verification:
|
|
201
|
+
|
|
202
|
+
```ruby
|
|
203
|
+
new_hash = Passlib.upgrade(password, stored_hash, verify: false)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The `upgrade?` method on `Passlib` (or any other [context](#isolated-contexts)) returns `true` in two cases:
|
|
207
|
+
|
|
208
|
+
1. The hash uses a different algorithm than the configured preferred scheme.
|
|
209
|
+
2. It uses the right algorithm but with cost parameters that don't exactly match the current configuration.
|
|
210
|
+
|
|
211
|
+
For the second case, an exact match is required, so a hash with higher costs than configured is also considered outdated (allowing a downgrade when parameters were set too high for acceptable performance).
|
|
212
|
+
|
|
213
|
+
### Check algorithm availability at runtime
|
|
214
|
+
|
|
215
|
+
Since some algorithms depend on optional gems, `Passlib.available?` lets you check at runtime whether a given algorithm can be used:
|
|
216
|
+
|
|
217
|
+
```ruby
|
|
218
|
+
Passlib.available?(:argon2) # => true if the argon2 gem is installed, false otherwise
|
|
219
|
+
Passlib.available?(:bcrypt) # => true if the bcrypt gem is installed, false otherwise
|
|
220
|
+
Passlib.available?(:unknown) # => nil (unrecognized algorithm)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Integration with other tools, libraries, and frameworks
|
|
224
|
+
|
|
225
|
+
### Devise
|
|
226
|
+
|
|
227
|
+
Use the [devise-passlib](https://github.com/rkh/passlib/tree/main/devise-passlib) gem to integrate Passlib with Devise:
|
|
228
|
+
|
|
229
|
+
```ruby
|
|
230
|
+
# app/models/user.rb
|
|
231
|
+
class User < ApplicationRecord
|
|
232
|
+
devise :database_authenticatable, :passlib
|
|
233
|
+
end
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
It will respect the global Passlib configuration, but you can also specify options that only apply to devise hashes:
|
|
237
|
+
|
|
238
|
+
```ruby
|
|
239
|
+
# config/initializers/devise.rb
|
|
240
|
+
Devise.setup do |config|
|
|
241
|
+
config.passlib.preferred_scheme = :argon2
|
|
242
|
+
config.passlib.argon2.profile = :rfc_9106_high_memory
|
|
243
|
+
end
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
This is a drop-in replacement for Devise's default bcrypt implementation, so you can use it with any of the supported algorithms and change the configuration without needing to modify your models or database. With the above configuration, it will automatically upgrade existing bcrypt hashes to argon2 on login.
|
|
247
|
+
|
|
248
|
+
## Configuration
|
|
249
|
+
|
|
250
|
+
### Global configuration
|
|
251
|
+
|
|
252
|
+
`Passlib.config` (also aliased as `Passlib.configuration`) returns the global `Passlib::Configuration` object. Changes to it affect all calls made through the `Passlib` module.
|
|
253
|
+
|
|
254
|
+
```ruby
|
|
255
|
+
Passlib.config.preferred_scheme = :argon2
|
|
256
|
+
Passlib.create("hunter2") # => #<Passlib::Argon2 "$argon2id$...">
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Use `Passlib.configure` (or its alias `Passlib.setup`) to apply several settings in one block:
|
|
260
|
+
|
|
261
|
+
```ruby
|
|
262
|
+
Passlib.configure do |c|
|
|
263
|
+
c.preferred_scheme = :bcrypt
|
|
264
|
+
c.bcrypt.cost = 14
|
|
265
|
+
end
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Preferred scheme
|
|
269
|
+
|
|
270
|
+
`preferred_scheme=` sets the single algorithm used for new hashes. `preferred_schemes=` accepts an ordered list: Passlib picks the first one whose optional gem is available. This lets you specify a preference order without requiring every gem to be installed:
|
|
271
|
+
|
|
272
|
+
```ruby
|
|
273
|
+
Passlib.config.preferred_schemes = [:argon2, :bcrypt, :sha2_crypt]
|
|
274
|
+
Passlib.create("hunter2") # => argon2 if available, otherwise bcrypt, etc.
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
`preferred_scheme` (the reader, without `=`) returns the first available scheme from the list, or the configured single value.
|
|
278
|
+
|
|
279
|
+
#### Default setting
|
|
280
|
+
|
|
281
|
+
The default preference order is:
|
|
282
|
+
|
|
283
|
+
1. [yescrypt](#yescrypt)
|
|
284
|
+
2. [argon2](#argon2)
|
|
285
|
+
3. [balloon](#balloon)
|
|
286
|
+
4. [scrypt](#scrypt)
|
|
287
|
+
5. [bcrypt](#bcrypt)
|
|
288
|
+
6. [pbkdf2](#pbkdf2) with SHA3-512
|
|
289
|
+
7. [pbkdf2](#pbkdf2) with SHA-512
|
|
290
|
+
|
|
291
|
+
The last option should be available in all environments since it only depends on OpenSSL, and works with older OpenSSL versions that don't support SHA3.
|
|
292
|
+
|
|
293
|
+
> [!NOTE]
|
|
294
|
+
> As you can see, [bcrypt-sha256](#bcrypt_sha256) is not included in the default, even though it improves security for long passwords. This is because it is not supported by any other Ruby library, but most environments come with bcrypt loaded by default, and adding it above bcrypt would cause all passwords to be re-hashed in a format that creates a hard dependency on Passlib.
|
|
295
|
+
|
|
296
|
+
### Per-algorithm settings
|
|
297
|
+
|
|
298
|
+
Each algorithm has its own configuration namespace under `Passlib.config`. Algorithm-specific options (cost, rounds, variant, etc.) are set there:
|
|
299
|
+
|
|
300
|
+
```ruby
|
|
301
|
+
Passlib.configure do |config|
|
|
302
|
+
config.bcrypt.cost = 14
|
|
303
|
+
config.argon2.t_cost = 4
|
|
304
|
+
config.argon2.m_cost = 18 # 2^18 = 256 MiB
|
|
305
|
+
config.sha2_crypt.rounds = 800_000
|
|
306
|
+
config.pbkdf2.rounds = 40_000
|
|
307
|
+
config.scrypt.ln = 17
|
|
308
|
+
end
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
The full list of options for each algorithm is documented in the [Supported hash formats and algorithms](#supported-hash-formats-and-algorithms) section.
|
|
312
|
+
|
|
313
|
+
### Configuration inheritance
|
|
314
|
+
|
|
315
|
+
`Passlib::Configuration` objects can inherit from a parent. A child reads the parent's value for any option it has not set itself, and changes to the parent propagate until the child is frozen.
|
|
316
|
+
|
|
317
|
+
```ruby
|
|
318
|
+
base = Passlib::Configuration.new(preferred_scheme: :bcrypt)
|
|
319
|
+
base.bcrypt.cost = 12
|
|
320
|
+
|
|
321
|
+
child = Passlib::Configuration.new(base)
|
|
322
|
+
child.preferred_scheme # => :bcrypt (inherited)
|
|
323
|
+
child.bcrypt.cost # => 12 (inherited)
|
|
324
|
+
|
|
325
|
+
child.bcrypt.cost = 14
|
|
326
|
+
child.bcrypt.cost # => 14 (overridden)
|
|
327
|
+
base.bcrypt.cost # => 12 (unchanged)
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
`freeze` snapshots all values from the parent chain into the child and prevents further mutation. The parent itself is not frozen:
|
|
331
|
+
|
|
332
|
+
```ruby
|
|
333
|
+
child.freeze
|
|
334
|
+
base.bcrypt.cost = 16
|
|
335
|
+
child.bcrypt.cost # => 14 (snapshot is unaffected)
|
|
336
|
+
base.frozen? # => false
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Thread safety
|
|
340
|
+
|
|
341
|
+
The global configuration uses `Concurrent::Map` for its options store and is safe to read concurrently. Writes should be done at application startup before any concurrent access. For runtime isolation, create a `Passlib::Context` (see [isolated contexts](#isolated-contexts)) per request or tenant instead of mutating the global config.
|
|
342
|
+
|
|
343
|
+
## Supported hash formats and algorithms
|
|
344
|
+
|
|
345
|
+
### `argon2`
|
|
346
|
+
|
|
347
|
+
Uses the [argon2](https://github.com/technion/ruby-argon2/) gem, which is a wrapper around the reference C implementation of the Argon2 password hashing algorithm. This is the OWASP recommended password hashing algorithm, and is the winner of the Password Hashing Competition. It is designed to be resistant to GPU cracking attacks, and is highly configurable in terms of memory usage, time cost, and parallelism.
|
|
348
|
+
|
|
349
|
+
``` ruby
|
|
350
|
+
hash = Passlib::Argon2.hash("password") # => #<Passlib::Argon2 "$argon2id$...">
|
|
351
|
+
hash.to_s # => "$argon2id$..."
|
|
352
|
+
hash.verify("password") # => true
|
|
353
|
+
|
|
354
|
+
# Pass options to argon2 gem:
|
|
355
|
+
Passlib::Argon2.hash("password", t_cost: 4, m_cost: 16) # => #<Passlib::Argon2 "$argon2id$...">
|
|
356
|
+
|
|
357
|
+
# Change the default options:
|
|
358
|
+
Passlib.config.argon2.profile = :rfc_9106_high_memory
|
|
359
|
+
|
|
360
|
+
# Set argon2 to be the default for new hashes:
|
|
361
|
+
Passlib.config.default_scheme = :argon2
|
|
362
|
+
Passlib.create("password") # => #<Passlib::Argon2 "$argon2id$...">
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Generated hashes are in the **Modular Crypt Format (MCF)**, using `argon2id` identifier.
|
|
366
|
+
It is also possible to load hashes with the IDs `argon2i` and `argon2d`.
|
|
367
|
+
|
|
368
|
+
Hash format:
|
|
369
|
+
|
|
370
|
+
```ruby
|
|
371
|
+
"$argon2#d$v=#{v}$m=#{m},t=#{t},p=#{p}$#{salt}$#{digest}"
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
The implementation does not (currently) support recreating the exact same hash.
|
|
375
|
+
|
|
376
|
+
### `balloon`
|
|
377
|
+
|
|
378
|
+
Uses the [balloon_hashing](https://rubygems.org/gems/balloon_hashing) gem. Balloon hashing is a memory-hard password hashing function designed to be resistant to GPU and ASIC attacks.
|
|
379
|
+
|
|
380
|
+
```ruby
|
|
381
|
+
hash = Passlib::Balloon.create("password") # => #<Passlib::Balloon "$balloon$...">
|
|
382
|
+
hash.verify("password") # => true
|
|
383
|
+
|
|
384
|
+
# With options:
|
|
385
|
+
Passlib::Balloon.create("password", s_cost: 1024, t_cost: 3, algorithm: "sha256")
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Generated hashes are in the **Modular Crypt Format (MCF)**.
|
|
389
|
+
|
|
390
|
+
Hash format:
|
|
391
|
+
|
|
392
|
+
```ruby
|
|
393
|
+
"$balloon$v=1$alg=#{algorithm},s=#{s_cost},t=#{t_cost}$#{salt}$#{checksum}"
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
Options:
|
|
397
|
+
- `:s_cost` — space cost (memory usage)
|
|
398
|
+
- `:t_cost` — time cost (iterations)
|
|
399
|
+
- `:algorithm` — digest algorithm name (e.g. `"sha256"`)
|
|
400
|
+
|
|
401
|
+
### `bcrypt`
|
|
402
|
+
|
|
403
|
+
Standard hashing algorithm used by Rails and Devise. Uses the [bcrypt](https://github.com/bcrypt-ruby/bcrypt-ruby) gem. It uses the Blowfish cipher internally, and is designed to be slow and resistant to brute-force attacks. It is widely supported and has been around for a long time, but is no longer considered the best choice for password hashing due to its relatively low memory usage.
|
|
404
|
+
|
|
405
|
+
```ruby
|
|
406
|
+
hash = Passlib::BCrypt.create("password", cost: 12)
|
|
407
|
+
hash.verify("password") # => true
|
|
408
|
+
hash.to_s # => "$2a$12$..."
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Generated hashes are in the **Modular Crypt Format (MCF)**, using the `$2a$` identifier. Hashes with the IDs `$2b$`, `$2x$`, and `$2y$` are also accepted on load.
|
|
412
|
+
|
|
413
|
+
Hash format:
|
|
414
|
+
|
|
415
|
+
```ruby
|
|
416
|
+
"$2a$#{cost}$#{salt}#{checksum}"
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
Recreating the exact same hash is supported via `:salt`.
|
|
420
|
+
|
|
421
|
+
Options:
|
|
422
|
+
- `:cost` — bcrypt cost factor, 4–31 (default: `BCrypt::Engine::DEFAULT_COST`)
|
|
423
|
+
- `:salt` — custom bcrypt salt string (normally auto-generated, must include the cost factor in standard bcrypt format)
|
|
424
|
+
|
|
425
|
+
### `bcrypt_sha256`
|
|
426
|
+
|
|
427
|
+
A hybrid scheme that works around bcrypt's 72-byte password truncation limit. The password is first run through HMAC-SHA256 (keyed with the bcrypt salt), the 32-byte result is base64-encoded, and that string is then hashed with standard bcrypt. Passwords of any length are handled correctly. Uses the [bcrypt](https://github.com/bcrypt-ruby/bcrypt-ruby) gem.
|
|
428
|
+
|
|
429
|
+
This format is compatible with Python's [passlib.hash.bcrypt_sha256](https://passlib.readthedocs.io/en/stable/lib/passlib.hash.bcrypt_sha256.html).
|
|
430
|
+
|
|
431
|
+
```ruby
|
|
432
|
+
hash = Passlib::BcryptSHA256.create("password", cost: 12)
|
|
433
|
+
hash.verify("password") # => true
|
|
434
|
+
hash.to_s # => "$bcrypt-sha256$v=2,t=2b,r=12$..."
|
|
435
|
+
|
|
436
|
+
# Long passwords are not truncated:
|
|
437
|
+
long = "a" * 100
|
|
438
|
+
Passlib::BcryptSHA256.create(long).verify(long) # => true
|
|
439
|
+
Passlib::BcryptSHA256.create(long).verify("a" * 99 + "b") # => false
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
Generated hashes are in the **Modular Crypt Format (MCF)**, using the `$bcrypt-sha256$` identifier.
|
|
443
|
+
|
|
444
|
+
Hash format:
|
|
445
|
+
|
|
446
|
+
```
|
|
447
|
+
$bcrypt-sha256$v=2,t=2b,r=#{cost}$#{salt22}$#{digest31}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
Options:
|
|
451
|
+
- `:cost` — bcrypt cost factor, 4–31 (default: `BCrypt::Engine::DEFAULT_COST`)
|
|
452
|
+
- `:salt` — custom bcrypt salt string in `$2b$NN$<22chars>` format (normally auto-generated)
|
|
453
|
+
|
|
454
|
+
### `ldap_digest`
|
|
455
|
+
|
|
456
|
+
> [!WARNING]
|
|
457
|
+
> Not all of the supported variants are considered secure by modern standards. Moreover, even the ones that are aren't suitable for new hashes due to their low iteration count and lack of memory hardness (they aren't key derivation functions). They are supported here to verify existing hashes and migrate users to a stronger scheme on next login. Do not use them for new password hashes.
|
|
458
|
+
|
|
459
|
+
LDAP RFC 2307-style password hashes using digest algorithms. Implemented using OpenSSL, no additional gem dependencies.
|
|
460
|
+
|
|
461
|
+
Plain (unsalted) and salted variants are supported for MD5, SHA-1, SHA-256, SHA-512, SHA3-256, and SHA3-512. MD5 and SHA-1 hashes may be stored in hex encoding (as produced by some LDAP implementations) or standard base64, both are detected automatically on load.
|
|
462
|
+
|
|
463
|
+
```ruby
|
|
464
|
+
hash = Passlib::LdapDigest.create("password", variant: "SSHA512")
|
|
465
|
+
hash.verify("password") # => true
|
|
466
|
+
hash.to_s # => "{SSHA512}..."
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
Supported variants (LDAP scheme names):
|
|
470
|
+
|
|
471
|
+
| Variant | Algorithm | Salted |
|
|
472
|
+
|-------------|-----------|--------|
|
|
473
|
+
| `MD5` | MD5 | no |
|
|
474
|
+
| `SMD5` | MD5 | yes |
|
|
475
|
+
| `SHA` | SHA-1 | no |
|
|
476
|
+
| `SSHA` | SHA-1 | yes |
|
|
477
|
+
| `SHA256` | SHA-256 | no |
|
|
478
|
+
| `SSHA256` | SHA-256 | yes |
|
|
479
|
+
| `SHA512` | SHA-512 | no |
|
|
480
|
+
| `SSHA512` | SHA-512 | yes |
|
|
481
|
+
| `SHA3-256` | SHA3-256 | no |
|
|
482
|
+
| `SSHA3-256` | SHA3-256 | yes |
|
|
483
|
+
| `SHA3-512` | SHA3-512 | no |
|
|
484
|
+
| `SSHA3-512` | SHA3-512 | yes |
|
|
485
|
+
|
|
486
|
+
Hash format:
|
|
487
|
+
|
|
488
|
+
```ruby
|
|
489
|
+
"{#{variant}}#{checksum_b64}" # base64 encoding (default)
|
|
490
|
+
"{#{variant}}#{checksum_hex}" # hex encoding (MD5 and SHA only)
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
Default variant: `SSHA512`.
|
|
494
|
+
|
|
495
|
+
Options:
|
|
496
|
+
- `:variant` — LDAP scheme name (case-insensitive, symbols accepted, underscores may substitute dashes)
|
|
497
|
+
- `:salt` — raw binary salt (only used for salted schemes, default: 4 random bytes for MD5/SHA-1, 8 for others)
|
|
498
|
+
- `:hex` — encode as hex instead of base64, only applicable to `MD5` and `SHA` schemes (default: `false`)
|
|
499
|
+
|
|
500
|
+
### `md5_crypt`
|
|
501
|
+
|
|
502
|
+
> [!WARNING]
|
|
503
|
+
> MD5-crypt is a legacy algorithm with a fixed, low round count that is vulnerable to brute-force attacks with modern hardware. It is supported here to verify existing hashes and migrate users to a stronger scheme. Do not use it for new hashes.
|
|
504
|
+
|
|
505
|
+
MD5-crypt was designed by Poul-Henning Kamp for FreeBSD in 1994 and was for many years the default password scheme on Linux systems. It runs 1000 rounds of an MD5-based mixing function. Implemented using OpenSSL, no additional gem dependencies.
|
|
506
|
+
|
|
507
|
+
Apache's variant (`$apr1$`) is also supported. The two variants are algorithmically identical and differ only in their MCF identifier. Both are auto-detected on load.
|
|
508
|
+
|
|
509
|
+
```ruby
|
|
510
|
+
hash = Passlib::MD5Crypt.create("hunter2")
|
|
511
|
+
hash.verify("hunter2") # => true
|
|
512
|
+
hash.to_s # => "$1$...$..."
|
|
513
|
+
|
|
514
|
+
apr = Passlib::MD5Crypt.create("hunter2", variant: :apr)
|
|
515
|
+
apr.to_s # => "$apr1$...$..."
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
Hash format:
|
|
519
|
+
|
|
520
|
+
```ruby
|
|
521
|
+
"$1$#{salt}$#{checksum}" # standard MD5-crypt
|
|
522
|
+
"$apr1$#{salt}$#{checksum}" # Apache APR variant
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
- `salt` — 0-8 characters from `./0-9A-Za-z` (default: 8 random characters)
|
|
526
|
+
- `checksum` — 22-character encoding of the 16-byte MD5 digest
|
|
527
|
+
|
|
528
|
+
Options:
|
|
529
|
+
- `:variant` — selects the MCF identifier: `:standard` (default, produces `$1$`) or `:apr` (produces `$apr1$`)
|
|
530
|
+
- `:salt` — custom salt string, 0-8 characters (default: 8 random characters)
|
|
531
|
+
|
|
532
|
+
### `pbkdf2`
|
|
533
|
+
|
|
534
|
+
PBKDF2 hashes via OpenSSL, no additional gem dependencies. New hashes are always produced in the **Modular Crypt Format (MCF)**. Two additional formats are accepted on load and normalized to MCF: LDAP-style hashes and Cryptacular's `cta_pbkdf2_sha1` format.
|
|
535
|
+
|
|
536
|
+
```ruby
|
|
537
|
+
hash = Passlib::PBKDF2.create("password", variant: "pbkdf2-sha256", rounds: 29_000)
|
|
538
|
+
hash.verify("password") # => true
|
|
539
|
+
hash.to_s # => "$pbkdf2-sha256$29000$...$..."
|
|
540
|
+
|
|
541
|
+
# Load and normalize an LDAP hash to MCF:
|
|
542
|
+
Passlib::PBKDF2.load("{PBKDF2-SHA256}29000$...$...").to_s
|
|
543
|
+
# => "$pbkdf2-sha256$29000$...$..."
|
|
544
|
+
|
|
545
|
+
# Load and normalize a Cryptacular cta_pbkdf2_sha1 hash to MCF:
|
|
546
|
+
Passlib::PBKDF2.load("$p5k2$2710$...$...").to_s
|
|
547
|
+
# => "$pbkdf2$10000$...$..."
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
Hash format (MCF, used for new hashes):
|
|
551
|
+
|
|
552
|
+
```ruby
|
|
553
|
+
"$#{variant}$#{rounds}$#{salt_ab64}$#{dk_ab64}"
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
Cryptacular `cta_pbkdf2_sha1` format (accepted on load, normalized to MCF):
|
|
557
|
+
|
|
558
|
+
```ruby
|
|
559
|
+
"$p5k2$#{rounds_hex}$#{salt_b64url}$#{dk_b64url}"
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
Supported variants:
|
|
563
|
+
|
|
564
|
+
| MCF variant | LDAP variant | Digest | Default rounds | Key length |
|
|
565
|
+
|--------------------|--------------------|----------|----------------|------------|
|
|
566
|
+
| `pbkdf2` | `PBKDF2` | SHA-1 | 131,000 | 20 bytes |
|
|
567
|
+
| `pbkdf2-sha256` | `PBKDF2-SHA256` | SHA-256 | 29,000 | 32 bytes |
|
|
568
|
+
| `pbkdf2-sha512` | `PBKDF2-SHA512` | SHA-512 | 25,000 | 64 bytes |
|
|
569
|
+
| `pbkdf2-sha3-256` | `PBKDF2-SHA3-256` | SHA3-256 | 29,000 | 32 bytes |
|
|
570
|
+
| `pbkdf2-sha3-512` | `PBKDF2-SHA3-512` | SHA3-512 | 25,000 | 64 bytes |
|
|
571
|
+
|
|
572
|
+
Default variant: `pbkdf2-sha3-512` if the OpenSSL build supports SHA3, otherwise `pbkdf2-sha512`.
|
|
573
|
+
|
|
574
|
+
Options:
|
|
575
|
+
- `:variant` — digest variant (case-insensitive, symbols accepted, underscores may substitute dashes)
|
|
576
|
+
- `:rounds` — iteration count (default: variant-specific, see table above)
|
|
577
|
+
- `:salt` — raw binary salt (default: 16 random bytes)
|
|
578
|
+
- `:key_len` — derived key length in bytes (default: variant-specific, see table above)
|
|
579
|
+
|
|
580
|
+
### `phpass`
|
|
581
|
+
|
|
582
|
+
> [!WARNING]
|
|
583
|
+
> `phpass` is a legacy algorithm based on MD5 and should not be used for new hashes. It is supported here to verify existing hashes and migrate users to a stronger scheme on next login.
|
|
584
|
+
|
|
585
|
+
The phpass Portable Hash, widely used by PHP applications such as WordPress, Drupal, and phpBB as a fallback password scheme. It applies iterated MD5 with a configurable round count. Implemented using OpenSSL, no additional gem dependencies.
|
|
586
|
+
|
|
587
|
+
Both the standard `$P$` identifier and the phpBB3 `$H$` variant are recognized on load. New hashes are always produced with `$P$`.
|
|
588
|
+
|
|
589
|
+
```ruby
|
|
590
|
+
hash = Passlib::PHPass.create("password", rounds: 19)
|
|
591
|
+
hash.verify("password") # => true
|
|
592
|
+
hash.to_s # => "$P$H..."
|
|
593
|
+
|
|
594
|
+
# Load and verify a $H$ (phpBB3) hash without any conversion:
|
|
595
|
+
Passlib::PHPass.load("$H$9IQRg...").verify("password")
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
Hash format:
|
|
599
|
+
|
|
600
|
+
```ruby
|
|
601
|
+
"$P$#{rounds}#{salt}#{checksum}"
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
- `rounds` — single character from the phpass alphabet encoding `log2(iterations)`, 7–30
|
|
605
|
+
- `salt` — 8-character salt using the phpass alphabet `./0-9A-Za-z`
|
|
606
|
+
- `checksum` — 22-character encoding of the 16-byte MD5 digest
|
|
607
|
+
|
|
608
|
+
Options:
|
|
609
|
+
- `:rounds` — base-2 log of the iteration count, 7–30, clamped if out of range (default: 19, i.e. 2^19 = 524 288 iterations)
|
|
610
|
+
- `:salt` — custom 8-character salt using the phpass alphabet (normally auto-generated)
|
|
611
|
+
|
|
612
|
+
### `scrypt`
|
|
613
|
+
|
|
614
|
+
Uses the [scrypt](https://rubygems.org/gems/scrypt) gem. scrypt is a memory-hard key derivation function designed to be expensive in both CPU and memory, making it resistant to brute-force attacks with custom hardware.
|
|
615
|
+
|
|
616
|
+
```ruby
|
|
617
|
+
hash = Passlib::SCrypt.create("password", ln: 14)
|
|
618
|
+
hash.verify("password") # => true
|
|
619
|
+
hash.to_s # => "$scrypt$ln=14,r=8,p=1$...$..."
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
Generated hashes are in the **Modular Crypt Format (MCF)**. Two hash formats are accepted on load:
|
|
623
|
+
|
|
624
|
+
```ruby
|
|
625
|
+
"$scrypt$ln=#{ln},r=#{r},p=#{p}$#{salt}$#{checksum}" # Passlib MCF
|
|
626
|
+
"#{n_hex}$#{r_hex}$#{p_hex}$#{salt_hex}$#{checksum_hex}" # scrypt gem native (normalized to MCF on load)
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
New hashes are always produced in the Passlib MCF format.
|
|
630
|
+
|
|
631
|
+
Options:
|
|
632
|
+
- `:ln` — CPU/memory cost as a base-2 log (default: `16`, meaning N=65536), mutually exclusive with `:n`
|
|
633
|
+
- `:n` — CPU/memory cost as an integer power of two (converted to `:ln` internally)
|
|
634
|
+
- `:r` — block size (default: `8`)
|
|
635
|
+
- `:p` — parallelization factor (default: `1`)
|
|
636
|
+
- `:salt` — custom salt as a binary string (default: 16 random bytes)
|
|
637
|
+
- `:key_len` — derived key length in bytes (default: `32`)
|
|
638
|
+
|
|
639
|
+
### `sha1_crypt`
|
|
640
|
+
|
|
641
|
+
> [!WARNING]
|
|
642
|
+
> `sha1_crypt` is a legacy algorithm and should not be used for new hashes. It is supported here to verify existing hashes and migrate users to a stronger scheme on next login.
|
|
643
|
+
|
|
644
|
+
NetBSD's crypt-sha1 algorithm designed by Simon Gerraty. It applies iterated HMAC-SHA1 and supports passwords of any length. Implemented using OpenSSL, no additional gem dependencies.
|
|
645
|
+
|
|
646
|
+
```ruby
|
|
647
|
+
hash = Passlib::SHA1Crypt.create("password", rounds: 480_000)
|
|
648
|
+
hash.verify("password") # => true
|
|
649
|
+
hash.to_s # => "$sha1$480000$...$..."
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
Hash format:
|
|
653
|
+
|
|
654
|
+
```ruby
|
|
655
|
+
"$sha1$#{rounds}$#{salt}$#{checksum}"
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
- `rounds` — decimal iteration count, 1–4,294,967,295
|
|
659
|
+
- `salt` — 1–64 characters from `./0-9A-Za-z` (default: 8 random characters)
|
|
660
|
+
- `checksum` — 28-character encoding of the 20-byte HMAC-SHA1 digest
|
|
661
|
+
|
|
662
|
+
Options:
|
|
663
|
+
- `:rounds` — iteration count, 1–4,294,967,295, clamped if out of range (default: 480,000)
|
|
664
|
+
- `:salt` — custom salt string, up to 64 characters (default: 8 random characters)
|
|
665
|
+
|
|
666
|
+
### `sha2_crypt`
|
|
667
|
+
|
|
668
|
+
Pure-Ruby implementation of the SHA-crypt algorithm as specified by Ulrich Drepper. Implemented using OpenSSL, no additional gem dependencies.
|
|
669
|
+
|
|
670
|
+
Generated hashes are in the **Modular Crypt Format (MCF)**. Both SHA-256 (`$5$`) and SHA-512 (`$6$`) variants are supported and auto-detected on load.
|
|
671
|
+
|
|
672
|
+
```ruby
|
|
673
|
+
hash = Passlib::SHA2Crypt.create("password", bits: 512, rounds: 10_000)
|
|
674
|
+
hash.verify("password") # => true
|
|
675
|
+
hash.to_s # => "$6$rounds=10000$...$..."
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
Hash format:
|
|
679
|
+
|
|
680
|
+
```ruby
|
|
681
|
+
"$#{id}$rounds=#{rounds}$#{salt}$#{checksum}" # explicit rounds
|
|
682
|
+
"$#{id}$#{salt}$#{checksum}" # implicit rounds (5,000)
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
| Variant | `id` | Digest | Default rounds | Checksum length |
|
|
686
|
+
|---------|------|---------|----------------|-----------------|
|
|
687
|
+
| SHA-256 | `5` | SHA-256 | 535,000 | 43 characters |
|
|
688
|
+
| SHA-512 | `6` | SHA-512 | 656,000 | 86 characters |
|
|
689
|
+
|
|
690
|
+
Rounds are clamped to the range 1,000–999,999,999. If no `rounds=` parameter appears in the hash string, 5,000 rounds are assumed (per spec).
|
|
691
|
+
|
|
692
|
+
Options:
|
|
693
|
+
- `:bits` — selects the SHA variant: `256` or `512` (default: `512`)
|
|
694
|
+
- `:rounds` — number of hashing rounds (default: variant-specific, see table above)
|
|
695
|
+
- `:salt` — custom salt string, up to 16 characters (default: random)
|
|
696
|
+
|
|
697
|
+
### `yescrypt`
|
|
698
|
+
|
|
699
|
+
Uses the [yescrypt](https://rubygems.org/gems/yescrypt) gem. yescrypt is the successor to scrypt, used as the default password hashing scheme in several modern Linux distributions.
|
|
700
|
+
|
|
701
|
+
```ruby
|
|
702
|
+
hash = Passlib::Yescrypt.create("password")
|
|
703
|
+
hash.verify("password") # => true
|
|
704
|
+
hash.to_s # => "$y$..."
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
Generated hashes are in the **Modular Crypt Format (MCF)**.
|
|
708
|
+
|
|
709
|
+
Hash format:
|
|
710
|
+
|
|
711
|
+
```ruby
|
|
712
|
+
"$y$#{params}$#{salt}$#{checksum}"
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
Options:
|
|
716
|
+
- `:n_log2` — base-2 log of the memory/CPU cost factor
|
|
717
|
+
- `:r` — block size
|
|
718
|
+
- `:p` — parallelization factor
|
|
719
|
+
- `:t` — additional time parameter
|
|
720
|
+
- `:flags` — algorithm flags bitmask
|
|
721
|
+
- `:salt` — custom salt (normally auto-generated)
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
## License
|
|
725
|
+
|
|
726
|
+
Passlib is released under the MIT License. See [MIT-LICENSE](MIT-LICENSE) for details.
|