bcrypt4 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)))
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.to_str, c.to_i, h[0, 29].to_str, mash[-31, 31].to_str
84
+ end
85
+ end
86
+
87
+ end
data/lib/bcrypt.rb ADDED
@@ -0,0 +1,21 @@
1
+ # A Ruby library implementing OpenBSD's bcrypt()/crypt_blowfish algorithm for
2
+ # hashing passwords.
3
+ module BCrypt
4
+ end
5
+
6
+ if RUBY_PLATFORM == "java"
7
+ require 'java'
8
+ else
9
+ require "openssl"
10
+ end
11
+
12
+ begin
13
+ RUBY_VERSION =~ /(\d+.\d+)/
14
+ require "#{$1}/bcrypt_ext"
15
+ rescue LoadError
16
+ require "bcrypt_ext"
17
+ end
18
+
19
+ require 'bcrypt/error'
20
+ require 'bcrypt/engine'
21
+ require 'bcrypt/password'
@@ -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,105 @@
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
+ expect(second).to be > first
8
+ end
9
+ end
10
+
11
+ describe "Generating BCrypt salts" do
12
+
13
+ specify "should produce strings" do
14
+ expect(BCrypt::Engine.generate_salt).to be_an_instance_of(String)
15
+ end
16
+
17
+ specify "should produce random data" do
18
+ expect(BCrypt::Engine.generate_salt).to_not equal(BCrypt::Engine.generate_salt)
19
+ end
20
+
21
+ specify "should raise a InvalidCostError if the cost parameter isn't numeric" do
22
+ expect { BCrypt::Engine.generate_salt('woo') }.to 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
+ expect { BCrypt::Engine.generate_salt(-1) }.to raise_error(BCrypt::Errors::InvalidCost)
27
+ end
28
+ end
29
+
30
+ describe "Autodetecting of salt cost" do
31
+
32
+ specify "should work" do
33
+ expect(BCrypt::Engine.autodetect_cost("$2a$08$hRx2IVeHNsTSYYtUWn61Ou")).to eq 8
34
+ expect(BCrypt::Engine.autodetect_cost("$2a$05$XKd1bMnLgUnc87qvbAaCUu")).to eq 5
35
+ expect(BCrypt::Engine.autodetect_cost("$2a$13$Lni.CZ6z5A7344POTFBBV.")).to 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
+ expect(BCrypt::Engine.hash_secret(@password, @salt)).to be_an_instance_of(String)
53
+ end
54
+
55
+ specify "should raise an InvalidSalt error if the salt is invalid" do
56
+ expect { BCrypt::Engine.hash_secret(@password, 'nino') }.to raise_error(BCrypt::Errors::InvalidSalt)
57
+ end
58
+
59
+ specify "should raise an InvalidSecret error if the secret is invalid" do
60
+ expect { BCrypt::Engine.hash_secret(MyInvalidSecret.new, @salt) }.to raise_error(BCrypt::Errors::InvalidSecret)
61
+ expect { BCrypt::Engine.hash_secret(nil, @salt) }.not_to raise_error
62
+ expect { BCrypt::Engine.hash_secret(false, @salt) }.not_to raise_error
63
+ end
64
+
65
+ specify "should call #to_s on the secret and use the return value as the actual secret data" do
66
+ expect(BCrypt::Engine.hash_secret(false, @salt)).to eq 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
+ ["0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", "$2a$05$abcdefghijklmnopqrstuu", "$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui"],
76
+ ["\xa3", "$2x$05$/OK.fbVrR/bpIqNJ5ianF.", "$2x$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e"],
77
+ ["\xff\xff\xa3", "$2x$05$/OK.fbVrR/bpIqNJ5ianF.", "$2x$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e"],
78
+ ["\xff\xff\xa3", "$2y$05$/OK.fbVrR/bpIqNJ5ianF.", "$2y$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e"],
79
+ ["\xff\xff\xa3", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.nqd1wy.pTMdcvrRWxyiGL2eMz.2a85."],
80
+ ["\xff\xff\xa3", "$2b$05$/OK.fbVrR/bpIqNJ5ianF.", "$2b$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e"],
81
+ ["\xa3", "$2y$05$/OK.fbVrR/bpIqNJ5ianF.", "$2y$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq"],
82
+ ["\xa3", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq"],
83
+ ["\xa3", "$2b$05$/OK.fbVrR/bpIqNJ5ianF.", "$2b$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq"],
84
+ ["1\xa3" "345", "$2x$05$/OK.fbVrR/bpIqNJ5ianF.", "$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi"],
85
+ ["\xff\xa3" "345", "$2x$05$/OK.fbVrR/bpIqNJ5ianF.", "$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi"],
86
+ ["\xff\xa3" "34" "\xff\xff\xff\xa3" "345", "$2x$05$/OK.fbVrR/bpIqNJ5ianF.", "$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi"],
87
+ ["\xff\xa3" "34" "\xff\xff\xff\xa3" "345", "$2y$05$/OK.fbVrR/bpIqNJ5ianF.", "$2y$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi"],
88
+ ["\xff\xa3" "34" "\xff\xff\xff\xa3" "345", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.ZC1JEJ8Z4gPfpe1JOr/oyPXTWl9EFd."],
89
+ ["\xff\xa3" "345", "$2y$05$/OK.fbVrR/bpIqNJ5ianF.", "$2y$05$/OK.fbVrR/bpIqNJ5ianF.nRht2l/HRhr6zmCp9vYUvvsqynflf9e"],
90
+ ["\xff\xa3" "345", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.nRht2l/HRhr6zmCp9vYUvvsqynflf9e"],
91
+ ["\xa3" "ab", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS"],
92
+ ["\xa3" "ab", "$2x$05$/OK.fbVrR/bpIqNJ5ianF.", "$2x$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS"],
93
+ ["\xa3" "ab", "$2y$05$/OK.fbVrR/bpIqNJ5ianF.", "$2y$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS"],
94
+ ["\xd1\x91", "$2x$05$6bNw2HLQYeqHYyBfLMsv/O", "$2x$05$6bNw2HLQYeqHYyBfLMsv/OiwqTymGIGzFsA4hOTWebfehXHNprcAS"],
95
+ ["\xd0\xc1\xd2\xcf\xcc\xd8", "$2x$05$6bNw2HLQYeqHYyBfLMsv/O", "$2x$05$6bNw2HLQYeqHYyBfLMsv/O9LIGgn8OMzuDoHfof8AQimSGfcSWxnS"],
96
+ ["\xaa"*72+"chars after 72 are ignored as usual", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.swQOIzjOiJ9GHEPuhEkvqrUyvWhEMx6"],
97
+ ["\xaa\x55"*36, "$2a$05$/OK.fbVrR/bpIqNJ5ianF.", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.R9xrDjiycxMbQE2bp.vgqlYpW5wx2yy"],
98
+ ["\x55\xaa\xff"*24, "$2a$05$/OK.fbVrR/bpIqNJ5ianF.", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.9tQZzcJfm3uj2NvJ/n5xkhpqLrMpWCe"],
99
+ ["", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.7uG0VCzI2bS7j6ymqJi9CdcdxiRTWNy"]
100
+ ]
101
+ for secret, salt, test_vector in test_vectors
102
+ expect(BCrypt::Engine.hash_secret(secret, salt)).to eql(test_vector)
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,37 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
2
+
3
+ describe "Errors" do
4
+
5
+ shared_examples "descends from StandardError" do
6
+ it "can be rescued as a StandardError" do
7
+ expect(described_class).to be < StandardError
8
+ end
9
+ end
10
+
11
+ shared_examples "descends from BCrypt::Error" do
12
+ it "can be rescued as a BCrypt::Error" do
13
+ expect(described_class).to be < BCrypt::Error
14
+ end
15
+ end
16
+
17
+ describe BCrypt::Error do
18
+ include_examples "descends from StandardError"
19
+ end
20
+
21
+ describe BCrypt::Errors::InvalidCost do
22
+ include_examples "descends from BCrypt::Error"
23
+ end
24
+
25
+ describe BCrypt::Errors::InvalidHash do
26
+ include_examples "descends from BCrypt::Error"
27
+ end
28
+
29
+ describe BCrypt::Errors::InvalidSalt do
30
+ include_examples "descends from BCrypt::Error"
31
+ end
32
+
33
+ describe BCrypt::Errors::InvalidSecret do
34
+ include_examples "descends from BCrypt::Error"
35
+ end
36
+
37
+ end
@@ -0,0 +1,124 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
2
+
3
+ describe "Creating a hashed password" do
4
+
5
+ before :each do
6
+ @secret = "wheedle"
7
+ @password = BCrypt::Password.create(@secret, :cost => 4)
8
+ end
9
+
10
+ specify "should return a BCrypt::Password" do
11
+ expect(@password).to be_an_instance_of(BCrypt::Password)
12
+ end
13
+
14
+ specify "should return a valid bcrypt password" do
15
+ expect { BCrypt::Password.new(@password) }.not_to raise_error
16
+ end
17
+
18
+ specify "should behave normally if the secret is not a string" do
19
+ expect { BCrypt::Password.create(nil) }.not_to raise_error
20
+ expect { BCrypt::Password.create({:woo => "yeah"}) }.not_to raise_error
21
+ expect { BCrypt::Password.create(false) }.not_to raise_error
22
+ end
23
+
24
+ specify "should tolerate empty string secrets" do
25
+ expect { BCrypt::Password.create( "\n".chop ) }.not_to raise_error
26
+ expect { BCrypt::Password.create( "" ) }.not_to raise_error
27
+ expect { BCrypt::Password.create( String.new ) }.not_to raise_error
28
+ end
29
+ end
30
+
31
+ describe "Reading a hashed password" do
32
+ before :each do
33
+ @secret = "U*U"
34
+ @hash = "$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW"
35
+ end
36
+
37
+ specify "the cost is too damn high" do
38
+ expect {
39
+ BCrypt::Password.create("hello", :cost => 32)
40
+ }.to raise_error(ArgumentError)
41
+ end
42
+
43
+ specify "the cost should be set to the default if nil" do
44
+ expect(BCrypt::Password.create("hello", :cost => nil).cost).to equal(BCrypt::Engine::DEFAULT_COST)
45
+ end
46
+
47
+ specify "the cost should be set to the default if empty hash" do
48
+ expect(BCrypt::Password.create("hello", {}).cost).to equal(BCrypt::Engine::DEFAULT_COST)
49
+ end
50
+
51
+ specify "the cost should be set to the passed value if provided" do
52
+ expect(BCrypt::Password.create("hello", :cost => 5).cost).to equal(5)
53
+ end
54
+
55
+ specify "the cost should be set to the global value if set" do
56
+ BCrypt::Engine.cost = 5
57
+ expect(BCrypt::Password.create("hello").cost).to equal(5)
58
+ # unset the global value to not affect other tests
59
+ BCrypt::Engine.cost = nil
60
+ end
61
+
62
+ specify "the cost should be set to an overridden constant for backwards compatibility" do
63
+ # suppress "already initialized constant" warning
64
+ old_verbose, $VERBOSE = $VERBOSE, nil
65
+ old_default_cost = BCrypt::Engine::DEFAULT_COST
66
+
67
+ BCrypt::Engine::DEFAULT_COST = 5
68
+ expect(BCrypt::Password.create("hello").cost).to equal(5)
69
+
70
+ # reset default to not affect other tests
71
+ BCrypt::Engine::DEFAULT_COST = old_default_cost
72
+ $VERBOSE = old_verbose
73
+ end
74
+
75
+ specify "should read the version, cost, salt, and hash" do
76
+ password = BCrypt::Password.new(@hash)
77
+ expect(password.version).to eql("2a")
78
+ expect(password.version.class).to eq String
79
+ expect(password.cost).to equal(5)
80
+ expect(password.salt).to eql("$2a$05$CCCCCCCCCCCCCCCCCCCCC.")
81
+ expect(password.salt.class).to eq String
82
+ expect(password.checksum).to eq("E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW")
83
+ expect(password.checksum.class).to eq String
84
+ expect(password.to_s).to eql(@hash)
85
+ end
86
+
87
+ specify "should raise an InvalidHashError when given an invalid hash" do
88
+ expect { BCrypt::Password.new('weedle') }.to raise_error(BCrypt::Errors::InvalidHash)
89
+ end
90
+ end
91
+
92
+ describe "Comparing a hashed password with a secret" do
93
+ before :each do
94
+ @secret = "U*U"
95
+ @hash = "$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW"
96
+ @password = BCrypt::Password.create(@secret)
97
+ end
98
+
99
+ specify "should compare successfully to the original secret" do
100
+ expect((@password == @secret)).to be(true)
101
+ end
102
+
103
+ specify "should compare unsuccessfully to anything besides original secret" do
104
+ expect((@password == "@secret")).to be(false)
105
+ end
106
+ end
107
+
108
+ describe "Validating a generated salt" do
109
+ specify "should not accept an invalid salt" do
110
+ expect(BCrypt::Engine.valid_salt?("invalid")).to eq(false)
111
+ end
112
+ specify "should accept a valid salt" do
113
+ expect(BCrypt::Engine.valid_salt?(BCrypt::Engine.generate_salt)).to eq(true)
114
+ end
115
+ end
116
+
117
+ describe "Validating a password hash" do
118
+ specify "should not accept an invalid password" do
119
+ expect(BCrypt::Password.valid_hash?("i_am_so_not_valid")).to be_falsey
120
+ end
121
+ specify "should accept a valid password" do
122
+ expect(BCrypt::Password.valid_hash?(BCrypt::Password.create "i_am_so_valid")).to be_truthy
123
+ end
124
+ end
@@ -0,0 +1,2 @@
1
+ $:.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'bcrypt'