bcrypt-ruby 1.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/COPYING +32 -0
- data/README +158 -0
- data/Rakefile +78 -0
- data/ext/bcrypt.c +334 -0
- data/ext/bcrypt_ext.c +28 -0
- data/ext/blf.h +84 -0
- data/ext/blowfish.c +685 -0
- data/ext/extconf.rb +5 -0
- data/lib/bcrypt.rb +151 -0
- data/spec/bcrypt/bcrypt_spec.rb +9 -0
- data/spec/bcrypt/internals_spec.rb +48 -0
- data/spec/bcrypt/password_spec.rb +53 -0
- data/spec/spec_helper.rb +4 -0
- metadata +65 -0
data/ext/extconf.rb
ADDED
data/lib/bcrypt.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
# A wrapper for OpenBSD's bcrypt/crypt_blowfish password-hashing algorithm.
|
2
|
+
|
3
|
+
$: << "ext"
|
4
|
+
require "bcrypt_ext"
|
5
|
+
require "openssl"
|
6
|
+
|
7
|
+
# A Ruby library implementing OpenBSD's bcrypt()/crypt_blowfish algorithm for
|
8
|
+
# hashing passwords.
|
9
|
+
module BCrypt
|
10
|
+
module Errors # :nodoc:
|
11
|
+
# The salt parameter provided to bcrypt() is invalid.
|
12
|
+
class InvalidSalt < Exception; end
|
13
|
+
# The hash parameter provided to bcrypt() is invalid.
|
14
|
+
class InvalidHash < Exception; end
|
15
|
+
# The cost parameter provided to bcrypt() is invalid.
|
16
|
+
class InvalidCost < Exception; end
|
17
|
+
end
|
18
|
+
|
19
|
+
# The default computational expense parameter.
|
20
|
+
DEFAULT_COST = 10
|
21
|
+
|
22
|
+
module Internals # :nodoc:
|
23
|
+
# contains the C-level methods and other internally bits
|
24
|
+
|
25
|
+
# Maximum length of salts.
|
26
|
+
MAX_SALT_LENGTH = 16
|
27
|
+
|
28
|
+
# Wrap this in some error checking otherwise it'll explode.
|
29
|
+
def _bc_crypt(key, salt)
|
30
|
+
if valid_salt?(salt)
|
31
|
+
__bc_crypt(key, salt)
|
32
|
+
else
|
33
|
+
raise Errors::InvalidSalt.new("invalid salt")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def _bc_salt(cost = DEFAULT_COST)
|
38
|
+
if cost.to_i > 0
|
39
|
+
__bc_salt(cost, OpenSSL::Random.random_bytes(MAX_SALT_LENGTH))
|
40
|
+
else
|
41
|
+
raise Errors::InvalidCost.new("cost must be numeric and > 0")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def valid_salt?(s)
|
46
|
+
s =~ /^\$[0-9a-z]{2,}\$[0-9]{2,}\$[A-Za-z0-9\.\/]{22,}$/
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns the cost factor which will result in computation times less than +upper_time_limit_in_ms+.
|
51
|
+
#
|
52
|
+
# Example:
|
53
|
+
#
|
54
|
+
# BCrypt.calibrate(200) #=> 10
|
55
|
+
# BCrypt.calibrate(1000) #=> 12
|
56
|
+
#
|
57
|
+
# # should take less than 200ms
|
58
|
+
# BCrypt::Password.create("woo", :cost => 10)
|
59
|
+
#
|
60
|
+
# # should take less than 1000ms
|
61
|
+
# BCrypt::Password.create("woo", :cost => 12)
|
62
|
+
def self.calibrate(upper_time_limit_in_ms)
|
63
|
+
40.times do |i|
|
64
|
+
start_time = Time.now
|
65
|
+
Password.create("testing testing", :cost => i+1)
|
66
|
+
end_time = Time.now - start_time
|
67
|
+
return i if end_time * 1_000 > upper_time_limit_in_ms
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# A password management class which allows you to safely store users' passwords and compare them.
|
72
|
+
#
|
73
|
+
# Example usage:
|
74
|
+
#
|
75
|
+
# include BCrypt
|
76
|
+
#
|
77
|
+
# # hash a user's password
|
78
|
+
# @password = Password.create("my grand secret")
|
79
|
+
# @password #=> "$2a$10$GtKs1Kbsig8ULHZzO1h2TetZfhO4Fmlxphp8bVKnUlZCBYYClPohG"
|
80
|
+
#
|
81
|
+
# # store it safely
|
82
|
+
# @user.update_attribute(:password, @password)
|
83
|
+
#
|
84
|
+
# # read it back
|
85
|
+
# @user.reload!
|
86
|
+
# @db_password = Password.new(@user.password)
|
87
|
+
#
|
88
|
+
# # compare it after retrieval
|
89
|
+
# @db_password == "my grand secret" #=> true
|
90
|
+
# @db_password == "a paltry guess" #=> false
|
91
|
+
#
|
92
|
+
class Password < String
|
93
|
+
# The hash portion of the stored password hash.
|
94
|
+
attr_reader :hash
|
95
|
+
# The salt of the store password hash (including version and cost).
|
96
|
+
attr_reader :salt
|
97
|
+
# The version of the bcrypt() algorithm used to create the hash.
|
98
|
+
attr_reader :version
|
99
|
+
# The cost factor used to create the hash.
|
100
|
+
attr_reader :cost
|
101
|
+
|
102
|
+
class << self
|
103
|
+
# Hashes a secret, returning a BCrypt::Password instance. Takes an optional <tt>:cost</tt> option, which is a
|
104
|
+
# logarithmic variable which determines how computational expensive the hash is to calculate (a <tt>:cost</tt> of
|
105
|
+
# 4 is twice as much work as a <tt>:cost</tt> of 3). The higher the <tt>:cost</tt> the harder it becomes for
|
106
|
+
# attackers to try to guess passwords (even if a copy of your database is stolen), but the slower it is to check
|
107
|
+
# users' passwords.
|
108
|
+
#
|
109
|
+
# Example:
|
110
|
+
#
|
111
|
+
# @password = BCrypt::Password.create("my secret", :cost => 13)
|
112
|
+
def create(secret, options = { :cost => DEFAULT_COST })
|
113
|
+
Password.new(_bc_crypt(secret, _bc_salt(options[:cost])))
|
114
|
+
end
|
115
|
+
private
|
116
|
+
include Internals
|
117
|
+
end
|
118
|
+
|
119
|
+
# Initializes a BCrypt::Password instance with the data from a stored hash.
|
120
|
+
def initialize(raw_hash)
|
121
|
+
if valid_hash?(raw_hash)
|
122
|
+
self.replace(raw_hash)
|
123
|
+
@version, @cost, @salt, @hash = split_hash(self)
|
124
|
+
else
|
125
|
+
raise Errors::InvalidHash.new("invalid hash")
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
alias_method :exactly_equals, :==
|
130
|
+
|
131
|
+
# Compares a potential secret against the hash. Returns true if the secret is the original secret, false otherwise.
|
132
|
+
def ==(secret)
|
133
|
+
self.exactly_equals(self.class._bc_crypt(secret, @salt))
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
# Returns true if +h+ is a valid hash.
|
138
|
+
def valid_hash?(h)
|
139
|
+
h =~ /^\$[0-9a-z]{2}\$[0-9]{2}\$[A-Za-z0-9\.\/]{53}$/
|
140
|
+
end
|
141
|
+
|
142
|
+
# call-seq:
|
143
|
+
# split_hash(raw_hash) -> version, cost, salt, hash
|
144
|
+
#
|
145
|
+
# Splits +h+ into version, cost, salt, and hash and returns them in that order.
|
146
|
+
def split_hash(h)
|
147
|
+
b, v, c, mash = h.split('$')
|
148
|
+
return v, c.to_i, h[0, 29], mash[-31, 31]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,9 @@
|
|
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
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "..", "spec_helper")
|
2
|
+
|
3
|
+
context "Generating BCrypt salts" do
|
4
|
+
include BCrypt::Internals
|
5
|
+
|
6
|
+
specify "should produce strings" do
|
7
|
+
_bc_salt.should be_an_instance_of(String)
|
8
|
+
end
|
9
|
+
|
10
|
+
specify "should produce random data" do
|
11
|
+
_bc_salt.should_not equal(_bc_salt)
|
12
|
+
end
|
13
|
+
|
14
|
+
specify "should raise a InvalidCostError if the cost parameter isn't numeric" do
|
15
|
+
lambda { _bc_salt('woo') }.should raise_error(BCrypt::Errors::InvalidCost)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "Generating BCrypt hashes" do
|
20
|
+
include BCrypt::Internals
|
21
|
+
|
22
|
+
setup do
|
23
|
+
@salt = _bc_salt
|
24
|
+
@password = "woo"
|
25
|
+
end
|
26
|
+
|
27
|
+
specify "should produce a string" do
|
28
|
+
_bc_crypt(@password, @salt).should be_an_instance_of(String)
|
29
|
+
end
|
30
|
+
|
31
|
+
specify "should raise an InvalidSaltError if the salt is invalid" do
|
32
|
+
lambda { _bc_crypt(@password, 'nino') }.should raise_error(BCrypt::Errors::InvalidSalt)
|
33
|
+
end
|
34
|
+
|
35
|
+
specify "should be interoperable with other implementations" do
|
36
|
+
# test vectors from the OpenWall implementation <http://www.openwall.com/crypt/>
|
37
|
+
test_vectors = [
|
38
|
+
["U*U", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW"],
|
39
|
+
["U*U*", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK"],
|
40
|
+
["U*U*U", "$2a$05$XXXXXXXXXXXXXXXXXXXXXO", "$2a$05$XXXXXXXXXXXXXXXXXXXXXOAcXxm9kjPGEMsLznoKqmqw7tc8WCx4a"],
|
41
|
+
["", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.7uG0VCzI2bS7j6ymqJi9CdcdxiRTWNy"],
|
42
|
+
["0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", "$2a$05$abcdefghijklmnopqrstuu", "$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui"]
|
43
|
+
]
|
44
|
+
for secret, salt, test_vector in test_vectors
|
45
|
+
_bc_crypt(secret, salt).should eql(test_vector)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "..", "spec_helper")
|
2
|
+
|
3
|
+
context "Creating a hashed password" do
|
4
|
+
|
5
|
+
setup do
|
6
|
+
@secret = "wheedle"
|
7
|
+
@password = BCrypt::Password.create(@secret)
|
8
|
+
end
|
9
|
+
|
10
|
+
specify "should return a BCrypt::Password" do
|
11
|
+
@password.should be_an_instance_of(BCrypt::Password)
|
12
|
+
end
|
13
|
+
|
14
|
+
specify "should return a valid bcrypt password" do
|
15
|
+
lambda { BCrypt::Password.new(@password) }.should_not raise_error
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
context "Reading a hashed password" do
|
21
|
+
setup do
|
22
|
+
@secret = "U*U"
|
23
|
+
@hash = "$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW"
|
24
|
+
end
|
25
|
+
|
26
|
+
specify "should read the version, cost, salt, and hash" do
|
27
|
+
password = BCrypt::Password.new(@hash)
|
28
|
+
password.version.should eql("2a")
|
29
|
+
password.cost.should equal(5)
|
30
|
+
password.salt.should eql("$2a$05$CCCCCCCCCCCCCCCCCCCCC.")
|
31
|
+
password.to_s.should_eql @hash
|
32
|
+
end
|
33
|
+
|
34
|
+
specify "should raise an InvalidHashError when given an invalid hash" do
|
35
|
+
lambda { BCrypt::Password.new('weedle') }.should raise_error(BCrypt::Errors::InvalidHash)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "Comparing a hashed password with a secret" do
|
40
|
+
setup do
|
41
|
+
@secret = "U*U"
|
42
|
+
@hash = "$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW"
|
43
|
+
@password = BCrypt::Password.create(@secret)
|
44
|
+
end
|
45
|
+
|
46
|
+
specify "should compare successfully to the original secret" do
|
47
|
+
(@password == @secret).should be(true)
|
48
|
+
end
|
49
|
+
|
50
|
+
specify "should compare unsuccessfully to anything besides original secret" do
|
51
|
+
(@password == "@secret").should be(false)
|
52
|
+
end
|
53
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.1
|
3
|
+
specification_version: 1
|
4
|
+
name: bcrypt-ruby
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 1.0.0
|
7
|
+
date: 2007-02-27 00:00:00 -08:00
|
8
|
+
summary: Blah.
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: coda.hale@gmail.com
|
12
|
+
homepage: http://bcrypt-ruby.rubyforge.org
|
13
|
+
rubyforge_project: bcrypt-ruby
|
14
|
+
description: Woot.
|
15
|
+
autorequire: bcrypt
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- - Coda Hale
|
31
|
+
files:
|
32
|
+
- COPYING
|
33
|
+
- Rakefile
|
34
|
+
- README
|
35
|
+
- lib/bcrypt.rb
|
36
|
+
- spec/spec_helper.rb
|
37
|
+
- spec/bcrypt/bcrypt_spec.rb
|
38
|
+
- spec/bcrypt/internals_spec.rb
|
39
|
+
- spec/bcrypt/password_spec.rb
|
40
|
+
- ext/bcrypt.c
|
41
|
+
- ext/bcrypt_ext.c
|
42
|
+
- ext/blowfish.c
|
43
|
+
- ext/blf.h
|
44
|
+
- ext/extconf.rb
|
45
|
+
test_files: []
|
46
|
+
|
47
|
+
rdoc_options:
|
48
|
+
- --title
|
49
|
+
- bcrypt-ruby
|
50
|
+
- --line-numbers
|
51
|
+
- --inline-source
|
52
|
+
- --main
|
53
|
+
- README
|
54
|
+
extra_rdoc_files:
|
55
|
+
- README
|
56
|
+
- COPYING
|
57
|
+
- lib/bcrypt.rb
|
58
|
+
executables: []
|
59
|
+
|
60
|
+
extensions:
|
61
|
+
- ext/extconf.rb
|
62
|
+
requirements: []
|
63
|
+
|
64
|
+
dependencies: []
|
65
|
+
|