Class: Tep::Password

Inherits:
Object
  • Object
show all
Defined in:
lib/tep/password.rb

Constant Summary collapse

DEFAULT_ITERS =
200000
SALT_BYTES =
16

Class Method Summary collapse

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