bcrypt-ruby 2.1.3-java

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of bcrypt-ruby might be problematic. Click here for more details.

@@ -0,0 +1,17 @@
1
+ if RUBY_PLATFORM == "java"
2
+ # Don't do anything when run in JRuby; this allows gem installation to pass.
3
+ # We need to write a dummy Makefile so that RubyGems doesn't think compilation
4
+ # failed.
5
+ File.open('Makefile', 'w') do |f|
6
+ f.puts "all:"
7
+ f.puts "\t@true"
8
+ f.puts "install:"
9
+ f.puts "\t@true"
10
+ end
11
+ exit 0
12
+ else
13
+ require "mkmf"
14
+ dir_config("bcrypt_ext")
15
+ CONFIG['CC'] << " -Wall "
16
+ create_makefile("bcrypt_ext")
17
+ end
data/lib/bcrypt.rb ADDED
@@ -0,0 +1,190 @@
1
+ # A wrapper for OpenBSD's bcrypt/crypt_blowfish password-hashing algorithm.
2
+
3
+ if RUBY_PLATFORM == "java"
4
+ require 'java'
5
+ else
6
+ require "openssl"
7
+ end
8
+
9
+ require 'bcrypt_ext'
10
+
11
+ # A Ruby library implementing OpenBSD's bcrypt()/crypt_blowfish algorithm for
12
+ # hashing passwords.
13
+ module BCrypt
14
+ module Errors
15
+ class InvalidSalt < StandardError; end # The salt parameter provided to bcrypt() is invalid.
16
+ class InvalidHash < StandardError; end # The hash parameter provided to bcrypt() is invalid.
17
+ class InvalidCost < StandardError; end # The cost parameter provided to bcrypt() is invalid.
18
+ class InvalidSecret < StandardError; end # The secret parameter provided to bcrypt() is invalid.
19
+ end
20
+
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
+
30
+ if RUBY_PLATFORM != "java"
31
+ # C-level routines which, if they don't get the right input, will crash the
32
+ # hell out of the Ruby process.
33
+ private_class_method :__bc_salt
34
+ private_class_method :__bc_crypt
35
+ end
36
+
37
+ # Given a secret and a valid salt (see BCrypt::Engine.generate_salt) calculates
38
+ # a bcrypt() password hash.
39
+ def self.hash_secret(secret, salt, cost = nil)
40
+ if valid_secret?(secret)
41
+ if valid_salt?(salt)
42
+ if cost.nil?
43
+ cost = autodetect_cost(salt)
44
+ end
45
+
46
+ if RUBY_PLATFORM == "java"
47
+ Java.bcrypt_jruby.BCrypt.hashpw(secret.to_s, salt.to_s)
48
+ else
49
+ __bc_crypt(secret.to_s, salt, cost)
50
+ end
51
+ else
52
+ raise Errors::InvalidSalt.new("invalid salt")
53
+ end
54
+ else
55
+ raise Errors::InvalidSecret.new("invalid secret")
56
+ end
57
+ end
58
+
59
+ # Generates a random salt with a given computational cost.
60
+ def self.generate_salt(cost = DEFAULT_COST)
61
+ cost = cost.to_i
62
+ if cost > 0
63
+ if cost < MIN_COST
64
+ cost = MIN_COST
65
+ end
66
+ if RUBY_PLATFORM == "java"
67
+ Java.bcrypt_jruby.BCrypt.gensalt(cost)
68
+ else
69
+ __bc_salt(cost, OpenSSL::Random.random_bytes(MAX_SALT_LENGTH))
70
+ end
71
+ else
72
+ raise Errors::InvalidCost.new("cost must be numeric and > 0")
73
+ end
74
+ end
75
+
76
+ # Returns true if +salt+ is a valid bcrypt() salt, false if not.
77
+ def self.valid_salt?(salt)
78
+ salt =~ /^\$[0-9a-z]{2,}\$[0-9]{2,}\$[A-Za-z0-9\.\/]{22,}$/
79
+ end
80
+
81
+ # Returns true if +secret+ is a valid bcrypt() secret, false if not.
82
+ def self.valid_secret?(secret)
83
+ secret.respond_to?(:to_s)
84
+ end
85
+
86
+ # Returns the cost factor which will result in computation times less than +upper_time_limit_in_ms+.
87
+ #
88
+ # Example:
89
+ #
90
+ # BCrypt.calibrate(200) #=> 10
91
+ # BCrypt.calibrate(1000) #=> 12
92
+ #
93
+ # # should take less than 200ms
94
+ # BCrypt::Password.create("woo", :cost => 10)
95
+ #
96
+ # # should take less than 1000ms
97
+ # BCrypt::Password.create("woo", :cost => 12)
98
+ def self.calibrate(upper_time_limit_in_ms)
99
+ 40.times do |i|
100
+ start_time = Time.now
101
+ Password.create("testing testing", :cost => i+1)
102
+ end_time = Time.now - start_time
103
+ return i if end_time * 1_000 > upper_time_limit_in_ms
104
+ end
105
+ end
106
+
107
+ # Autodetects the cost from the salt string.
108
+ def self.autodetect_cost(salt)
109
+ salt[4..5].to_i
110
+ end
111
+ end
112
+
113
+ # A password management class which allows you to safely store users' passwords and compare them.
114
+ #
115
+ # Example usage:
116
+ #
117
+ # include BCrypt
118
+ #
119
+ # # hash a user's password
120
+ # @password = Password.create("my grand secret")
121
+ # @password #=> "$2a$10$GtKs1Kbsig8ULHZzO1h2TetZfhO4Fmlxphp8bVKnUlZCBYYClPohG"
122
+ #
123
+ # # store it safely
124
+ # @user.update_attribute(:password, @password)
125
+ #
126
+ # # read it back
127
+ # @user.reload!
128
+ # @db_password = Password.new(@user.password)
129
+ #
130
+ # # compare it after retrieval
131
+ # @db_password == "my grand secret" #=> true
132
+ # @db_password == "a paltry guess" #=> false
133
+ #
134
+ class Password < String
135
+ # The hash portion of the stored password hash.
136
+ attr_reader :checksum
137
+ # The salt of the store password hash (including version and cost).
138
+ attr_reader :salt
139
+ # The version of the bcrypt() algorithm used to create the hash.
140
+ attr_reader :version
141
+ # The cost factor used to create the hash.
142
+ attr_reader :cost
143
+
144
+ class << self
145
+ # Hashes a secret, returning a BCrypt::Password instance. Takes an optional <tt>:cost</tt> option, which is a
146
+ # logarithmic variable which determines how computational expensive the hash is to calculate (a <tt>:cost</tt> of
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
+ # attackers to try to guess passwords (even if a copy of your database is stolen), but the slower it is to check
149
+ # users' passwords.
150
+ #
151
+ # Example:
152
+ #
153
+ # @password = BCrypt::Password.create("my secret", :cost => 13)
154
+ def create(secret, options = { :cost => BCrypt::Engine::DEFAULT_COST })
155
+ Password.new(BCrypt::Engine.hash_secret(secret, BCrypt::Engine.generate_salt(options[:cost]), options[:cost]))
156
+ end
157
+ end
158
+
159
+ # Initializes a BCrypt::Password instance with the data from a stored hash.
160
+ def initialize(raw_hash)
161
+ if valid_hash?(raw_hash)
162
+ self.replace(raw_hash)
163
+ @version, @cost, @salt, @checksum = split_hash(self)
164
+ else
165
+ raise Errors::InvalidHash.new("invalid hash")
166
+ end
167
+ end
168
+
169
+ # Compares a potential secret against the hash. Returns true if the secret is the original secret, false otherwise.
170
+ def ==(secret)
171
+ super(BCrypt::Engine.hash_secret(secret, @salt))
172
+ end
173
+ alias_method :is_password?, :==
174
+
175
+ private
176
+ # Returns true if +h+ is a valid hash.
177
+ def valid_hash?(h)
178
+ h =~ /^\$[0-9a-z]{2}\$[0-9]{2}\$[A-Za-z0-9\.\/]{53}$/
179
+ end
180
+
181
+ # call-seq:
182
+ # split_hash(raw_hash) -> version, cost, salt, hash
183
+ #
184
+ # Splits +h+ into version, cost, salt, and hash and returns them in that order.
185
+ def split_hash(h)
186
+ b, v, c, mash = h.split('$')
187
+ return v, c.to_i, h[0, 29].to_str, mash[-31, 31].to_str
188
+ end
189
+ end
190
+ end
Binary file
@@ -0,0 +1,175 @@
1
+ // Copyright (c) 2006 Damien Miller <djm@mindrot.org>
2
+ //
3
+ // Permission to use, copy, modify, and distribute this software for any
4
+ // purpose with or without fee is hereby granted, provided that the above
5
+ // copyright notice and this permission notice appear in all copies.
6
+ //
7
+ // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
+
15
+ import junit.framework.TestCase;
16
+
17
+ /**
18
+ * JUnit unit tests for BCrypt routines
19
+ * @author Damien Miller
20
+ * @version 0.2
21
+ */
22
+ public class TestBCrypt extends TestCase {
23
+ String test_vectors[][] = {
24
+ { "",
25
+ "$2a$06$DCq7YPn5Rq63x1Lad4cll.",
26
+ "$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s." },
27
+ { "",
28
+ "$2a$08$HqWuK6/Ng6sg9gQzbLrgb.",
29
+ "$2a$08$HqWuK6/Ng6sg9gQzbLrgb.Tl.ZHfXLhvt/SgVyWhQqgqcZ7ZuUtye" },
30
+ { "",
31
+ "$2a$10$k1wbIrmNyFAPwPVPSVa/ze",
32
+ "$2a$10$k1wbIrmNyFAPwPVPSVa/zecw2BCEnBwVS2GbrmgzxFUOqW9dk4TCW" },
33
+ { "",
34
+ "$2a$12$k42ZFHFWqBp3vWli.nIn8u",
35
+ "$2a$12$k42ZFHFWqBp3vWli.nIn8uYyIkbvYRvodzbfbK18SSsY.CsIQPlxO" },
36
+ { "a",
37
+ "$2a$06$m0CrhHm10qJ3lXRY.5zDGO",
38
+ "$2a$06$m0CrhHm10qJ3lXRY.5zDGO3rS2KdeeWLuGmsfGlMfOxih58VYVfxe" },
39
+ { "a",
40
+ "$2a$08$cfcvVd2aQ8CMvoMpP2EBfe",
41
+ "$2a$08$cfcvVd2aQ8CMvoMpP2EBfeodLEkkFJ9umNEfPD18.hUF62qqlC/V." },
42
+ { "a",
43
+ "$2a$10$k87L/MF28Q673VKh8/cPi.",
44
+ "$2a$10$k87L/MF28Q673VKh8/cPi.SUl7MU/rWuSiIDDFayrKk/1tBsSQu4u" },
45
+ { "a",
46
+ "$2a$12$8NJH3LsPrANStV6XtBakCe",
47
+ "$2a$12$8NJH3LsPrANStV6XtBakCez0cKHXVxmvxIlcz785vxAIZrihHZpeS" },
48
+ { "abc",
49
+ "$2a$06$If6bvum7DFjUnE9p2uDeDu",
50
+ "$2a$06$If6bvum7DFjUnE9p2uDeDu0YHzrHM6tf.iqN8.yx.jNN1ILEf7h0i" },
51
+ { "abc",
52
+ "$2a$08$Ro0CUfOqk6cXEKf3dyaM7O",
53
+ "$2a$08$Ro0CUfOqk6cXEKf3dyaM7OhSCvnwM9s4wIX9JeLapehKK5YdLxKcm" },
54
+ { "abc",
55
+ "$2a$10$WvvTPHKwdBJ3uk0Z37EMR.",
56
+ "$2a$10$WvvTPHKwdBJ3uk0Z37EMR.hLA2W6N9AEBhEgrAOljy2Ae5MtaSIUi" },
57
+ { "abc",
58
+ "$2a$12$EXRkfkdmXn2gzds2SSitu.",
59
+ "$2a$12$EXRkfkdmXn2gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q" },
60
+ { "abcdefghijklmnopqrstuvwxyz",
61
+ "$2a$06$.rCVZVOThsIa97pEDOxvGu",
62
+ "$2a$06$.rCVZVOThsIa97pEDOxvGuRRgzG64bvtJ0938xuqzv18d3ZpQhstC" },
63
+ { "abcdefghijklmnopqrstuvwxyz",
64
+ "$2a$08$aTsUwsyowQuzRrDqFflhge",
65
+ "$2a$08$aTsUwsyowQuzRrDqFflhgekJ8d9/7Z3GV3UcgvzQW3J5zMyrTvlz." },
66
+ { "abcdefghijklmnopqrstuvwxyz",
67
+ "$2a$10$fVH8e28OQRj9tqiDXs1e1u",
68
+ "$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq" },
69
+ { "abcdefghijklmnopqrstuvwxyz",
70
+ "$2a$12$D4G5f18o7aMMfwasBL7Gpu",
71
+ "$2a$12$D4G5f18o7aMMfwasBL7GpuQWuP3pkrZrOAnqP.bmezbMng.QwJ/pG" },
72
+ { "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
73
+ "$2a$06$fPIsBO8qRqkjj273rfaOI.",
74
+ "$2a$06$fPIsBO8qRqkjj273rfaOI.HtSV9jLDpTbZn782DC6/t7qT67P6FfO" },
75
+ { "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
76
+ "$2a$08$Eq2r4G/76Wv39MzSX262hu",
77
+ "$2a$08$Eq2r4G/76Wv39MzSX262huzPz612MZiYHVUJe/OcOql2jo4.9UxTW" },
78
+ { "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
79
+ "$2a$10$LgfYWkbzEvQ4JakH7rOvHe",
80
+ "$2a$10$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS" },
81
+ { "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
82
+ "$2a$12$WApznUOJfkEGSmYRfnkrPO",
83
+ "$2a$12$WApznUOJfkEGSmYRfnkrPOr466oFDCaj4b6HY3EXGvfxm43seyhgC" },
84
+ };
85
+
86
+ /**
87
+ * Entry point for unit tests
88
+ * @param args unused
89
+ */
90
+ public static void main(String[] args) {
91
+ junit.textui.TestRunner.run(TestBCrypt.class);
92
+ }
93
+
94
+ /**
95
+ * Test method for 'BCrypt.hashpw(String, String)'
96
+ */
97
+ public void testHashpw() {
98
+ System.out.print("BCrypt.hashpw(): ");
99
+ for (int i = 0; i < test_vectors.length; i++) {
100
+ String plain = test_vectors[i][0];
101
+ String salt = test_vectors[i][1];
102
+ String expected = test_vectors[i][2];
103
+ String hashed = BCrypt.hashpw(plain, salt);
104
+ assertEquals(hashed, expected);
105
+ System.out.print(".");
106
+ }
107
+ System.out.println("");
108
+ }
109
+
110
+ /**
111
+ * Test method for 'BCrypt.gensalt(int)'
112
+ */
113
+ public void testGensaltInt() {
114
+ System.out.print("BCrypt.gensalt(log_rounds):");
115
+ for (int i = 4; i <= 12; i++) {
116
+ System.out.print(" " + Integer.toString(i) + ":");
117
+ for (int j = 0; j < test_vectors.length; j += 4) {
118
+ String plain = test_vectors[j][0];
119
+ String salt = BCrypt.gensalt(i);
120
+ String hashed1 = BCrypt.hashpw(plain, salt);
121
+ String hashed2 = BCrypt.hashpw(plain, hashed1);
122
+ assertEquals(hashed1, hashed2);
123
+ System.out.print(".");
124
+ }
125
+ }
126
+ System.out.println("");
127
+ }
128
+
129
+ /**
130
+ * Test method for 'BCrypt.gensalt()'
131
+ */
132
+ public void testGensalt() {
133
+ System.out.print("BCrypt.gensalt(): ");
134
+ for (int i = 0; i < test_vectors.length; i += 4) {
135
+ String plain = test_vectors[i][0];
136
+ String salt = BCrypt.gensalt();
137
+ String hashed1 = BCrypt.hashpw(plain, salt);
138
+ String hashed2 = BCrypt.hashpw(plain, hashed1);
139
+ assertEquals(hashed1, hashed2);
140
+ System.out.print(".");
141
+ }
142
+ System.out.println("");
143
+ }
144
+
145
+ /**
146
+ * Test method for 'BCrypt.checkpw(String, String)'
147
+ * expecting success
148
+ */
149
+ public void testCheckpw_success() {
150
+ System.out.print("BCrypt.checkpw w/ good passwords: ");
151
+ for (int i = 0; i < test_vectors.length; i++) {
152
+ String plain = test_vectors[i][0];
153
+ String expected = test_vectors[i][2];
154
+ assertTrue(BCrypt.checkpw(plain, expected));
155
+ System.out.print(".");
156
+ }
157
+ System.out.println("");
158
+ }
159
+
160
+ /**
161
+ * Test method for 'BCrypt.checkpw(String, String)'
162
+ * expecting failure
163
+ */
164
+ public void testCheckpw_failure() {
165
+ System.out.print("BCrypt.checkpw w/ bad passwords: ");
166
+ for (int i = 0; i < test_vectors.length; i++) {
167
+ int broken_index = (i + 4) % test_vectors.length;
168
+ String plain = test_vectors[i][0];
169
+ String expected = test_vectors[broken_index][2];
170
+ assertFalse(BCrypt.checkpw(plain, expected));
171
+ System.out.print(".");
172
+ }
173
+ System.out.println("");
174
+ }
175
+ }
@@ -0,0 +1,82 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
2
+
3
+ describe "The BCrypt engine" do
4
+ specify "should calculate the optimal cost factor to fit in a specific time" do
5
+ first = BCrypt::Engine.calibrate(100)
6
+ second = BCrypt::Engine.calibrate(400)
7
+ second.should > first
8
+ end
9
+ end
10
+
11
+ describe "Generating BCrypt salts" do
12
+
13
+ specify "should produce strings" do
14
+ BCrypt::Engine.generate_salt.should be_an_instance_of(String)
15
+ end
16
+
17
+ specify "should produce random data" do
18
+ BCrypt::Engine.generate_salt.should_not equal(BCrypt::Engine.generate_salt)
19
+ end
20
+
21
+ specify "should raise a InvalidCostError if the cost parameter isn't numeric" do
22
+ lambda { BCrypt::Engine.generate_salt('woo') }.should raise_error(BCrypt::Errors::InvalidCost)
23
+ end
24
+
25
+ specify "should raise a InvalidCostError if the cost parameter isn't greater than 0" do
26
+ lambda { BCrypt::Engine.generate_salt(-1) }.should raise_error(BCrypt::Errors::InvalidCost)
27
+ end
28
+ end
29
+
30
+ describe "Autodetecting of salt cost" do
31
+
32
+ specify "should work" do
33
+ BCrypt::Engine.autodetect_cost("$2a$08$hRx2IVeHNsTSYYtUWn61Ou").should == 8
34
+ BCrypt::Engine.autodetect_cost("$2a$05$XKd1bMnLgUnc87qvbAaCUu").should == 5
35
+ BCrypt::Engine.autodetect_cost("$2a$13$Lni.CZ6z5A7344POTFBBV.").should == 13
36
+ end
37
+
38
+ end
39
+
40
+ describe "Generating BCrypt hashes" do
41
+
42
+ class MyInvalidSecret
43
+ undef to_s
44
+ end
45
+
46
+ before :each do
47
+ @salt = BCrypt::Engine.generate_salt(4)
48
+ @password = "woo"
49
+ end
50
+
51
+ specify "should produce a string" do
52
+ BCrypt::Engine.hash_secret(@password, @salt).should be_an_instance_of(String)
53
+ end
54
+
55
+ specify "should raise an InvalidSalt error if the salt is invalid" do
56
+ lambda { BCrypt::Engine.hash_secret(@password, 'nino') }.should raise_error(BCrypt::Errors::InvalidSalt)
57
+ end
58
+
59
+ specify "should raise an InvalidSecret error if the secret is invalid" do
60
+ lambda { BCrypt::Engine.hash_secret(MyInvalidSecret.new, @salt) }.should raise_error(BCrypt::Errors::InvalidSecret)
61
+ lambda { BCrypt::Engine.hash_secret(nil, @salt) }.should_not raise_error(BCrypt::Errors::InvalidSecret)
62
+ lambda { BCrypt::Engine.hash_secret(false, @salt) }.should_not raise_error(BCrypt::Errors::InvalidSecret)
63
+ end
64
+
65
+ specify "should call #to_s on the secret and use the return value as the actual secret data" do
66
+ BCrypt::Engine.hash_secret(false, @salt).should == BCrypt::Engine.hash_secret("false", @salt)
67
+ end
68
+
69
+ specify "should be interoperable with other implementations" do
70
+ # test vectors from the OpenWall implementation <http://www.openwall.com/crypt/>
71
+ test_vectors = [
72
+ ["U*U", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW"],
73
+ ["U*U*", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK"],
74
+ ["U*U*U", "$2a$05$XXXXXXXXXXXXXXXXXXXXXO", "$2a$05$XXXXXXXXXXXXXXXXXXXXXOAcXxm9kjPGEMsLznoKqmqw7tc8WCx4a"],
75
+ ["", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.7uG0VCzI2bS7j6ymqJi9CdcdxiRTWNy"],
76
+ ["0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", "$2a$05$abcdefghijklmnopqrstuu", "$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui"]
77
+ ]
78
+ for secret, salt, test_vector in test_vectors
79
+ BCrypt::Engine.hash_secret(secret, salt).should eql(test_vector)
80
+ end
81
+ end
82
+ end