git ssb

0+

dangerousbeans / %aPBe2k3ugtjBr4rrsU1…



Tree: b4024d4b1505f3c87b4c0b1ed59b8850aeea719f

Files: b4024d4b1505f3c87b4c0b1ed59b8850aeea719f / lib / bcrypt.rb

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

Built with git-ssb-web