bcrypt4 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +3 -0
- data/.travis.yml +16 -0
- data/CHANGELOG +87 -0
- data/COPYING +28 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +44 -0
- data/README.md +192 -0
- data/Rakefile +93 -0
- data/bcrypt4.gemspec +29 -0
- data/ext/jruby/bcrypt_jruby/BCrypt.java +752 -0
- data/ext/mri/bcrypt_ext.c +64 -0
- data/ext/mri/crypt.c +57 -0
- data/ext/mri/crypt.h +24 -0
- data/ext/mri/crypt_blowfish.c +907 -0
- data/ext/mri/crypt_blowfish.h +27 -0
- data/ext/mri/crypt_gensalt.c +124 -0
- data/ext/mri/crypt_gensalt.h +30 -0
- data/ext/mri/extconf.rb +16 -0
- data/ext/mri/ow-crypt.h +43 -0
- data/ext/mri/wrapper.c +271 -0
- data/lib/bcrypt/engine.rb +116 -0
- data/lib/bcrypt/error.rb +22 -0
- data/lib/bcrypt/password.rb +87 -0
- data/lib/bcrypt.rb +21 -0
- data/spec/TestBCrypt.java +194 -0
- data/spec/bcrypt/engine_spec.rb +105 -0
- data/spec/bcrypt/error_spec.rb +37 -0
- data/spec/bcrypt/password_spec.rb +124 -0
- data/spec/spec_helper.rb +2 -0
- metadata +132 -0
@@ -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
|
data/spec/spec_helper.rb
ADDED