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