Commit 6f2637c0f63c49f473b4e5167c1ed53c79623dc2
Break up big one-file implementation into more standard gem structure
T.J. Schuck committed on 5/16/2013, 4:21:28 PMParent: f660002fe99df85013712a3a48de12d6b30d6cd1
Files changed
lib/bcrypt.rb | changed |
lib/bcrypt/engine.rb | added |
lib/bcrypt/error.rb | added |
lib/bcrypt/password.rb | added |
lib/bcrypt.rb | ||
---|---|---|
@@ -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 | |
2 | 5 | |
3 | 6 | if RUBY_PLATFORM == "java" |
4 | 7 | require 'java' |
5 | 8 | else |
6 | 9 | require "openssl" |
7 | 10 | end |
8 | 11 | |
9 | 12 | 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.rb | ||
---|---|---|
@@ -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.rb | ||
---|---|---|
@@ -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.rb | ||
---|---|---|
@@ -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