bcrypt-ruby 2.1.2 → 2.1.3
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.
- data/.gitignore +7 -0
- data/.rspec +3 -0
- data/Gemfile +2 -0
- data/Rakefile +34 -75
- data/bcrypt-ruby.gemspec +28 -0
- data/ext/mri/bcrypt.c +4 -4
- data/ext/mri/bcrypt.h +2 -2
- data/ext/mri/bcrypt_ext.c +3 -3
- data/lib/bcrypt.rb +34 -35
- data/spec/TestBCrypt.java +175 -0
- data/spec/bcrypt/engine_spec.rb +17 -17
- data/spec/bcrypt/password_spec.rb +16 -13
- data/spec/spec_helper.rb +2 -4
- metadata +50 -16
- data/ext/jruby/bcrypt_jruby/BCrypt.class +0 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/Rakefile
CHANGED
@@ -1,24 +1,12 @@
|
|
1
|
-
|
2
|
-
require "spec/rake/spectask"
|
1
|
+
require 'rspec/core/rake_task'
|
3
2
|
require 'rake/gempackagetask'
|
3
|
+
require 'rake/extensiontask'
|
4
|
+
require 'rake/javaextensiontask'
|
4
5
|
require 'rake/contrib/rubyforgepublisher'
|
5
6
|
require 'rake/clean'
|
6
7
|
require 'rake/rdoctask'
|
7
|
-
require
|
8
|
+
require 'benchmark'
|
8
9
|
|
9
|
-
PKG_NAME = "bcrypt-ruby"
|
10
|
-
PKG_VERSION = "2.1.2"
|
11
|
-
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
12
|
-
PKG_FILES = FileList[
|
13
|
-
'[A-Z]*',
|
14
|
-
'lib/**/*.rb',
|
15
|
-
'spec/**/*.rb',
|
16
|
-
'ext/mri/*.c',
|
17
|
-
'ext/mri/*.h',
|
18
|
-
'ext/mri/*.rb',
|
19
|
-
'ext/jruby/bcrypt_jruby/BCrypt.java',
|
20
|
-
'ext/jruby/bcrypt_jruby/BCrypt.class'
|
21
|
-
]
|
22
10
|
CLEAN.include(
|
23
11
|
"ext/mri/*.o",
|
24
12
|
"ext/mri/*.bundle",
|
@@ -30,91 +18,62 @@ CLOBBER.include(
|
|
30
18
|
"doc/coverage",
|
31
19
|
"pkg"
|
32
20
|
)
|
21
|
+
GEMSPEC = eval(File.read(File.expand_path("../bcrypt-ruby.gemspec", __FILE__)))
|
33
22
|
|
34
23
|
task :default => [:compile, :spec]
|
35
24
|
|
36
25
|
desc "Run all specs"
|
37
|
-
|
38
|
-
t.
|
39
|
-
t.spec_opts = ['--color','--backtrace','--diff']
|
26
|
+
RSpec::Core::RakeTask.new do |t|
|
27
|
+
t.pattern = 'spec/**/*_spec.rb'
|
40
28
|
end
|
41
29
|
|
42
30
|
desc "Run all specs, with coverage testing"
|
43
|
-
|
44
|
-
t.
|
45
|
-
t.spec_opts = ['--color','--backtrace','--diff']
|
31
|
+
RSpec::Core::RakeTask.new(:rcov) do |t|
|
32
|
+
t.pattern = 'spec/**/*_spec.rb'
|
46
33
|
t.rcov = true
|
47
|
-
t.
|
34
|
+
t.rcov_path = 'doc/coverage'
|
48
35
|
t.rcov_opts = ['--exclude', 'rspec,diff-lcs,rcov,_spec,_helper']
|
49
36
|
end
|
50
37
|
|
51
38
|
desc 'Generate RDoc'
|
52
39
|
rd = Rake::RDocTask.new do |rdoc|
|
53
40
|
rdoc.rdoc_dir = 'doc/rdoc'
|
54
|
-
rdoc.options
|
41
|
+
rdoc.options += GEMSPEC.rdoc_options
|
55
42
|
rdoc.template = ENV['TEMPLATE'] if ENV['TEMPLATE']
|
56
|
-
rdoc.rdoc_files.include(
|
43
|
+
rdoc.rdoc_files.include(*GEMSPEC.extra_rdoc_files)
|
57
44
|
end
|
58
45
|
|
59
|
-
|
60
|
-
s.name = PKG_NAME
|
61
|
-
s.version = PKG_VERSION
|
62
|
-
s.summary = "OpenBSD's bcrypt() password hashing algorithm."
|
63
|
-
s.description = <<-EOF
|
64
|
-
bcrypt() is a sophisticated and secure hash algorithm designed by The OpenBSD project
|
65
|
-
for hashing passwords. bcrypt-ruby provides a simple, humane wrapper for safely handling
|
66
|
-
passwords.
|
67
|
-
EOF
|
68
|
-
|
69
|
-
s.files = PKG_FILES.to_a
|
70
|
-
s.require_path = 'lib'
|
71
|
-
|
72
|
-
s.has_rdoc = true
|
73
|
-
s.rdoc_options = rd.options
|
74
|
-
s.extra_rdoc_files = rd.rdoc_files.to_a
|
75
|
-
|
76
|
-
s.extensions = FileList["ext/mri/extconf.rb"].to_a
|
77
|
-
|
78
|
-
s.authors = ["Coda Hale"]
|
79
|
-
s.email = "coda.hale@gmail.com"
|
80
|
-
s.homepage = "http://bcrypt-ruby.rubyforge.org"
|
81
|
-
s.rubyforge_project = "bcrypt-ruby"
|
82
|
-
end
|
83
|
-
|
84
|
-
file 'ext/jruby/bcrypt_jruby/BCrypt.class' => ["ext/jruby/bcrypt_jruby/BCrypt.java"] do
|
85
|
-
Rake::Task['compile:jruby'].invoke
|
86
|
-
end
|
87
|
-
|
88
|
-
Rake::GemPackageTask.new(spec) do |pkg|
|
46
|
+
Rake::GemPackageTask.new(GEMSPEC) do |pkg|
|
89
47
|
pkg.need_zip = true
|
90
48
|
pkg.need_tar = true
|
91
49
|
end
|
92
50
|
|
93
|
-
|
94
|
-
|
95
|
-
|
51
|
+
if RUBY_PLATFORM =~ /java/
|
52
|
+
Rake::JavaExtensionTask.new('bcrypt_ext', GEMSPEC) do |ext|
|
53
|
+
ext.ext_dir = 'ext/jruby'
|
54
|
+
end
|
96
55
|
else
|
97
|
-
|
98
|
-
|
56
|
+
Rake::ExtensionTask.new("bcrypt_ext", GEMSPEC) do |ext|
|
57
|
+
ext.ext_dir = 'ext/mri'
|
58
|
+
ext.cross_compile = true
|
59
|
+
ext.cross_platform = ['x86-mingw32', 'x86-mswin32-60']
|
99
60
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
desc "Clean, then compile the MRI extension"
|
105
|
-
task :mri => :clean do
|
106
|
-
Dir.chdir('ext/mri') do
|
107
|
-
ruby "extconf.rb"
|
108
|
-
sh "make"
|
61
|
+
# inject 1.8/1.9 pure-ruby entry point
|
62
|
+
ext.cross_compiling do |spec|
|
63
|
+
spec.files += ["lib/#{ext.name}.rb"]
|
109
64
|
end
|
110
65
|
end
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
66
|
+
end
|
67
|
+
|
68
|
+
# Entry point for fat-binary gems on win32
|
69
|
+
file("lib/bcrypt_ext.rb") do |t|
|
70
|
+
File.open(t.name, 'wb') do |f|
|
71
|
+
f.write <<-eoruby
|
72
|
+
RUBY_VERSION =~ /(\\d+.\\d+)/
|
73
|
+
require "\#{$1}/#{File.basename(t.name, '.rb')}"
|
74
|
+
eoruby
|
117
75
|
end
|
76
|
+
at_exit{ FileUtils.rm t.name if File.exists?(t.name) }
|
118
77
|
end
|
119
78
|
|
120
79
|
desc "Run a set of benchmarks on the compiled extension."
|
data/bcrypt-ruby.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'bcrypt-ruby'
|
3
|
+
s.version = '2.1.3'
|
4
|
+
|
5
|
+
s.summary = "OpenBSD's bcrypt() password hashing algorithm."
|
6
|
+
s.description = <<-EOF
|
7
|
+
bcrypt() is a sophisticated and secure hash algorithm designed by The OpenBSD project
|
8
|
+
for hashing passwords. bcrypt-ruby provides a simple, humane wrapper for safely handling
|
9
|
+
passwords.
|
10
|
+
EOF
|
11
|
+
|
12
|
+
s.files = `git ls-files`.split("\n")
|
13
|
+
s.require_path = 'lib'
|
14
|
+
|
15
|
+
s.add_development_dependency 'rake-compiler'
|
16
|
+
s.add_development_dependency 'rspec'
|
17
|
+
|
18
|
+
s.has_rdoc = true
|
19
|
+
s.rdoc_options += ['--title', 'bcrypt-ruby', '--line-numbers', '--inline-source', '--main', 'README']
|
20
|
+
s.extra_rdoc_files += ['README', 'COPYING', 'CHANGELOG', *Dir['lib/**/*.rb']]
|
21
|
+
|
22
|
+
s.extensions = 'ext/mri/extconf.rb'
|
23
|
+
|
24
|
+
s.authors = ["Coda Hale"]
|
25
|
+
s.email = "coda.hale@gmail.com"
|
26
|
+
s.homepage = "http://bcrypt-ruby.rubyforge.org"
|
27
|
+
s.rubyforge_project = "bcrypt-ruby"
|
28
|
+
end
|
data/ext/mri/bcrypt.c
CHANGED
@@ -77,10 +77,10 @@ static void encode_salt(char *, uint8_t *, uint16_t, uint8_t);
|
|
77
77
|
static void encode_base64(uint8_t *, uint8_t *, uint16_t);
|
78
78
|
static void decode_base64(uint8_t *, uint16_t, uint8_t *);
|
79
79
|
|
80
|
-
const
|
80
|
+
static const uint8_t Base64Code[] =
|
81
81
|
"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
82
82
|
|
83
|
-
const
|
83
|
+
static const uint8_t index_64[128] = {
|
84
84
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
85
85
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
86
86
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
@@ -150,7 +150,7 @@ encode_salt(char *salt, uint8_t *csalt, uint16_t clen, uint8_t logr)
|
|
150
150
|
*/
|
151
151
|
|
152
152
|
char *
|
153
|
-
|
153
|
+
ruby_bcrypt_gensalt(char *output, uint8_t log_rounds, uint8_t *rseed)
|
154
154
|
{
|
155
155
|
if (log_rounds < 4)
|
156
156
|
log_rounds = 4;
|
@@ -164,7 +164,7 @@ bcrypt_gensalt(char *output, uint8_t log_rounds, uint8_t *rseed)
|
|
164
164
|
i.e. $2$04$iwouldntknowwhattosayetKdJ6iFtacBqJdKe6aW7ou */
|
165
165
|
|
166
166
|
char *
|
167
|
-
|
167
|
+
ruby_bcrypt(char *output, const char *key, const char *salt)
|
168
168
|
{
|
169
169
|
blf_ctx state;
|
170
170
|
uint32_t rounds, i, k;
|
data/ext/mri/bcrypt.h
CHANGED
@@ -51,7 +51,7 @@
|
|
51
51
|
* cryptographically secure random source.
|
52
52
|
* Returns: output
|
53
53
|
*/
|
54
|
-
char *
|
54
|
+
char *ruby_bcrypt_gensalt(char *output, uint8_t log_rounds, uint8_t *rseed);
|
55
55
|
|
56
56
|
/*
|
57
57
|
* Given a secret and a salt, generates a salted hash (which you can then store safely).
|
@@ -62,6 +62,6 @@ char *bcrypt_gensalt(char *output, uint8_t log_rounds, uint8_t *rseed);
|
|
62
62
|
* salt: The salt, as generated by bcrypt_gensalt().
|
63
63
|
* Returns: output on success, NULL on error.
|
64
64
|
*/
|
65
|
-
char *
|
65
|
+
char *ruby_bcrypt(char *output, const char *key, const char *salt);
|
66
66
|
|
67
67
|
#endif /* _BCRYPT_H_ */
|
data/ext/mri/bcrypt_ext.c
CHANGED
@@ -30,7 +30,7 @@ static VALUE cBCryptEngine;
|
|
30
30
|
|
31
31
|
static VALUE bcrypt_wrapper(void *_args) {
|
32
32
|
BCryptArguments *args = (BCryptArguments *)_args;
|
33
|
-
return (VALUE)
|
33
|
+
return (VALUE)ruby_bcrypt(args->output, args->key, args->salt);
|
34
34
|
}
|
35
35
|
|
36
36
|
#endif /* RUBY_1_9 */
|
@@ -41,7 +41,7 @@ static VALUE bc_salt(VALUE self, VALUE cost, VALUE seed) {
|
|
41
41
|
int icost = NUM2INT(cost);
|
42
42
|
char salt[BCRYPT_SALT_OUTPUT_SIZE];
|
43
43
|
|
44
|
-
|
44
|
+
ruby_bcrypt_gensalt(salt, icost, (uint8_t *)RSTRING_PTR(seed));
|
45
45
|
return rb_str_new2(salt);
|
46
46
|
}
|
47
47
|
|
@@ -70,7 +70,7 @@ static VALUE bc_crypt(VALUE self, VALUE key, VALUE salt, VALUE cost) {
|
|
70
70
|
/* otherwise, fallback to the non-GIL-unlocking code, just like on Ruby 1.8 */
|
71
71
|
#endif
|
72
72
|
|
73
|
-
if (
|
73
|
+
if (ruby_bcrypt(output, safeguarded, (char *)RSTRING_PTR(salt)) != NULL) {
|
74
74
|
return rb_str_new2(output);
|
75
75
|
} else {
|
76
76
|
return Qnil;
|
data/lib/bcrypt.rb
CHANGED
@@ -2,13 +2,12 @@
|
|
2
2
|
|
3
3
|
if RUBY_PLATFORM == "java"
|
4
4
|
require 'java'
|
5
|
-
$CLASSPATH << File.expand_path(File.join(File.dirname(__FILE__), "..", "ext", "jruby"))
|
6
5
|
else
|
7
|
-
$LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "ext", "mri")))
|
8
|
-
require "bcrypt_ext"
|
9
6
|
require "openssl"
|
10
7
|
end
|
11
8
|
|
9
|
+
require 'bcrypt_ext'
|
10
|
+
|
12
11
|
# A Ruby library implementing OpenBSD's bcrypt()/crypt_blowfish algorithm for
|
13
12
|
# hashing passwords.
|
14
13
|
module BCrypt
|
@@ -18,7 +17,7 @@ module BCrypt
|
|
18
17
|
class InvalidCost < StandardError; end # The cost parameter provided to bcrypt() is invalid.
|
19
18
|
class InvalidSecret < StandardError; end # The secret parameter provided to bcrypt() is invalid.
|
20
19
|
end
|
21
|
-
|
20
|
+
|
22
21
|
# A Ruby wrapper for the bcrypt() C extension calls and the Java calls.
|
23
22
|
class Engine
|
24
23
|
# The default computational expense parameter.
|
@@ -27,14 +26,14 @@ module BCrypt
|
|
27
26
|
MIN_COST = 4
|
28
27
|
# Maximum possible size of bcrypt() salts.
|
29
28
|
MAX_SALT_LENGTH = 16
|
30
|
-
|
29
|
+
|
31
30
|
if RUBY_PLATFORM != "java"
|
32
31
|
# C-level routines which, if they don't get the right input, will crash the
|
33
32
|
# hell out of the Ruby process.
|
34
33
|
private_class_method :__bc_salt
|
35
34
|
private_class_method :__bc_crypt
|
36
35
|
end
|
37
|
-
|
36
|
+
|
38
37
|
# Given a secret and a valid salt (see BCrypt::Engine.generate_salt) calculates
|
39
38
|
# a bcrypt() password hash.
|
40
39
|
def self.hash_secret(secret, salt, cost = nil)
|
@@ -43,7 +42,7 @@ module BCrypt
|
|
43
42
|
if cost.nil?
|
44
43
|
cost = autodetect_cost(salt)
|
45
44
|
end
|
46
|
-
|
45
|
+
|
47
46
|
if RUBY_PLATFORM == "java"
|
48
47
|
Java.bcrypt_jruby.BCrypt.hashpw(secret.to_s, salt.to_s)
|
49
48
|
else
|
@@ -56,7 +55,7 @@ module BCrypt
|
|
56
55
|
raise Errors::InvalidSecret.new("invalid secret")
|
57
56
|
end
|
58
57
|
end
|
59
|
-
|
58
|
+
|
60
59
|
# Generates a random salt with a given computational cost.
|
61
60
|
def self.generate_salt(cost = DEFAULT_COST)
|
62
61
|
cost = cost.to_i
|
@@ -73,27 +72,27 @@ module BCrypt
|
|
73
72
|
raise Errors::InvalidCost.new("cost must be numeric and > 0")
|
74
73
|
end
|
75
74
|
end
|
76
|
-
|
75
|
+
|
77
76
|
# Returns true if +salt+ is a valid bcrypt() salt, false if not.
|
78
77
|
def self.valid_salt?(salt)
|
79
78
|
salt =~ /^\$[0-9a-z]{2,}\$[0-9]{2,}\$[A-Za-z0-9\.\/]{22,}$/
|
80
79
|
end
|
81
|
-
|
80
|
+
|
82
81
|
# Returns true if +secret+ is a valid bcrypt() secret, false if not.
|
83
82
|
def self.valid_secret?(secret)
|
84
83
|
secret.respond_to?(:to_s)
|
85
84
|
end
|
86
|
-
|
85
|
+
|
87
86
|
# Returns the cost factor which will result in computation times less than +upper_time_limit_in_ms+.
|
88
|
-
#
|
87
|
+
#
|
89
88
|
# Example:
|
90
|
-
#
|
89
|
+
#
|
91
90
|
# BCrypt.calibrate(200) #=> 10
|
92
91
|
# BCrypt.calibrate(1000) #=> 12
|
93
|
-
#
|
92
|
+
#
|
94
93
|
# # should take less than 200ms
|
95
94
|
# BCrypt::Password.create("woo", :cost => 10)
|
96
|
-
#
|
95
|
+
#
|
97
96
|
# # should take less than 1000ms
|
98
97
|
# BCrypt::Password.create("woo", :cost => 12)
|
99
98
|
def self.calibrate(upper_time_limit_in_ms)
|
@@ -104,88 +103,88 @@ module BCrypt
|
|
104
103
|
return i if end_time * 1_000 > upper_time_limit_in_ms
|
105
104
|
end
|
106
105
|
end
|
107
|
-
|
106
|
+
|
108
107
|
# Autodetects the cost from the salt string.
|
109
108
|
def self.autodetect_cost(salt)
|
110
109
|
salt[4..5].to_i
|
111
110
|
end
|
112
111
|
end
|
113
|
-
|
112
|
+
|
114
113
|
# A password management class which allows you to safely store users' passwords and compare them.
|
115
|
-
#
|
114
|
+
#
|
116
115
|
# Example usage:
|
117
|
-
#
|
116
|
+
#
|
118
117
|
# include BCrypt
|
119
|
-
#
|
118
|
+
#
|
120
119
|
# # hash a user's password
|
121
120
|
# @password = Password.create("my grand secret")
|
122
121
|
# @password #=> "$2a$10$GtKs1Kbsig8ULHZzO1h2TetZfhO4Fmlxphp8bVKnUlZCBYYClPohG"
|
123
|
-
#
|
122
|
+
#
|
124
123
|
# # store it safely
|
125
124
|
# @user.update_attribute(:password, @password)
|
126
|
-
#
|
125
|
+
#
|
127
126
|
# # read it back
|
128
127
|
# @user.reload!
|
129
128
|
# @db_password = Password.new(@user.password)
|
130
|
-
#
|
129
|
+
#
|
131
130
|
# # compare it after retrieval
|
132
131
|
# @db_password == "my grand secret" #=> true
|
133
132
|
# @db_password == "a paltry guess" #=> false
|
134
|
-
#
|
133
|
+
#
|
135
134
|
class Password < String
|
136
135
|
# The hash portion of the stored password hash.
|
137
|
-
attr_reader :
|
136
|
+
attr_reader :checksum
|
138
137
|
# The salt of the store password hash (including version and cost).
|
139
138
|
attr_reader :salt
|
140
139
|
# The version of the bcrypt() algorithm used to create the hash.
|
141
140
|
attr_reader :version
|
142
141
|
# The cost factor used to create the hash.
|
143
142
|
attr_reader :cost
|
144
|
-
|
143
|
+
|
145
144
|
class << self
|
146
145
|
# Hashes a secret, returning a BCrypt::Password instance. Takes an optional <tt>:cost</tt> option, which is a
|
147
146
|
# logarithmic variable which determines how computational expensive the hash is to calculate (a <tt>:cost</tt> of
|
148
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
|
149
148
|
# attackers to try to guess passwords (even if a copy of your database is stolen), but the slower it is to check
|
150
149
|
# users' passwords.
|
151
|
-
#
|
150
|
+
#
|
152
151
|
# Example:
|
153
|
-
#
|
152
|
+
#
|
154
153
|
# @password = BCrypt::Password.create("my secret", :cost => 13)
|
155
154
|
def create(secret, options = { :cost => BCrypt::Engine::DEFAULT_COST })
|
156
155
|
Password.new(BCrypt::Engine.hash_secret(secret, BCrypt::Engine.generate_salt(options[:cost]), options[:cost]))
|
157
156
|
end
|
158
157
|
end
|
159
|
-
|
158
|
+
|
160
159
|
# Initializes a BCrypt::Password instance with the data from a stored hash.
|
161
160
|
def initialize(raw_hash)
|
162
161
|
if valid_hash?(raw_hash)
|
163
162
|
self.replace(raw_hash)
|
164
|
-
@version, @cost, @salt, @
|
163
|
+
@version, @cost, @salt, @checksum = split_hash(self)
|
165
164
|
else
|
166
165
|
raise Errors::InvalidHash.new("invalid hash")
|
167
166
|
end
|
168
167
|
end
|
169
|
-
|
168
|
+
|
170
169
|
# Compares a potential secret against the hash. Returns true if the secret is the original secret, false otherwise.
|
171
170
|
def ==(secret)
|
172
171
|
super(BCrypt::Engine.hash_secret(secret, @salt))
|
173
172
|
end
|
174
173
|
alias_method :is_password?, :==
|
175
|
-
|
174
|
+
|
176
175
|
private
|
177
176
|
# Returns true if +h+ is a valid hash.
|
178
177
|
def valid_hash?(h)
|
179
178
|
h =~ /^\$[0-9a-z]{2}\$[0-9]{2}\$[A-Za-z0-9\.\/]{53}$/
|
180
179
|
end
|
181
|
-
|
180
|
+
|
182
181
|
# call-seq:
|
183
182
|
# split_hash(raw_hash) -> version, cost, salt, hash
|
184
|
-
#
|
183
|
+
#
|
185
184
|
# Splits +h+ into version, cost, salt, and hash and returns them in that order.
|
186
185
|
def split_hash(h)
|
187
186
|
b, v, c, mash = h.split('$')
|
188
|
-
return v, c.to_i, h[0, 29], mash[-31, 31]
|
187
|
+
return v, c.to_i, h[0, 29].to_str, mash[-31, 31].to_str
|
189
188
|
end
|
190
189
|
end
|
191
190
|
end
|
@@ -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
|
+
}
|
data/spec/bcrypt/engine_spec.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
|
2
2
|
|
3
|
-
|
3
|
+
describe "The BCrypt engine" do
|
4
4
|
specify "should calculate the optimal cost factor to fit in a specific time" do
|
5
5
|
first = BCrypt::Engine.calibrate(100)
|
6
6
|
second = BCrypt::Engine.calibrate(400)
|
@@ -8,64 +8,64 @@ context "The BCrypt engine" do
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
describe "Generating BCrypt salts" do
|
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
|
-
|
31
|
-
|
30
|
+
describe "Autodetecting of salt cost" do
|
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
|
-
|
41
|
-
|
40
|
+
describe "Generating BCrypt hashes" do
|
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 = [
|
@@ -1,26 +1,26 @@
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
describe "Creating a hashed password" do
|
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
|
@@ -28,37 +28,40 @@ context "Creating a hashed password" do
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
-
|
31
|
+
describe "Reading a hashed password" do
|
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
|
+
password.salt.class.should == String
|
43
|
+
password.checksum.should eq("E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW")
|
44
|
+
password.checksum.class.should == String
|
42
45
|
password.to_s.should eql(@hash)
|
43
46
|
end
|
44
|
-
|
47
|
+
|
45
48
|
specify "should raise an InvalidHashError when given an invalid hash" do
|
46
49
|
lambda { BCrypt::Password.new('weedle') }.should raise_error(BCrypt::Errors::InvalidHash)
|
47
50
|
end
|
48
51
|
end
|
49
52
|
|
50
|
-
|
53
|
+
describe "Comparing a hashed password with a secret" do
|
51
54
|
before :each do
|
52
55
|
@secret = "U*U"
|
53
56
|
@hash = "$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW"
|
54
57
|
@password = BCrypt::Password.create(@secret)
|
55
58
|
end
|
56
|
-
|
59
|
+
|
57
60
|
specify "should compare successfully to the original secret" do
|
58
61
|
(@password == @secret).should be(true)
|
59
62
|
end
|
60
|
-
|
63
|
+
|
61
64
|
specify "should compare unsuccessfully to anything besides original secret" do
|
62
65
|
(@password == "@secret").should be(false)
|
63
66
|
end
|
64
|
-
end
|
67
|
+
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bcrypt-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 2
|
7
|
+
- 1
|
8
|
+
- 3
|
9
|
+
version: 2.1.3
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- Coda Hale
|
@@ -9,10 +14,33 @@ autorequire:
|
|
9
14
|
bindir: bin
|
10
15
|
cert_chain: []
|
11
16
|
|
12
|
-
date:
|
17
|
+
date: 2010-12-20 00:00:00 -08:00
|
13
18
|
default_executable:
|
14
|
-
dependencies:
|
15
|
-
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rake-compiler
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
version: "0"
|
30
|
+
type: :development
|
31
|
+
version_requirements: *id001
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: rspec
|
34
|
+
prerelease: false
|
35
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
segments:
|
40
|
+
- 0
|
41
|
+
version: "0"
|
42
|
+
type: :development
|
43
|
+
version_requirements: *id002
|
16
44
|
description: " bcrypt() is a sophisticated and secure hash algorithm designed by The OpenBSD project\n for hashing passwords. bcrypt-ruby provides a simple, humane wrapper for safely handling\n passwords.\n"
|
17
45
|
email: coda.hale@gmail.com
|
18
46
|
executables: []
|
@@ -25,22 +53,26 @@ extra_rdoc_files:
|
|
25
53
|
- CHANGELOG
|
26
54
|
- lib/bcrypt.rb
|
27
55
|
files:
|
56
|
+
- .gitignore
|
57
|
+
- .rspec
|
28
58
|
- CHANGELOG
|
29
59
|
- COPYING
|
30
|
-
-
|
60
|
+
- Gemfile
|
31
61
|
- README
|
32
|
-
-
|
33
|
-
-
|
34
|
-
-
|
35
|
-
- spec/spec_helper.rb
|
62
|
+
- Rakefile
|
63
|
+
- bcrypt-ruby.gemspec
|
64
|
+
- ext/jruby/bcrypt_jruby/BCrypt.java
|
36
65
|
- ext/mri/bcrypt.c
|
37
|
-
- ext/mri/bcrypt_ext.c
|
38
|
-
- ext/mri/blowfish.c
|
39
66
|
- ext/mri/bcrypt.h
|
67
|
+
- ext/mri/bcrypt_ext.c
|
40
68
|
- ext/mri/blf.h
|
69
|
+
- ext/mri/blowfish.c
|
41
70
|
- ext/mri/extconf.rb
|
42
|
-
-
|
43
|
-
-
|
71
|
+
- lib/bcrypt.rb
|
72
|
+
- spec/TestBCrypt.java
|
73
|
+
- spec/bcrypt/engine_spec.rb
|
74
|
+
- spec/bcrypt/password_spec.rb
|
75
|
+
- spec/spec_helper.rb
|
44
76
|
has_rdoc: true
|
45
77
|
homepage: http://bcrypt-ruby.rubyforge.org
|
46
78
|
licenses: []
|
@@ -59,18 +91,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
59
91
|
requirements:
|
60
92
|
- - ">="
|
61
93
|
- !ruby/object:Gem::Version
|
94
|
+
segments:
|
95
|
+
- 0
|
62
96
|
version: "0"
|
63
|
-
version:
|
64
97
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
98
|
requirements:
|
66
99
|
- - ">="
|
67
100
|
- !ruby/object:Gem::Version
|
101
|
+
segments:
|
102
|
+
- 0
|
68
103
|
version: "0"
|
69
|
-
version:
|
70
104
|
requirements: []
|
71
105
|
|
72
106
|
rubyforge_project: bcrypt-ruby
|
73
|
-
rubygems_version: 1.3.
|
107
|
+
rubygems_version: 1.3.6
|
74
108
|
signing_key:
|
75
109
|
specification_version: 3
|
76
110
|
summary: OpenBSD's bcrypt() password hashing algorithm.
|
Binary file
|