git ssb

0+

dangerousbeans / %aPBe2k3ugtjBr4rrsU1…



Commit 6f2637c0f63c49f473b4e5167c1ed53c79623dc2

Break up big one-file implementation into more standard gem structure

T.J. Schuck committed on 5/16/2013, 4:21:28 PM
Parent: f660002fe99df85013712a3a48de12d6b30d6cd1

Files changed

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

Built with git-ssb-web