bcrypt-ruby 3.1.2.rc1-x64-mingw32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,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
@@ -0,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
@@ -0,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
@@ -0,0 +1,194 @@
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
+
176
+ /**
177
+ * Test for correct hashing of non-US-ASCII passwords
178
+ */
179
+ public void testInternationalChars() {
180
+ System.out.print("BCrypt.hashpw w/ international chars: ");
181
+ String pw1 = "ππππππππ";
182
+ String pw2 = "????????";
183
+
184
+ String h1 = BCrypt.hashpw(pw1, BCrypt.gensalt());
185
+ assertFalse(BCrypt.checkpw(pw2, h1));
186
+ System.out.print(".");
187
+
188
+ String h2 = BCrypt.hashpw(pw2, BCrypt.gensalt());
189
+ assertFalse(BCrypt.checkpw(pw1, h2));
190
+ System.out.print(".");
191
+ System.out.println("");
192
+ }
193
+
194
+ }
@@ -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 eq 8
34
+ BCrypt::Engine.autodetect_cost("$2a$05$XKd1bMnLgUnc87qvbAaCUu").should eq 5
35
+ BCrypt::Engine.autodetect_cost("$2a$13$Lni.CZ6z5A7344POTFBBV.").should eq 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