lib/authlogic/crypto_providers/bcrypt.rb
# frozen_string_literal: true
require "bcrypt"
module Authlogic
module CryptoProviders
# The family of adaptive hash functions (BCrypt, SCrypt, PBKDF2)
# is the best choice for password storage today. They have the
# three properties of password hashing that are desirable. They
# are one-way, unique, and slow. While a salted SHA or MD5 hash is
# one-way and unique, preventing rainbow table attacks, they are
# still lightning fast and attacks on the stored passwords are
# much more effective. This benchmark demonstrates the effective
# slowdown that BCrypt provides:
#
# require "bcrypt"
# require "digest"
# require "benchmark"
#
# Benchmark.bm(18) do |x|
# x.report("BCrypt (cost = 10:") {
# 100.times { BCrypt::Password.create("mypass", :cost => 10) }
# }
# x.report("BCrypt (cost = 4:") {
# 100.times { BCrypt::Password.create("mypass", :cost => 4) }
# }
# x.report("Sha512:") {
# 100.times { Digest::SHA512.hexdigest("mypass") }
# }
# x.report("Sha1:") {
# 100.times { Digest::SHA1.hexdigest("mypass") }
# }
# end
#
# user system total real
# BCrypt (cost = 10): 37.360000 0.020000 37.380000 ( 37.558943)
# BCrypt (cost = 4): 0.680000 0.000000 0.680000 ( 0.677460)
# Sha512: 0.000000 0.000000 0.000000 ( 0.000672)
# Sha1: 0.000000 0.000000 0.000000 ( 0.000454)
#
# You can play around with the cost to get that perfect balance
# between performance and security. A default cost of 10 is the
# best place to start.
#
# Decided BCrypt is for you? Just install the bcrypt gem:
#
# gem install bcrypt
#
# Tell acts_as_authentic to use it:
#
# acts_as_authentic do |c|
# c.crypto_provider = Authlogic::CryptoProviders::BCrypt
# end
#
# You are good to go!
class BCrypt
class << self
# This is the :cost option for the BCrpyt library. The higher the cost
# the more secure it is and the longer is take the generate a hash. By
# default this is 10. Set this to any value >= the engine's minimum
# (currently 4), play around with it to get that perfect balance between
# security and performance.
def cost
@cost ||= 10
end
def cost=(val)
if val < ::BCrypt::Engine::MIN_COST
raise ArgumentError, "Authlogic's bcrypt cost cannot be set below the engine's " \
"min cost (#{::BCrypt::Engine::MIN_COST})"
end
@cost = val
end
# Creates a BCrypt hash for the password passed.
def encrypt(*tokens)
::BCrypt::Password.create(join_tokens(tokens), cost: cost)
end
# Does the hash match the tokens? Uses the same tokens that were used to
# encrypt.
def matches?(hash, *tokens)
hash = new_from_hash(hash)
return false if hash.blank?
hash == join_tokens(tokens)
end
# This method is used as a flag to tell Authlogic to "resave" the
# password upon a successful login, using the new cost
def cost_matches?(hash)
hash = new_from_hash(hash)
if hash.blank?
false
else
hash.cost == cost
end
end
private
def join_tokens(tokens)
tokens.flatten.join
end
def new_from_hash(hash)
::BCrypt::Password.new(hash)
rescue ::BCrypt::Errors::InvalidHash
nil
end
end
end
end
end