bcrypt-ruby 2.1.2 → 2.1.3
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.
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
|