module BCrypt # A password management class which allows you to safely store users' passwords and compare them. # # Example usage: # # include BCrypt # # # hash a user's password # @password = Password.create("my grand secret") # @password #=> "$2a$12$C5.FIvVDS9W4AYZ/Ib37YuWd/7ozp1UaMhU28UKrfSxp2oDchbi3K" # # # store it safely # @user.update_attribute(:password, @password) # # # read it back # @user.reload! # @db_password = Password.new(@user.password) # # # compare it after retrieval # @db_password == "my grand secret" #=> true # @db_password == "a paltry guess" #=> false # class Password < String # The hash portion of the stored password hash. attr_reader :checksum # The salt of the store password hash (including version and cost). attr_reader :salt # The version of the bcrypt() algorithm used to create the hash. attr_reader :version # The cost factor used to create the hash. attr_reader :cost class << self # Hashes a secret, returning a BCrypt::Password instance. Takes an optional :cost option, which is a # logarithmic variable which determines how computational expensive the hash is to calculate (a :cost of # 4 is twice as much work as a :cost of 3). The higher the :cost the harder it becomes for # attackers to try to guess passwords (even if a copy of your database is stolen), but the slower it is to check # users' passwords. # # Example: # # @password = BCrypt::Password.create("my secret", :cost => 13) def create(secret, options = {}) cost = options[:cost] || BCrypt::Engine.cost raise ArgumentError if cost > BCrypt::Engine::MAX_COST Password.new(BCrypt::Engine.hash_secret(secret, BCrypt::Engine.generate_salt(cost))) end def valid_hash?(h) /\A\$[0-9a-z]{2}\$[0-9]{2}\$[A-Za-z0-9\.\/]{53}\z/ === h end end # Initializes a BCrypt::Password instance with the data from a stored hash. def initialize(raw_hash) if valid_hash?(raw_hash) self.replace(raw_hash) @version, @cost, @salt, @checksum = split_hash(self) else raise Errors::InvalidHash.new("invalid hash") end end # Compares a potential secret against the hash. Returns true if the secret is the original secret, false otherwise. # # Comparison edge case/gotcha: # # secret = "my secret" # @password = BCrypt::Password.create(secret) # # @password == secret # => True # @password == @password # => False # @password == @password.to_s # => False # @password.to_s == @password # => True # @password.to_s == @password.to_s # => True def ==(secret) super(BCrypt::Engine.hash_secret(secret, @salt)) end alias_method :is_password?, :== private # Returns true if +h+ is a valid hash. def valid_hash?(h) self.class.valid_hash?(h) end # call-seq: # split_hash(raw_hash) -> version, cost, salt, hash # # Splits +h+ into version, cost, salt, and hash and returns them in that order. def split_hash(h) _, v, c, mash = h.split('$') return v.to_str, c.to_i, h[0, 29].to_str, mash[-31, 31].to_str end end end