Module: BetterAuth::Password
- Defined in:
- lib/better_auth/password.rb
Constant Summary collapse
- PREFIX =
"bcrypt_sha256$"- BCRYPT_PREFIXES =
["$2a$", "$2b$", "$2x$", "$2y$"].freeze
- SCRYPT =
{ N: 16_384, r: 16, p: 1, length: 64 }.freeze
Class Method Summary collapse
- .bcrypt_hash?(digest) ⇒ Boolean
- .bcrypt_password_class ⇒ Object
- .call_verifier(verifier, password, digest) ⇒ Object
- .hash(password, hasher: nil, algorithm: :scrypt) ⇒ Object
- .hash_bcrypt(password) ⇒ Object
- .hash_scrypt(password) ⇒ Object
- .password_input(password) ⇒ Object
- .require_bcrypt! ⇒ Object
- .scrypt_hash?(digest) ⇒ Boolean
- .scrypt_key(password, salt) ⇒ Object
- .verify(password:, hash:, verifier: nil, algorithm: :scrypt) ⇒ Object
- .verify_bcrypt(password, digest) ⇒ Object
- .verify_scrypt(password, digest) ⇒ Object
Class Method Details
.bcrypt_hash?(digest) ⇒ Boolean
93 94 95 |
# File 'lib/better_auth/password.rb', line 93 def bcrypt_hash?(digest) BCRYPT_PREFIXES.any? { |prefix| digest.start_with?(prefix) } end |
.bcrypt_password_class ⇒ Object
109 110 111 112 113 114 |
# File 'lib/better_auth/password.rb', line 109 def bcrypt_password_class require "bcrypt" BCrypt::Password rescue LoadError nil end |
.call_verifier(verifier, password, digest) ⇒ Object
101 102 103 104 105 106 107 |
# File 'lib/better_auth/password.rb', line 101 def call_verifier(verifier, password, digest) if verifier.arity == 1 verifier.call(password: password, hash: digest) else verifier.call(password, digest) end end |
.hash(password, hasher: nil, algorithm: :scrypt) ⇒ Object
21 22 23 24 25 26 27 28 29 30 31 32 |
# File 'lib/better_auth/password.rb', line 21 def hash(password, hasher: nil, algorithm: :scrypt) return hasher.call(password) if hasher.respond_to?(:call) case (hasher || algorithm || :scrypt).to_sym when :scrypt hash_scrypt(password) when :bcrypt hash_bcrypt(password) else raise Error, "Unsupported password hasher: #{hasher || algorithm}. Supported hashers are :scrypt and :bcrypt." end end |
.hash_bcrypt(password) ⇒ Object
81 82 83 84 |
# File 'lib/better_auth/password.rb', line 81 def hash_bcrypt(password) klass = require_bcrypt! "#{PREFIX}#{klass.create(password_input(password))}" end |
.hash_scrypt(password) ⇒ Object
52 53 54 55 56 |
# File 'lib/better_auth/password.rb', line 52 def hash_scrypt(password) salt = SecureRandom.random_bytes(16).unpack1("H*") key = scrypt_key(password, salt) "#{salt}:#{key.unpack1("H*")}" end |
.password_input(password) ⇒ Object
48 49 50 |
# File 'lib/better_auth/password.rb', line 48 def password_input(password) Digest::SHA256.hexdigest(password.to_s) end |
.require_bcrypt! ⇒ Object
116 117 118 |
# File 'lib/better_auth/password.rb', line 116 def require_bcrypt! bcrypt_password_class || raise(Error, "The :bcrypt password hasher requires the optional bcrypt gem. Add `gem \"bcrypt\"` to your Gemfile.") end |
.scrypt_hash?(digest) ⇒ Boolean
97 98 99 |
# File 'lib/better_auth/password.rb', line 97 def scrypt_hash?(digest) /\A[0-9a-fA-F]{32}:[0-9a-fA-F]{128}\z/.match?(digest.to_s) end |
.scrypt_key(password, salt) ⇒ Object
70 71 72 73 74 75 76 77 78 79 |
# File 'lib/better_auth/password.rb', line 70 def scrypt_key(password, salt) OpenSSL::KDF.scrypt( password.to_s.unicode_normalize(:nfkc), salt: salt, N: SCRYPT.fetch(:N), r: SCRYPT.fetch(:r), p: SCRYPT.fetch(:p), length: SCRYPT.fetch(:length) ) end |
.verify(password:, hash:, verifier: nil, algorithm: :scrypt) ⇒ Object
34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/better_auth/password.rb', line 34 def verify(password:, hash:, verifier: nil, algorithm: :scrypt) return call_verifier(verifier, password, hash) if verifier.respond_to?(:call) digest = hash.to_s if digest.start_with?(PREFIX) return verify_bcrypt(password_input(password), digest.delete_prefix(PREFIX)) end return verify_bcrypt(password.to_s, digest) if bcrypt_hash?(digest) return verify_scrypt(password, digest) if scrypt_hash?(digest) false end |
.verify_bcrypt(password, digest) ⇒ Object
86 87 88 89 90 91 |
# File 'lib/better_auth/password.rb', line 86 def verify_bcrypt(password, digest) klass = require_bcrypt! klass.new(digest) == password.to_s rescue BCrypt::Errors::InvalidHash false end |
.verify_scrypt(password, digest) ⇒ Object
58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/better_auth/password.rb', line 58 def verify_scrypt(password, digest) salt, key = digest.to_s.split(":", 2) return false unless salt && key expected = scrypt_key(password, salt).unpack1("H*") return false unless expected.bytesize == key.bytesize OpenSSL.fixed_length_secure_compare(expected, key.downcase) rescue OpenSSL::KDF::KDFError, ArgumentError false end |