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/ext/extconf.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "mkmf"
2
+ dir_config("bcrypt_ext")
3
+ # enable this when we're feeling nitpicky
4
+ # CONFIG['CC'] << " -Wall "
5
+ create_makefile("bcrypt_ext")
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
@@ -0,0 +1,4 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
2
+ require "rubygems"
3
+ require "spec"
4
+ require "bcrypt"
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
+