bcrypt.rb 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. # A wrapper for OpenBSD's bcrypt/crypt_blowfish password-hashing algorithm.
  2. if RUBY_PLATFORM == "java"
  3. require 'java'
  4. else
  5. require "openssl"
  6. end
  7. if defined?(RUBY_ENGINE) and RUBY_ENGINE == "maglev"
  8. require 'bcrypt_engine'
  9. else
  10. require 'bcrypt_ext'
  11. end
  12. # A Ruby library implementing OpenBSD's bcrypt()/crypt_blowfish algorithm for
  13. # hashing passwords.
  14. module BCrypt
  15. module Errors
  16. class InvalidSalt < StandardError; end # The salt parameter provided to bcrypt() is invalid.
  17. class InvalidHash < StandardError; end # The hash parameter provided to bcrypt() is invalid.
  18. class InvalidCost < StandardError; end # The cost parameter provided to bcrypt() is invalid.
  19. class InvalidSecret < StandardError; end # The secret parameter provided to bcrypt() is invalid.
  20. end
  21. # A Ruby wrapper for the bcrypt() C extension calls and the Java calls.
  22. class Engine
  23. # The default computational expense parameter.
  24. DEFAULT_COST = 10
  25. # The minimum cost supported by the algorithm.
  26. MIN_COST = 4
  27. # Maximum possible size of bcrypt() salts.
  28. MAX_SALT_LENGTH = 16
  29. if RUBY_PLATFORM != "java"
  30. # C-level routines which, if they don't get the right input, will crash the
  31. # hell out of the Ruby process.
  32. private_class_method :__bc_salt
  33. private_class_method :__bc_crypt
  34. end
  35. # Given a secret and a valid salt (see BCrypt::Engine.generate_salt) calculates
  36. # a bcrypt() password hash.
  37. def self.hash_secret(secret, salt, cost = nil)
  38. if valid_secret?(secret)
  39. if valid_salt?(salt)
  40. if cost.nil?
  41. cost = autodetect_cost(salt)
  42. end
  43. if RUBY_PLATFORM == "java"
  44. Java.bcrypt_jruby.BCrypt.hashpw(secret.to_s, salt.to_s)
  45. else
  46. __bc_crypt(secret.to_s, salt)
  47. end
  48. else
  49. raise Errors::InvalidSalt.new("invalid salt")
  50. end
  51. else
  52. raise Errors::InvalidSecret.new("invalid secret")
  53. end
  54. end
  55. # Generates a random salt with a given computational cost.
  56. def self.generate_salt(cost = DEFAULT_COST)
  57. cost = cost.to_i
  58. if cost > 0
  59. if cost < MIN_COST
  60. cost = MIN_COST
  61. end
  62. if RUBY_PLATFORM == "java"
  63. Java.bcrypt_jruby.BCrypt.gensalt(cost)
  64. else
  65. prefix = "$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW"
  66. __bc_salt(prefix, cost, OpenSSL::Random.random_bytes(MAX_SALT_LENGTH))
  67. end
  68. else
  69. raise Errors::InvalidCost.new("cost must be numeric and > 0")
  70. end
  71. end
  72. # Returns true if +salt+ is a valid bcrypt() salt, false if not.
  73. def self.valid_salt?(salt)
  74. !!(salt =~ /^\$[0-9a-z]{2,}\$[0-9]{2,}\$[A-Za-z0-9\.\/]{22,}$/)
  75. end
  76. # Returns true if +secret+ is a valid bcrypt() secret, false if not.
  77. def self.valid_secret?(secret)
  78. secret.respond_to?(:to_s)
  79. end
  80. # Returns the cost factor which will result in computation times less than +upper_time_limit_in_ms+.
  81. #
  82. # Example:
  83. #
  84. # BCrypt.calibrate(200) #=> 10
  85. # BCrypt.calibrate(1000) #=> 12
  86. #
  87. # # should take less than 200ms
  88. # BCrypt::Password.create("woo", :cost => 10)
  89. #
  90. # # should take less than 1000ms
  91. # BCrypt::Password.create("woo", :cost => 12)
  92. def self.calibrate(upper_time_limit_in_ms)
  93. 40.times do |i|
  94. start_time = Time.now
  95. Password.create("testing testing", :cost => i+1)
  96. end_time = Time.now - start_time
  97. return i if end_time * 1_000 > upper_time_limit_in_ms
  98. end
  99. end
  100. # Autodetects the cost from the salt string.
  101. def self.autodetect_cost(salt)
  102. salt[4..5].to_i
  103. end
  104. end
  105. # A password management class which allows you to safely store users' passwords and compare them.
  106. #
  107. # Example usage:
  108. #
  109. # include BCrypt
  110. #
  111. # # hash a user's password
  112. # @password = Password.create("my grand secret")
  113. # @password #=> "$2a$10$GtKs1Kbsig8ULHZzO1h2TetZfhO4Fmlxphp8bVKnUlZCBYYClPohG"
  114. #
  115. # # store it safely
  116. # @user.update_attribute(:password, @password)
  117. #
  118. # # read it back
  119. # @user.reload!
  120. # @db_password = Password.new(@user.password)
  121. #
  122. # # compare it after retrieval
  123. # @db_password == "my grand secret" #=> true
  124. # @db_password == "a paltry guess" #=> false
  125. #
  126. class Password < String
  127. # The hash portion of the stored password hash.
  128. attr_reader :checksum
  129. # The salt of the store password hash (including version and cost).
  130. attr_reader :salt
  131. # The version of the bcrypt() algorithm used to create the hash.
  132. attr_reader :version
  133. # The cost factor used to create the hash.
  134. attr_reader :cost
  135. class << self
  136. # Hashes a secret, returning a BCrypt::Password instance. Takes an optional <tt>:cost</tt> option, which is a
  137. # logarithmic variable which determines how computational expensive the hash is to calculate (a <tt>:cost</tt> of
  138. # 4 is twice as much work as a <tt>:cost</tt> of 3). The higher the <tt>:cost</tt> the harder it becomes for
  139. # attackers to try to guess passwords (even if a copy of your database is stolen), but the slower it is to check
  140. # users' passwords.
  141. #
  142. # Example:
  143. #
  144. # @password = BCrypt::Password.create("my secret", :cost => 13)
  145. def create(secret, options = { :cost => BCrypt::Engine::DEFAULT_COST })
  146. raise ArgumentError if options[:cost] > 31
  147. Password.new(BCrypt::Engine.hash_secret(secret, BCrypt::Engine.generate_salt(options[:cost]), options[:cost]))
  148. end
  149. end
  150. # Initializes a BCrypt::Password instance with the data from a stored hash.
  151. def initialize(raw_hash)
  152. if valid_hash?(raw_hash)
  153. self.replace(raw_hash)
  154. @version, @cost, @salt, @checksum = split_hash(self)
  155. else
  156. raise Errors::InvalidHash.new("invalid hash")
  157. end
  158. end
  159. # Compares a potential secret against the hash. Returns true if the secret is the original secret, false otherwise.
  160. def ==(secret)
  161. super(BCrypt::Engine.hash_secret(secret, @salt))
  162. end
  163. alias_method :is_password?, :==
  164. private
  165. # Returns true if +h+ is a valid hash.
  166. def valid_hash?(h)
  167. h =~ /^\$[0-9a-z]{2}\$[0-9]{2}\$[A-Za-z0-9\.\/]{53}$/
  168. end
  169. # call-seq:
  170. # split_hash(raw_hash) -> version, cost, salt, hash
  171. #
  172. # Splits +h+ into version, cost, salt, and hash and returns them in that order.
  173. def split_hash(h)
  174. _, v, c, mash = h.split('$')
  175. return v, c.to_i, h[0, 29].to_str, mash[-31, 31].to_str
  176. end
  177. end
  178. end