Commit 32a206ce672a8af55442c22e030e147da01ddde3
Remove trailing whitespace
Aman Gupta committed on 12/20/2010, 6:55:23 PMParent: 85cd242425bb89f848cce3fdc24d7f8b1cc6764f
Files changed
lib/bcrypt.rb | changed |
spec/bcrypt/engine_spec.rb | changed |
spec/bcrypt/password_spec.rb | changed |
lib/bcrypt.rb | ||
---|---|---|
@@ -16,34 +16,34 @@ | ||
16 | 16 | class InvalidHash < StandardError; end # The hash parameter provided to bcrypt() is invalid. |
17 | 17 | class InvalidCost < StandardError; end # The cost parameter provided to bcrypt() is invalid. |
18 | 18 | class InvalidSecret < StandardError; end # The secret parameter provided to bcrypt() is invalid. |
19 | 19 | end |
20 | - | |
20 | + | |
21 | 21 | # A Ruby wrapper for the bcrypt() C extension calls and the Java calls. |
22 | 22 | class Engine |
23 | 23 | # The default computational expense parameter. |
24 | 24 | DEFAULT_COST = 10 |
25 | 25 | # The minimum cost supported by the algorithm. |
26 | 26 | MIN_COST = 4 |
27 | 27 | # Maximum possible size of bcrypt() salts. |
28 | 28 | MAX_SALT_LENGTH = 16 |
29 | - | |
29 | + | |
30 | 30 | if RUBY_PLATFORM != "java" |
31 | 31 | # C-level routines which, if they don't get the right input, will crash the |
32 | 32 | # hell out of the Ruby process. |
33 | 33 | private_class_method :__bc_salt |
34 | 34 | private_class_method :__bc_crypt |
35 | 35 | end |
36 | - | |
36 | + | |
37 | 37 | # Given a secret and a valid salt (see BCrypt::Engine.generate_salt) calculates |
38 | 38 | # a bcrypt() password hash. |
39 | 39 | def self.hash_secret(secret, salt, cost = nil) |
40 | 40 | if valid_secret?(secret) |
41 | 41 | if valid_salt?(salt) |
42 | 42 | if cost.nil? |
43 | 43 | cost = autodetect_cost(salt) |
44 | 44 | end |
45 | - | |
45 | + | |
46 | 46 | if RUBY_PLATFORM == "java" |
47 | 47 | Java.bcrypt_jruby.BCrypt.hashpw(secret.to_s, salt.to_s) |
48 | 48 | else |
49 | 49 | __bc_crypt(secret.to_s, salt, cost) |
@@ -54,9 +54,9 @@ | ||
54 | 54 | else |
55 | 55 | raise Errors::InvalidSecret.new("invalid secret") |
56 | 56 | end |
57 | 57 | end |
58 | - | |
58 | + | |
59 | 59 | # Generates a random salt with a given computational cost. |
60 | 60 | def self.generate_salt(cost = DEFAULT_COST) |
61 | 61 | cost = cost.to_i |
62 | 62 | if cost > 0 |
@@ -71,29 +71,29 @@ | ||
71 | 71 | else |
72 | 72 | raise Errors::InvalidCost.new("cost must be numeric and > 0") |
73 | 73 | end |
74 | 74 | end |
75 | - | |
75 | + | |
76 | 76 | # Returns true if +salt+ is a valid bcrypt() salt, false if not. |
77 | 77 | def self.valid_salt?(salt) |
78 | 78 | salt =~ /^\$[0-9a-z]{2,}\$[0-9]{2,}\$[A-Za-z0-9\.\/]{22,}$/ |
79 | 79 | end |
80 | - | |
80 | + | |
81 | 81 | # Returns true if +secret+ is a valid bcrypt() secret, false if not. |
82 | 82 | def self.valid_secret?(secret) |
83 | 83 | secret.respond_to?(:to_s) |
84 | 84 | end |
85 | - | |
85 | + | |
86 | 86 | # Returns the cost factor which will result in computation times less than +upper_time_limit_in_ms+. |
87 | - # | |
87 | + # | |
88 | 88 | # Example: |
89 | - # | |
89 | + # | |
90 | 90 | # BCrypt.calibrate(200) #=> 10 |
91 | 91 | # BCrypt.calibrate(1000) #=> 12 |
92 | - # | |
92 | + # | |
93 | 93 | # # should take less than 200ms |
94 | 94 | # BCrypt::Password.create("woo", :cost => 10) |
95 | - # | |
95 | + # | |
96 | 96 | # # should take less than 1000ms |
97 | 97 | # BCrypt::Password.create("woo", :cost => 12) |
98 | 98 | def self.calibrate(upper_time_limit_in_ms) |
99 | 99 | 40.times do |i| |
@@ -102,36 +102,36 @@ | ||
102 | 102 | end_time = Time.now - start_time |
103 | 103 | return i if end_time * 1_000 > upper_time_limit_in_ms |
104 | 104 | end |
105 | 105 | end |
106 | - | |
106 | + | |
107 | 107 | # Autodetects the cost from the salt string. |
108 | 108 | def self.autodetect_cost(salt) |
109 | 109 | salt[4..5].to_i |
110 | 110 | end |
111 | 111 | end |
112 | - | |
112 | + | |
113 | 113 | # A password management class which allows you to safely store users' passwords and compare them. |
114 | - # | |
114 | + # | |
115 | 115 | # Example usage: |
116 | - # | |
116 | + # | |
117 | 117 | # include BCrypt |
118 | - # | |
118 | + # | |
119 | 119 | # # hash a user's password |
120 | 120 | # @password = Password.create("my grand secret") |
121 | 121 | # @password #=> "$2a$10$GtKs1Kbsig8ULHZzO1h2TetZfhO4Fmlxphp8bVKnUlZCBYYClPohG" |
122 | - # | |
122 | + # | |
123 | 123 | # # store it safely |
124 | 124 | # @user.update_attribute(:password, @password) |
125 | - # | |
125 | + # | |
126 | 126 | # # read it back |
127 | 127 | # @user.reload! |
128 | 128 | # @db_password = Password.new(@user.password) |
129 | - # | |
129 | + # | |
130 | 130 | # # compare it after retrieval |
131 | 131 | # @db_password == "my grand secret" #=> true |
132 | 132 | # @db_password == "a paltry guess" #=> false |
133 | - # | |
133 | + # | |
134 | 134 | class Password < String |
135 | 135 | # The hash portion of the stored password hash. |
136 | 136 | attr_reader :hash |
137 | 137 | # The salt of the store password hash (including version and cost). |
@@ -139,24 +139,24 @@ | ||
139 | 139 | # The version of the bcrypt() algorithm used to create the hash. |
140 | 140 | attr_reader :version |
141 | 141 | # The cost factor used to create the hash. |
142 | 142 | attr_reader :cost |
143 | - | |
143 | + | |
144 | 144 | class << self |
145 | 145 | # Hashes a secret, returning a BCrypt::Password instance. Takes an optional <tt>:cost</tt> option, which is a |
146 | 146 | # logarithmic variable which determines how computational expensive the hash is to calculate (a <tt>:cost</tt> of |
147 | 147 | # 4 is twice as much work as a <tt>:cost</tt> of 3). The higher the <tt>:cost</tt> the harder it becomes for |
148 | 148 | # attackers to try to guess passwords (even if a copy of your database is stolen), but the slower it is to check |
149 | 149 | # users' passwords. |
150 | - # | |
150 | + # | |
151 | 151 | # Example: |
152 | - # | |
152 | + # | |
153 | 153 | # @password = BCrypt::Password.create("my secret", :cost => 13) |
154 | 154 | def create(secret, options = { :cost => BCrypt::Engine::DEFAULT_COST }) |
155 | 155 | Password.new(BCrypt::Engine.hash_secret(secret, BCrypt::Engine.generate_salt(options[:cost]), options[:cost])) |
156 | 156 | end |
157 | 157 | end |
158 | - | |
158 | + | |
159 | 159 | # Initializes a BCrypt::Password instance with the data from a stored hash. |
160 | 160 | def initialize(raw_hash) |
161 | 161 | if valid_hash?(raw_hash) |
162 | 162 | self.replace(raw_hash) |
@@ -164,24 +164,24 @@ | ||
164 | 164 | else |
165 | 165 | raise Errors::InvalidHash.new("invalid hash") |
166 | 166 | end |
167 | 167 | end |
168 | - | |
168 | + | |
169 | 169 | # Compares a potential secret against the hash. Returns true if the secret is the original secret, false otherwise. |
170 | 170 | def ==(secret) |
171 | 171 | super(BCrypt::Engine.hash_secret(secret, @salt)) |
172 | 172 | end |
173 | 173 | alias_method :is_password?, :== |
174 | - | |
174 | + | |
175 | 175 | private |
176 | 176 | # Returns true if +h+ is a valid hash. |
177 | 177 | def valid_hash?(h) |
178 | 178 | h =~ /^\$[0-9a-z]{2}\$[0-9]{2}\$[A-Za-z0-9\.\/]{53}$/ |
179 | 179 | end |
180 | - | |
180 | + | |
181 | 181 | # call-seq: |
182 | 182 | # split_hash(raw_hash) -> version, cost, salt, hash |
183 | - # | |
183 | + # | |
184 | 184 | # Splits +h+ into version, cost, salt, and hash and returns them in that order. |
185 | 185 | def split_hash(h) |
186 | 186 | b, v, c, mash = h.split('$') |
187 | 187 | return v, c.to_i, h[0, 29], mash[-31, 31] |
spec/bcrypt/engine_spec.rb | ||
---|---|---|
@@ -8,65 +8,65 @@ | ||
8 | 8 | end |
9 | 9 | end |
10 | 10 | |
11 | 11 | describe "Generating BCrypt salts" do |
12 | - | |
12 | + | |
13 | 13 | specify "should produce strings" do |
14 | 14 | BCrypt::Engine.generate_salt.should be_an_instance_of(String) |
15 | 15 | end |
16 | - | |
16 | + | |
17 | 17 | specify "should produce random data" do |
18 | 18 | BCrypt::Engine.generate_salt.should_not equal(BCrypt::Engine.generate_salt) |
19 | 19 | end |
20 | - | |
20 | + | |
21 | 21 | specify "should raise a InvalidCostError if the cost parameter isn't numeric" do |
22 | 22 | lambda { BCrypt::Engine.generate_salt('woo') }.should raise_error(BCrypt::Errors::InvalidCost) |
23 | 23 | end |
24 | - | |
24 | + | |
25 | 25 | specify "should raise a InvalidCostError if the cost parameter isn't greater than 0" do |
26 | 26 | lambda { BCrypt::Engine.generate_salt(-1) }.should raise_error(BCrypt::Errors::InvalidCost) |
27 | 27 | end |
28 | 28 | end |
29 | 29 | |
30 | 30 | describe "Autodetecting of salt cost" do |
31 | - | |
31 | + | |
32 | 32 | specify "should work" do |
33 | 33 | BCrypt::Engine.autodetect_cost("$2a$08$hRx2IVeHNsTSYYtUWn61Ou").should == 8 |
34 | 34 | BCrypt::Engine.autodetect_cost("$2a$05$XKd1bMnLgUnc87qvbAaCUu").should == 5 |
35 | 35 | BCrypt::Engine.autodetect_cost("$2a$13$Lni.CZ6z5A7344POTFBBV.").should == 13 |
36 | 36 | end |
37 | - | |
37 | + | |
38 | 38 | end |
39 | 39 | |
40 | 40 | describe "Generating BCrypt hashes" do |
41 | - | |
41 | + | |
42 | 42 | class MyInvalidSecret |
43 | 43 | undef to_s |
44 | 44 | end |
45 | - | |
45 | + | |
46 | 46 | before :each do |
47 | 47 | @salt = BCrypt::Engine.generate_salt(4) |
48 | 48 | @password = "woo" |
49 | 49 | end |
50 | - | |
50 | + | |
51 | 51 | specify "should produce a string" do |
52 | 52 | BCrypt::Engine.hash_secret(@password, @salt).should be_an_instance_of(String) |
53 | 53 | end |
54 | - | |
54 | + | |
55 | 55 | specify "should raise an InvalidSalt error if the salt is invalid" do |
56 | 56 | lambda { BCrypt::Engine.hash_secret(@password, 'nino') }.should raise_error(BCrypt::Errors::InvalidSalt) |
57 | 57 | end |
58 | - | |
58 | + | |
59 | 59 | specify "should raise an InvalidSecret error if the secret is invalid" do |
60 | 60 | lambda { BCrypt::Engine.hash_secret(MyInvalidSecret.new, @salt) }.should raise_error(BCrypt::Errors::InvalidSecret) |
61 | 61 | lambda { BCrypt::Engine.hash_secret(nil, @salt) }.should_not raise_error(BCrypt::Errors::InvalidSecret) |
62 | 62 | lambda { BCrypt::Engine.hash_secret(false, @salt) }.should_not raise_error(BCrypt::Errors::InvalidSecret) |
63 | 63 | end |
64 | - | |
64 | + | |
65 | 65 | specify "should call #to_s on the secret and use the return value as the actual secret data" do |
66 | 66 | BCrypt::Engine.hash_secret(false, @salt).should == BCrypt::Engine.hash_secret("false", @salt) |
67 | 67 | end |
68 | - | |
68 | + | |
69 | 69 | specify "should be interoperable with other implementations" do |
70 | 70 | # test vectors from the OpenWall implementation <http://www.openwall.com/crypt/> |
71 | 71 | test_vectors = [ |
72 | 72 | ["U*U", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW"], |
spec/bcrypt/password_spec.rb | ||
---|---|---|
@@ -1,27 +1,27 @@ | ||
1 | 1 | require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper")) |
2 | 2 | |
3 | 3 | describe "Creating a hashed password" do |
4 | - | |
4 | + | |
5 | 5 | before :each do |
6 | 6 | @secret = "wheedle" |
7 | 7 | @password = BCrypt::Password.create(@secret, :cost => 4) |
8 | 8 | end |
9 | - | |
9 | + | |
10 | 10 | specify "should return a BCrypt::Password" do |
11 | 11 | @password.should be_an_instance_of(BCrypt::Password) |
12 | 12 | end |
13 | - | |
13 | + | |
14 | 14 | specify "should return a valid bcrypt password" do |
15 | 15 | lambda { BCrypt::Password.new(@password) }.should_not raise_error |
16 | 16 | end |
17 | - | |
17 | + | |
18 | 18 | specify "should behave normally if the secret is not a string" do |
19 | 19 | lambda { BCrypt::Password.create(nil) }.should_not raise_error(BCrypt::Errors::InvalidSecret) |
20 | 20 | lambda { BCrypt::Password.create({:woo => "yeah"}) }.should_not raise_error(BCrypt::Errors::InvalidSecret) |
21 | 21 | lambda { BCrypt::Password.create(false) }.should_not raise_error(BCrypt::Errors::InvalidSecret) |
22 | 22 | end |
23 | - | |
23 | + | |
24 | 24 | specify "should tolerate empty string secrets" do |
25 | 25 | lambda { BCrypt::Password.create( "\n".chop ) }.should_not raise_error |
26 | 26 | lambda { BCrypt::Password.create( "" ) }.should_not raise_error |
27 | 27 | lambda { BCrypt::Password.create( String.new ) }.should_not raise_error |
@@ -32,17 +32,17 @@ | ||
32 | 32 | before :each do |
33 | 33 | @secret = "U*U" |
34 | 34 | @hash = "$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW" |
35 | 35 | end |
36 | - | |
36 | + | |
37 | 37 | specify "should read the version, cost, salt, and hash" do |
38 | 38 | password = BCrypt::Password.new(@hash) |
39 | 39 | password.version.should eql("2a") |
40 | 40 | password.cost.should equal(5) |
41 | 41 | password.salt.should eql("$2a$05$CCCCCCCCCCCCCCCCCCCCC.") |
42 | 42 | password.to_s.should eql(@hash) |
43 | 43 | end |
44 | - | |
44 | + | |
45 | 45 | specify "should raise an InvalidHashError when given an invalid hash" do |
46 | 46 | lambda { BCrypt::Password.new('weedle') }.should raise_error(BCrypt::Errors::InvalidHash) |
47 | 47 | end |
48 | 48 | end |
@@ -52,13 +52,13 @@ | ||
52 | 52 | @secret = "U*U" |
53 | 53 | @hash = "$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW" |
54 | 54 | @password = BCrypt::Password.create(@secret) |
55 | 55 | end |
56 | - | |
56 | + | |
57 | 57 | specify "should compare successfully to the original secret" do |
58 | 58 | (@password == @secret).should be(true) |
59 | 59 | end |
60 | - | |
60 | + | |
61 | 61 | specify "should compare unsuccessfully to anything besides original secret" do |
62 | 62 | (@password == "@secret").should be(false) |
63 | 63 | end |
64 | 64 | end |
Built with git-ssb-web