bcrypt-ruby 1.0.0 → 2.0.0
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/CHANGELOG +8 -0
- data/Rakefile +36 -12
- data/ext/bcrypt_ext.c +4 -4
- data/lib/bcrypt.rb +60 -51
- data/spec/bcrypt/{internals_spec.rb → engine_spec.rb} +24 -10
- data/spec/bcrypt/password_spec.rb +4 -1
- metadata +7 -6
- data/spec/bcrypt/bcrypt_spec.rb +0 -9
data/CHANGELOG
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
1.0.0 Feb 27 2007
|
2
|
+
- Initial release.
|
3
|
+
|
4
|
+
2.0.0 Mar 07 2007
|
5
|
+
- Removed BCrypt::Password#exactly_equals -- use BCrypt::Password#eql? instead.
|
6
|
+
- Added BCrypt::Password#is_password?.
|
7
|
+
- Refactored out BCrypt::Internals into more useful BCrypt::Engine.
|
8
|
+
- Added validation of secrets -- nil is not healthy.
|
data/Rakefile
CHANGED
@@ -4,9 +4,10 @@ require 'rake/gempackagetask'
|
|
4
4
|
require 'rake/contrib/rubyforgepublisher'
|
5
5
|
require 'rake/clean'
|
6
6
|
require 'rake/rdoctask'
|
7
|
+
require "benchmark"
|
7
8
|
|
8
9
|
PKG_NAME = "bcrypt-ruby"
|
9
|
-
PKG_VERSION = "
|
10
|
+
PKG_VERSION = "2.0.0"
|
10
11
|
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
11
12
|
PKG_FILES = FileList[
|
12
13
|
'[A-Z]*',
|
@@ -16,6 +17,14 @@ PKG_FILES = FileList[
|
|
16
17
|
'ext/*.h',
|
17
18
|
'ext/*.rb'
|
18
19
|
]
|
20
|
+
CLEAN.include(
|
21
|
+
"ext/*.o",
|
22
|
+
"ext/*.bundle",
|
23
|
+
"ext/*.so"
|
24
|
+
)
|
25
|
+
CLOBBER.include(
|
26
|
+
"doc/coverage"
|
27
|
+
)
|
19
28
|
|
20
29
|
task :default => [:spec]
|
21
30
|
|
@@ -24,30 +33,34 @@ Spec::Rake::SpecTask.new do |t|
|
|
24
33
|
t.spec_files = FileList['spec/**/*_spec.rb']
|
25
34
|
t.spec_opts = ['--color','--backtrace','--diff']
|
26
35
|
t.rcov = true
|
27
|
-
t.rcov_dir = 'doc/
|
36
|
+
t.rcov_dir = 'doc/coverage'
|
28
37
|
t.rcov_opts = ['--exclude', 'spec\/spec,spec\/.*_spec.rb']
|
29
38
|
end
|
30
39
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
40
|
+
namespace :spec do
|
41
|
+
desc "Run all specs and store html output in doc/specs.html"
|
42
|
+
Spec::Rake::SpecTask.new('html') do |t|
|
43
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
44
|
+
t.spec_opts = ['--diff','--format html','--backtrace','--out doc/specs.html']
|
45
|
+
end
|
35
46
|
end
|
36
47
|
|
37
48
|
desc 'Generate RDoc'
|
38
49
|
rd = Rake::RDocTask.new do |rdoc|
|
39
|
-
rdoc.rdoc_dir = 'doc/
|
50
|
+
rdoc.rdoc_dir = 'doc/rdoc'
|
40
51
|
rdoc.options << '--title' << 'bcrypt-ruby' << '--line-numbers' << '--inline-source' << '--main' << 'README'
|
41
52
|
rdoc.template = ENV['TEMPLATE'] if ENV['TEMPLATE']
|
42
|
-
rdoc.rdoc_files.include('README', 'COPYING', 'lib/**/*.rb')
|
53
|
+
rdoc.rdoc_files.include('README', 'COPYING', 'CHANGELOG', 'lib/**/*.rb')
|
43
54
|
end
|
44
55
|
|
45
56
|
spec = Gem::Specification.new do |s|
|
46
57
|
s.name = PKG_NAME
|
47
58
|
s.version = PKG_VERSION
|
48
|
-
s.summary = "
|
59
|
+
s.summary = "OpenBSD's bcrypt() password hashing algorithm."
|
49
60
|
s.description = <<-EOF
|
50
|
-
|
61
|
+
bcrypt() is a sophisticated and secure hash algorithm designed by The OpenBSD project
|
62
|
+
for hashing passwords. bcrypt-ruby provides a simple, humane wrapper for safely handling
|
63
|
+
passwords.
|
51
64
|
EOF
|
52
65
|
|
53
66
|
s.files = PKG_FILES.to_a
|
@@ -71,8 +84,19 @@ Rake::GemPackageTask.new(spec) do |pkg|
|
|
71
84
|
pkg.need_tar = true
|
72
85
|
end
|
73
86
|
|
74
|
-
task :compile do
|
87
|
+
task :compile => [:clean] do
|
75
88
|
Dir.chdir('./ext')
|
76
89
|
system "ruby extconf.rb"
|
77
90
|
system "make"
|
78
|
-
end
|
91
|
+
end
|
92
|
+
|
93
|
+
task :benchmark do
|
94
|
+
TESTS = 100
|
95
|
+
TEST_PWD = "this is a test"
|
96
|
+
require "lib/bcrypt"
|
97
|
+
Benchmark.bmbm do |results|
|
98
|
+
4.upto(10) do |n|
|
99
|
+
results.report("cost #{n}:") { TESTS.times { BCrypt::Password.create(TEST_PWD, :cost => n) } }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/ext/bcrypt_ext.c
CHANGED
@@ -4,7 +4,7 @@ char *bcrypt_gensalt(u_int8_t, u_int8_t *);
|
|
4
4
|
char *bcrypt(const char *, const char *);
|
5
5
|
|
6
6
|
VALUE mBCrypt;
|
7
|
-
VALUE
|
7
|
+
VALUE cBCryptEngine;
|
8
8
|
|
9
9
|
/* Given a logarithmic cost parameter, generates a salt for use with +bc_crypt+.
|
10
10
|
*/
|
@@ -21,8 +21,8 @@ static VALUE bc_crypt(VALUE self, VALUE key, VALUE salt) {
|
|
21
21
|
/* Create the BCrypt and BCrypt::Internals modules, and populate them with methods. */
|
22
22
|
void Init_bcrypt_ext(){
|
23
23
|
mBCrypt = rb_define_module("BCrypt");
|
24
|
-
|
24
|
+
cBCryptEngine = rb_define_class_under(mBCrypt, "Engine", rb_cObject);
|
25
25
|
|
26
|
-
|
27
|
-
|
26
|
+
rb_define_singleton_method(cBCryptEngine, "__bc_salt", bc_salt, 2);
|
27
|
+
rb_define_singleton_method(cBCryptEngine, "__bc_crypt", bc_crypt, 2);
|
28
28
|
}
|
data/lib/bcrypt.rb
CHANGED
@@ -1,40 +1,46 @@
|
|
1
1
|
# A wrapper for OpenBSD's bcrypt/crypt_blowfish password-hashing algorithm.
|
2
2
|
|
3
|
-
|
4
|
-
require "bcrypt_ext"
|
3
|
+
require "ext/bcrypt_ext"
|
5
4
|
require "openssl"
|
6
5
|
|
7
6
|
# A Ruby library implementing OpenBSD's bcrypt()/crypt_blowfish algorithm for
|
8
7
|
# hashing passwords.
|
9
|
-
module BCrypt
|
8
|
+
module BCrypt
|
10
9
|
module Errors # :nodoc:
|
11
|
-
# The salt parameter provided to bcrypt() is invalid.
|
12
|
-
class
|
13
|
-
# The
|
14
|
-
class
|
15
|
-
# The cost parameter provided to bcrypt() is invalid.
|
16
|
-
class InvalidCost < Exception; end
|
10
|
+
class InvalidSalt < Exception; end # The salt parameter provided to bcrypt() is invalid.
|
11
|
+
class InvalidHash < Exception; end # The hash parameter provided to bcrypt() is invalid.
|
12
|
+
class InvalidCost < Exception; end # The cost parameter provided to bcrypt() is invalid.
|
13
|
+
class InvalidSecret < Exception; end # The secret parameter provided to bcrypt() is invalid.
|
17
14
|
end
|
18
15
|
|
19
|
-
#
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
#
|
24
|
-
|
25
|
-
# Maximum length of salts.
|
16
|
+
# A Ruby wrapper for the bcrypt() extension calls.
|
17
|
+
class Engine
|
18
|
+
# The default computational expense parameter.
|
19
|
+
DEFAULT_COST = 10
|
20
|
+
# Maximum possible size of bcrypt() salts.
|
26
21
|
MAX_SALT_LENGTH = 16
|
27
22
|
|
28
|
-
#
|
29
|
-
|
30
|
-
|
31
|
-
|
23
|
+
# C-level routines which, if they don't get the right input, will crash the
|
24
|
+
# hell out of the Ruby process.
|
25
|
+
private_class_method :__bc_salt
|
26
|
+
private_class_method :__bc_crypt
|
27
|
+
|
28
|
+
# Given a secret and a valid salt (see BCrypt::Engine.generate_salt) calculates
|
29
|
+
# a bcrypt() password hash.
|
30
|
+
def self.hash(secret, salt)
|
31
|
+
if valid_secret?(secret)
|
32
|
+
if valid_salt?(salt)
|
33
|
+
__bc_crypt(secret, salt)
|
34
|
+
else
|
35
|
+
raise Errors::InvalidSalt.new("invalid salt")
|
36
|
+
end
|
32
37
|
else
|
33
|
-
raise Errors::
|
38
|
+
raise Errors::InvalidSecret.new("invalid secret")
|
34
39
|
end
|
35
40
|
end
|
36
41
|
|
37
|
-
|
42
|
+
# Generates a random salt with a given computational cost.
|
43
|
+
def self.generate_salt(cost = DEFAULT_COST)
|
38
44
|
if cost.to_i > 0
|
39
45
|
__bc_salt(cost, OpenSSL::Random.random_bytes(MAX_SALT_LENGTH))
|
40
46
|
else
|
@@ -42,29 +48,35 @@ module BCrypt
|
|
42
48
|
end
|
43
49
|
end
|
44
50
|
|
45
|
-
|
46
|
-
|
51
|
+
# Returns true if +salt+ is a valid bcrypt() salt, false if not.
|
52
|
+
def self.valid_salt?(salt)
|
53
|
+
salt =~ /^\$[0-9a-z]{2,}\$[0-9]{2,}\$[A-Za-z0-9\.\/]{22,}$/
|
47
54
|
end
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
55
|
+
|
56
|
+
# Returns true if +secret+ is a valid bcrypt() secret, false if not.
|
57
|
+
def self.valid_secret?(secret)
|
58
|
+
!secret.nil?
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the cost factor which will result in computation times less than +upper_time_limit_in_ms+.
|
62
|
+
#
|
63
|
+
# Example:
|
64
|
+
#
|
65
|
+
# BCrypt.calibrate(200) #=> 10
|
66
|
+
# BCrypt.calibrate(1000) #=> 12
|
67
|
+
#
|
68
|
+
# # should take less than 200ms
|
69
|
+
# BCrypt::Password.create("woo", :cost => 10)
|
70
|
+
#
|
71
|
+
# # should take less than 1000ms
|
72
|
+
# BCrypt::Password.create("woo", :cost => 12)
|
73
|
+
def self.calibrate(upper_time_limit_in_ms)
|
74
|
+
40.times do |i|
|
75
|
+
start_time = Time.now
|
76
|
+
Password.create("testing testing", :cost => i+1)
|
77
|
+
end_time = Time.now - start_time
|
78
|
+
return i if end_time * 1_000 > upper_time_limit_in_ms
|
79
|
+
end
|
68
80
|
end
|
69
81
|
end
|
70
82
|
|
@@ -109,11 +121,9 @@ module BCrypt
|
|
109
121
|
# Example:
|
110
122
|
#
|
111
123
|
# @password = BCrypt::Password.create("my secret", :cost => 13)
|
112
|
-
def create(secret, options = { :cost => DEFAULT_COST })
|
113
|
-
Password.new(
|
124
|
+
def create(secret, options = { :cost => BCrypt::Engine::DEFAULT_COST })
|
125
|
+
Password.new(BCrypt::Engine.hash(secret, BCrypt::Engine.generate_salt(options[:cost])))
|
114
126
|
end
|
115
|
-
private
|
116
|
-
include Internals
|
117
127
|
end
|
118
128
|
|
119
129
|
# Initializes a BCrypt::Password instance with the data from a stored hash.
|
@@ -126,12 +136,11 @@ module BCrypt
|
|
126
136
|
end
|
127
137
|
end
|
128
138
|
|
129
|
-
alias_method :exactly_equals, :==
|
130
|
-
|
131
139
|
# Compares a potential secret against the hash. Returns true if the secret is the original secret, false otherwise.
|
132
140
|
def ==(secret)
|
133
|
-
|
141
|
+
super(BCrypt::Engine.hash(secret, @salt))
|
134
142
|
end
|
143
|
+
alias_method :is_password?, :==
|
135
144
|
|
136
145
|
private
|
137
146
|
# Returns true if +h+ is a valid hash.
|
@@ -1,35 +1,49 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), "..", "spec_helper")
|
2
2
|
|
3
|
+
context "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(300)
|
7
|
+
second.should >(first + 1)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
3
11
|
context "Generating BCrypt salts" do
|
4
|
-
include BCrypt::Internals
|
5
12
|
|
6
13
|
specify "should produce strings" do
|
7
|
-
|
14
|
+
BCrypt::Engine.generate_salt.should be_an_instance_of(String)
|
8
15
|
end
|
9
16
|
|
10
17
|
specify "should produce random data" do
|
11
|
-
|
18
|
+
BCrypt::Engine.generate_salt.should_not equal(BCrypt::Engine.generate_salt)
|
12
19
|
end
|
13
20
|
|
14
21
|
specify "should raise a InvalidCostError if the cost parameter isn't numeric" do
|
15
|
-
lambda {
|
22
|
+
lambda { BCrypt::Engine.generate_salt('woo') }.should raise_error(BCrypt::Errors::InvalidCost)
|
23
|
+
end
|
24
|
+
|
25
|
+
specify "should raise a InvalidCostError if the cost parameter isn't greater than 0" do
|
26
|
+
lambda { BCrypt::Engine.generate_salt(-1) }.should raise_error(BCrypt::Errors::InvalidCost)
|
16
27
|
end
|
17
28
|
end
|
18
29
|
|
19
30
|
context "Generating BCrypt hashes" do
|
20
|
-
include BCrypt::Internals
|
21
31
|
|
22
32
|
setup do
|
23
|
-
@salt =
|
33
|
+
@salt = BCrypt::Engine.generate_salt(4)
|
24
34
|
@password = "woo"
|
25
35
|
end
|
26
36
|
|
27
37
|
specify "should produce a string" do
|
28
|
-
|
38
|
+
BCrypt::Engine.hash(@password, @salt).should be_an_instance_of(String)
|
39
|
+
end
|
40
|
+
|
41
|
+
specify "should raise an InvalidSalt error if the salt is invalid" do
|
42
|
+
lambda { BCrypt::Engine.hash(@password, 'nino') }.should raise_error(BCrypt::Errors::InvalidSalt)
|
29
43
|
end
|
30
44
|
|
31
|
-
specify "should raise an
|
32
|
-
lambda {
|
45
|
+
specify "should raise an InvalidSecret error if the secret is invalid" do
|
46
|
+
lambda { BCrypt::Engine.hash(nil, @salt) }.should raise_error(BCrypt::Errors::InvalidSecret)
|
33
47
|
end
|
34
48
|
|
35
49
|
specify "should be interoperable with other implementations" do
|
@@ -42,7 +56,7 @@ context "Generating BCrypt hashes" do
|
|
42
56
|
["0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", "$2a$05$abcdefghijklmnopqrstuu", "$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui"]
|
43
57
|
]
|
44
58
|
for secret, salt, test_vector in test_vectors
|
45
|
-
|
59
|
+
BCrypt::Engine.hash(secret, salt).should eql(test_vector)
|
46
60
|
end
|
47
61
|
end
|
48
62
|
end
|
@@ -4,7 +4,7 @@ context "Creating a hashed password" do
|
|
4
4
|
|
5
5
|
setup do
|
6
6
|
@secret = "wheedle"
|
7
|
-
@password = BCrypt::Password.create(@secret)
|
7
|
+
@password = BCrypt::Password.create(@secret, :cost => 4)
|
8
8
|
end
|
9
9
|
|
10
10
|
specify "should return a BCrypt::Password" do
|
@@ -15,6 +15,9 @@ context "Creating a hashed password" do
|
|
15
15
|
lambda { BCrypt::Password.new(@password) }.should_not raise_error
|
16
16
|
end
|
17
17
|
|
18
|
+
specify "should raise an InvalidSecret exception if the secret is nil" do
|
19
|
+
lambda { BCrypt::Password.create(nil) }.should raise_error(BCrypt::Errors::InvalidSecret)
|
20
|
+
end
|
18
21
|
end
|
19
22
|
|
20
23
|
context "Reading a hashed password" do
|
metadata
CHANGED
@@ -3,15 +3,15 @@ rubygems_version: 0.9.1
|
|
3
3
|
specification_version: 1
|
4
4
|
name: bcrypt-ruby
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version:
|
7
|
-
date: 2007-
|
8
|
-
summary:
|
6
|
+
version: 2.0.0
|
7
|
+
date: 2007-03-07 00:00:00 -08:00
|
8
|
+
summary: OpenBSD's bcrypt() password hashing algorithm.
|
9
9
|
require_paths:
|
10
10
|
- lib
|
11
11
|
email: coda.hale@gmail.com
|
12
12
|
homepage: http://bcrypt-ruby.rubyforge.org
|
13
13
|
rubyforge_project: bcrypt-ruby
|
14
|
-
description:
|
14
|
+
description: bcrypt() is a sophisticated and secure hash algorithm designed by The OpenBSD project for hashing passwords. bcrypt-ruby provides a simple, humane wrapper for safely handling passwords.
|
15
15
|
autorequire: bcrypt
|
16
16
|
default_executable:
|
17
17
|
bindir: bin
|
@@ -29,13 +29,13 @@ post_install_message:
|
|
29
29
|
authors:
|
30
30
|
- - Coda Hale
|
31
31
|
files:
|
32
|
+
- CHANGELOG
|
32
33
|
- COPYING
|
33
34
|
- Rakefile
|
34
35
|
- README
|
35
36
|
- lib/bcrypt.rb
|
36
37
|
- spec/spec_helper.rb
|
37
|
-
- spec/bcrypt/
|
38
|
-
- spec/bcrypt/internals_spec.rb
|
38
|
+
- spec/bcrypt/engine_spec.rb
|
39
39
|
- spec/bcrypt/password_spec.rb
|
40
40
|
- ext/bcrypt.c
|
41
41
|
- ext/bcrypt_ext.c
|
@@ -54,6 +54,7 @@ rdoc_options:
|
|
54
54
|
extra_rdoc_files:
|
55
55
|
- README
|
56
56
|
- COPYING
|
57
|
+
- CHANGELOG
|
57
58
|
- lib/bcrypt.rb
|
58
59
|
executables: []
|
59
60
|
|
data/spec/bcrypt/bcrypt_spec.rb
DELETED
@@ -1,9 +0,0 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), "..", "spec_helper")
|
2
|
-
|
3
|
-
context "BCrypt" do
|
4
|
-
specify "should calculate the optimal cost factor to fit in a specific time" do
|
5
|
-
first = BCrypt.calibrate(100)
|
6
|
-
second = BCrypt.calibrate(300)
|
7
|
-
second.should >(first + 1)
|
8
|
-
end
|
9
|
-
end
|