Class: Tep::Password
- Inherits:
-
Object
- Object
- Tep::Password
- Defined in:
- lib/tep/password.rb
Constant Summary collapse
- DEFAULT_ITERS =
200000- SALT_BYTES =
16
Class Method Summary collapse
-
.hash(plain) ⇒ Object
Derive a stored hash from a plain password.
-
.split4(s) ⇒ Object
Split a 4-segment “$”-delimited stored hash into its four parts.
-
.verify(plain, stored) ⇒ Object
Verify ‘plain` against a stored hash.
Class Method Details
.hash(plain) ⇒ Object
Derive a stored hash from a plain password. Generates a fresh CSPRNG salt and runs PBKDF2-SHA256 at the default iter count. Returns the self-describing storage string. Named ‘hash` to match the bcrypt-gem-style factory shape.
47 48 49 50 51 |
# File 'lib/tep/password.rb', line 47 def self.hash(plain) salt = Crypto.sp_crypto_random_b64url(SALT_BYTES) derived = Crypto.sp_crypto_pbkdf2_sha256_b64url(plain, salt, DEFAULT_ITERS) "pbkdf2-sha256$" + DEFAULT_ITERS.to_s + "$" + salt + "$" + derived end |
.split4(s) ⇒ Object
Split a 4-segment “$”-delimited stored hash into its four parts. spinel’s ‘String#split` exists but its behaviour on complex inputs has tripped us before; the explicit walker is small and obviously correct.
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/tep/password.rb', line 80 def self.split4(s) out = ["", "", "", ""] n = s.length seg = 0 start = 0 i = 0 while i < n if s[i] == "$" if seg < 4 out[seg] = s[start, i - start] end seg += 1 start = i + 1 end i += 1 end if seg < 4 out[seg] = s[start, n - start] end out end |
.verify(plain, stored) ⇒ Object
Verify ‘plain` against a stored hash. Re-runs PBKDF2 with the same salt + iter count embedded in the stored string and constant-time compares. Rejects malformed stored hashes by returning false.
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/tep/password.rb', line 57 def self.verify(plain, stored) parts = Password.split4(stored) if parts[0] != "pbkdf2-sha256" return false end iters_s = parts[1] salt = parts[2] derived = parts[3] if iters_s.length == 0 || salt.length == 0 || derived.length == 0 return false end iters = iters_s.to_i if iters < 1 return false end candidate = Crypto.sp_crypto_pbkdf2_sha256_b64url(plain, salt, iters) Tep::Jwt.timing_safe_eq(candidate, derived) end |