bcrypt-ruby 3.0.1 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -6,3 +6,4 @@ doc
6
6
  pkg
7
7
  *.class
8
8
  tmp/
9
+ .DS_Store
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.8.7"
4
+ - "1.9.2"
5
+ - "1.9.3"
6
+ - "2.0.0"
7
+ - jruby-18mode
8
+ - jruby-19mode
9
+ - rbx-18mode
10
+ - rbx-19mode
11
+ script: bundle exec rake
data/CHANGELOG CHANGED
@@ -39,9 +39,14 @@
39
39
  2.1.2 Sep 16 2009
40
40
  - Fixed support for Solaris, OpenSolaris.
41
41
 
42
- 3.0.0 Aug 24, 2011
42
+ 3.0.0 Aug 24 2011
43
43
  - Bcrypt C implementation replaced with a public domain implementation.
44
44
  - License changed to MIT
45
45
 
46
- 3.0.1
46
+ 3.0.1 Sep 12 2011
47
47
  - create raises an exception if the cost is higher than 31. GH #27
48
+
49
+ 3.1.0 May 07 2013
50
+ - Add BCrypt::Password.valid_hash?(str) to check if a string is a valid bcrypt password hash
51
+ - BCrypt::Password cost should be set to DEFAULT_COST if nil
52
+ - Add BCrypt::Engine.cost attribute for getting/setting a default cost externally
data/Gemfile CHANGED
@@ -1,2 +1,2 @@
1
- source :rubygems
1
+ source 'https://rubygems.org'
2
2
  gemspec
@@ -1,29 +1,35 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- bcrypt-ruby (3.0.0)
4
+ bcrypt-ruby (3.1.0)
5
5
 
6
6
  GEM
7
- remote: http://rubygems.org/
7
+ remote: https://rubygems.org/
8
8
  specs:
9
- diff-lcs (1.1.2)
10
- rake (0.8.7)
11
- rake-compiler (0.7.5)
9
+ diff-lcs (1.2.4)
10
+ json (1.7.3)
11
+ json (1.7.3-java)
12
+ rake (10.0.4)
13
+ rake-compiler (0.8.3)
12
14
  rake
13
- rspec (2.5.0)
14
- rspec-core (~> 2.5.0)
15
- rspec-expectations (~> 2.5.0)
16
- rspec-mocks (~> 2.5.0)
17
- rspec-core (2.5.1)
18
- rspec-expectations (2.5.0)
19
- diff-lcs (~> 1.1.2)
20
- rspec-mocks (2.5.0)
15
+ rdoc (3.12)
16
+ json (~> 1.4)
17
+ rspec (2.13.0)
18
+ rspec-core (~> 2.13.0)
19
+ rspec-expectations (~> 2.13.0)
20
+ rspec-mocks (~> 2.13.0)
21
+ rspec-core (2.13.1)
22
+ rspec-expectations (2.13.0)
23
+ diff-lcs (>= 1.1.3, < 2.0)
24
+ rspec-mocks (2.13.1)
21
25
 
22
26
  PLATFORMS
23
27
  java
24
28
  ruby
29
+ x86-mingw32
25
30
 
26
31
  DEPENDENCIES
27
32
  bcrypt-ruby!
28
33
  rake-compiler
34
+ rdoc
29
35
  rspec
data/README.md CHANGED
@@ -5,6 +5,8 @@ An easy way to keep your users' passwords secure.
5
5
  * http://bcrypt-ruby.rubyforge.org/
6
6
  * http://github.com/codahale/bcrypt-ruby/tree/master
7
7
 
8
+ [![Build Status](https://travis-ci.org/codahale/bcrypt-ruby.png?branch=master)](https://travis-ci.org/codahale/bcrypt-ruby)
9
+
8
10
  ## Why you should use `bcrypt()`
9
11
 
10
12
  If you store user passwords in the clear, then an attacker who steals a copy of your database has a giant list of emails
@@ -24,7 +26,7 @@ re-hash those passwords. This vulernability only affected the JRuby gem.
24
26
 
25
27
  ## How to install bcrypt
26
28
 
27
- sudo gem install bcrypt-ruby
29
+ gem install bcrypt-ruby
28
30
 
29
31
  The bcrypt-ruby gem is available on the following ruby platforms:
30
32
 
@@ -34,6 +36,10 @@ The bcrypt-ruby gem is available on the following ruby platforms:
34
36
 
35
37
  ## How to use `bcrypt()` in your Rails application
36
38
 
39
+ *Note*: Rails versions >= 3 ship with `ActiveModel::SecurePassword` which uses bcrypt-ruby.
40
+ `has_secure_password` [docs](http://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html#method-i-has_secure_password)
41
+ implements a similar authentication strategy to the code below.
42
+
37
43
  ### The _User_ model
38
44
 
39
45
  require 'bcrypt'
@@ -165,6 +171,20 @@ The default cost factor used by bcrypt-ruby is 10, which is fine for session-bas
165
171
  stateless authentication architecture (e.g., HTTP Basic Auth), you will want to lower the cost factor to reduce your
166
172
  server load and keep your request times down. This will lower the security provided you, but there are few alternatives.
167
173
 
174
+ To change the default cost factor used by bcrypt-ruby, use `BCrypt::Engine.cost = new_value`:
175
+
176
+ BCrypt::Password.create('secret').cost
177
+ #=> 10, the default provided by bcrypt-ruby
178
+
179
+ # set a new default cost
180
+ BCrypt::Engine.cost = 8
181
+ BCrypt::Password.create('secret').cost
182
+ #=> 8
183
+
184
+ The default cost can be overridden as needed by passing an options hash with a different cost:
185
+
186
+ BCrypt::Password.create('secret', :cost => 6).cost #=> 6
187
+
168
188
  ## More Information
169
189
 
170
190
  `bcrypt()` is currently used as the default password storage hash in OpenBSD, widely regarded as the most secure operating
data/Rakefile CHANGED
@@ -1,10 +1,10 @@
1
1
  require 'rspec/core/rake_task'
2
- require 'rake/gempackagetask'
2
+ require 'rubygems/package_task'
3
3
  require 'rake/extensiontask'
4
4
  require 'rake/javaextensiontask'
5
5
  require 'rake/contrib/rubyforgepublisher'
6
6
  require 'rake/clean'
7
- require 'rake/rdoctask'
7
+ require 'rdoc/task'
8
8
  require 'benchmark'
9
9
 
10
10
  CLEAN.include(
@@ -37,14 +37,14 @@ RSpec::Core::RakeTask.new(:rcov) do |t|
37
37
  end
38
38
 
39
39
  desc 'Generate RDoc'
40
- rd = Rake::RDocTask.new do |rdoc|
40
+ RDoc::Task.new do |rdoc|
41
41
  rdoc.rdoc_dir = 'doc/rdoc'
42
42
  rdoc.options += GEMSPEC.rdoc_options
43
43
  rdoc.template = ENV['TEMPLATE'] if ENV['TEMPLATE']
44
44
  rdoc.rdoc_files.include(*GEMSPEC.extra_rdoc_files)
45
45
  end
46
46
 
47
- Rake::GemPackageTask.new(GEMSPEC) do |pkg|
47
+ Gem::PackageTask.new(GEMSPEC) do |pkg|
48
48
  pkg.need_zip = true
49
49
  pkg.need_tar = true
50
50
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'bcrypt-ruby'
3
- s.version = '3.0.1'
3
+ s.version = '3.1.0'
4
4
 
5
5
  s.summary = "OpenBSD's bcrypt() password hashing algorithm."
6
6
  s.description = <<-EOF
@@ -14,6 +14,7 @@ Gem::Specification.new do |s|
14
14
 
15
15
  s.add_development_dependency 'rake-compiler'
16
16
  s.add_development_dependency 'rspec'
17
+ s.add_development_dependency 'rdoc'
17
18
 
18
19
  s.has_rdoc = true
19
20
  s.rdoc_options += ['--title', 'bcrypt-ruby', '--line-numbers', '--inline-source', '--main', 'README.md']
@@ -25,4 +26,5 @@ Gem::Specification.new do |s|
25
26
  s.email = "coda.hale@gmail.com"
26
27
  s.homepage = "http://bcrypt-ruby.rubyforge.org"
27
28
  s.rubyforge_project = "bcrypt-ruby"
29
+ s.license = "MIT"
28
30
  end
@@ -386,7 +386,7 @@ public class BCrypt {
386
386
  private static String encode_base64(byte d[], int len)
387
387
  throws IllegalArgumentException {
388
388
  int off = 0;
389
- StringBuffer rs = new StringBuffer();
389
+ StringBuilder rs = new StringBuilder();
390
390
  int c1, c2;
391
391
 
392
392
  if (len <= 0 || len > d.length)
@@ -439,7 +439,7 @@ public class BCrypt {
439
439
  */
440
440
  private static byte[] decode_base64(String s, int maxolen)
441
441
  throws IllegalArgumentException {
442
- StringBuffer rs = new StringBuffer();
442
+ StringBuilder rs = new StringBuilder();
443
443
  int off = 0, slen = s.length(), olen = 0;
444
444
  byte ret[];
445
445
  byte c1, c2, c3, c4, o;
@@ -651,7 +651,7 @@ public class BCrypt {
651
651
  byte passwordb[], saltb[], hashed[];
652
652
  char minor = (char)0;
653
653
  int rounds, off = 0;
654
- StringBuffer rs = new StringBuffer();
654
+ StringBuilder rs = new StringBuilder();
655
655
 
656
656
  if (salt.charAt(0) != '$' || salt.charAt(1) != '2')
657
657
  throw new IllegalArgumentException ("Invalid salt version");
@@ -704,7 +704,7 @@ public class BCrypt {
704
704
  * @return an encoded salt value
705
705
  */
706
706
  public static String gensalt(int log_rounds, SecureRandom random) {
707
- StringBuffer rs = new StringBuffer();
707
+ StringBuilder rs = new StringBuilder();
708
708
  byte rnd[] = new byte[BCRYPT_SALT_LEN];
709
709
 
710
710
  random.nextBytes(rnd);
@@ -4,31 +4,6 @@
4
4
  static VALUE mBCrypt;
5
5
  static VALUE cBCryptEngine;
6
6
 
7
- #ifdef RUBY_VM
8
- # define RUBY_1_9
9
- #endif
10
-
11
- #ifdef RUBY_1_9
12
-
13
- /* When on Ruby 1.9+, we will want to unlock the GIL while performing
14
- * expensive calculations, for greater concurrency. Do not do this for
15
- * cheap calculations because locking/unlocking the GIL incurs some overhead as well.
16
- */
17
- #define GIL_UNLOCK_COST_THRESHOLD 9
18
-
19
- typedef struct {
20
- char *output;
21
- const char *key;
22
- const char *salt;
23
- } BCryptArguments;
24
-
25
- static VALUE bcrypt_wrapper(void *_args) {
26
- BCryptArguments *args = (BCryptArguments *)_args;
27
- return (VALUE)ruby_bcrypt(args->output, args->key, args->salt);
28
- }
29
-
30
- #endif /* RUBY_1_9 */
31
-
32
7
  /* Given a logarithmic cost parameter, generates a salt for use with +bc_crypt+.
33
8
  */
34
9
  static VALUE bc_salt(VALUE self, VALUE prefix, VALUE count, VALUE input) {
@@ -9,31 +9,9 @@ if RUBY_PLATFORM == "java"
9
9
  f.puts "\t@true"
10
10
  end
11
11
  exit 0
12
- elsif defined?(RUBY_ENGINE) && RUBY_ENGINE == "maglev"
13
- # Maglev doesn't support C extensions, fall back to compiling an FFI usable
14
- # library
15
- File.open('Makefile', 'w') do |f|
16
- f.puts <<-MAKEFILE
17
- CFLAGS = -fPIC
18
- OBJS = bcrypt.o blowfish.o
19
- DLIB = bcrypt_ext.so
20
- OS ?= $(strip $(shell uname -s | tr '[:upper:]' '[:lower:]'))
21
- ifeq ($(OS),darwin)
22
- DLIB = bcrypt_ext.dylib
23
- CFLAGS += -dynamiclib
24
- endif
25
-
26
- all: $(OBJS)
27
- cc -shared -o $(DLIB) $(OBJS)
28
- install:
29
- install $(DLIB) "../../lib/"
30
- clean:
31
- $(RM) $(OBJS) bcrypt_ext.so
32
- MAKEFILE
33
- end
34
- exit 0
35
12
  else
36
13
  require "mkmf"
14
+ have_header('ruby/util.h')
37
15
  dir_config("bcrypt_ext")
38
16
  create_makefile("bcrypt_ext")
39
17
  end
@@ -24,7 +24,11 @@
24
24
  #endif
25
25
 
26
26
  #include <ruby.h>
27
+ #ifdef HAVE_RUBY_UTIL_H
28
+ #include <ruby/util.h>
29
+ #else
27
30
  #include <util.h>
31
+ #endif
28
32
 
29
33
  #define CRYPT_OUTPUT_SIZE (7 + 22 + 31 + 1)
30
34
  #define CRYPT_GENSALT_OUTPUT_SIZE (7 + 22 + 1)
@@ -1,4 +1,7 @@
1
- # A wrapper for OpenBSD's bcrypt/crypt_blowfish password-hashing algorithm.
1
+ # A Ruby library implementing OpenBSD's bcrypt()/crypt_blowfish algorithm for
2
+ # hashing passwords.
3
+ module BCrypt
4
+ end
2
5
 
3
6
  if RUBY_PLATFORM == "java"
4
7
  require 'java'
@@ -6,191 +9,7 @@ else
6
9
  require "openssl"
7
10
  end
8
11
 
9
- if defined?(RUBY_ENGINE) and RUBY_ENGINE == "maglev"
10
- require 'bcrypt_engine'
11
- else
12
- require 'bcrypt_ext'
13
- end
14
-
15
- # A Ruby library implementing OpenBSD's bcrypt()/crypt_blowfish algorithm for
16
- # hashing passwords.
17
- module BCrypt
18
- module Errors
19
- class InvalidSalt < StandardError; end # The salt parameter provided to bcrypt() is invalid.
20
- class InvalidHash < StandardError; end # The hash parameter provided to bcrypt() is invalid.
21
- class InvalidCost < StandardError; end # The cost parameter provided to bcrypt() is invalid.
22
- class InvalidSecret < StandardError; end # The secret parameter provided to bcrypt() is invalid.
23
- end
24
-
25
- # A Ruby wrapper for the bcrypt() C extension calls and the Java calls.
26
- class Engine
27
- # The default computational expense parameter.
28
- DEFAULT_COST = 10
29
- # The minimum cost supported by the algorithm.
30
- MIN_COST = 4
31
- # Maximum possible size of bcrypt() salts.
32
- MAX_SALT_LENGTH = 16
33
-
34
- if RUBY_PLATFORM != "java"
35
- # C-level routines which, if they don't get the right input, will crash the
36
- # hell out of the Ruby process.
37
- private_class_method :__bc_salt
38
- private_class_method :__bc_crypt
39
- end
40
-
41
- # Given a secret and a valid salt (see BCrypt::Engine.generate_salt) calculates
42
- # a bcrypt() password hash.
43
- def self.hash_secret(secret, salt, cost = nil)
44
- if valid_secret?(secret)
45
- if valid_salt?(salt)
46
- if cost.nil?
47
- cost = autodetect_cost(salt)
48
- end
49
-
50
- if RUBY_PLATFORM == "java"
51
- Java.bcrypt_jruby.BCrypt.hashpw(secret.to_s, salt.to_s)
52
- else
53
- __bc_crypt(secret.to_s, salt)
54
- end
55
- else
56
- raise Errors::InvalidSalt.new("invalid salt")
57
- end
58
- else
59
- raise Errors::InvalidSecret.new("invalid secret")
60
- end
61
- end
62
-
63
- # Generates a random salt with a given computational cost.
64
- def self.generate_salt(cost = DEFAULT_COST)
65
- cost = cost.to_i
66
- if cost > 0
67
- if cost < MIN_COST
68
- cost = MIN_COST
69
- end
70
- if RUBY_PLATFORM == "java"
71
- Java.bcrypt_jruby.BCrypt.gensalt(cost)
72
- else
73
- prefix = "$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW"
74
- __bc_salt(prefix, cost, OpenSSL::Random.random_bytes(MAX_SALT_LENGTH))
75
- end
76
- else
77
- raise Errors::InvalidCost.new("cost must be numeric and > 0")
78
- end
79
- end
80
-
81
- # Returns true if +salt+ is a valid bcrypt() salt, false if not.
82
- def self.valid_salt?(salt)
83
- !!(salt =~ /^\$[0-9a-z]{2,}\$[0-9]{2,}\$[A-Za-z0-9\.\/]{22,}$/)
84
- end
85
-
86
- # Returns true if +secret+ is a valid bcrypt() secret, false if not.
87
- def self.valid_secret?(secret)
88
- secret.respond_to?(:to_s)
89
- end
90
-
91
- # Returns the cost factor which will result in computation times less than +upper_time_limit_in_ms+.
92
- #
93
- # Example:
94
- #
95
- # BCrypt.calibrate(200) #=> 10
96
- # BCrypt.calibrate(1000) #=> 12
97
- #
98
- # # should take less than 200ms
99
- # BCrypt::Password.create("woo", :cost => 10)
100
- #
101
- # # should take less than 1000ms
102
- # BCrypt::Password.create("woo", :cost => 12)
103
- def self.calibrate(upper_time_limit_in_ms)
104
- 40.times do |i|
105
- start_time = Time.now
106
- Password.create("testing testing", :cost => i+1)
107
- end_time = Time.now - start_time
108
- return i if end_time * 1_000 > upper_time_limit_in_ms
109
- end
110
- end
111
-
112
- # Autodetects the cost from the salt string.
113
- def self.autodetect_cost(salt)
114
- salt[4..5].to_i
115
- end
116
- end
117
-
118
- # A password management class which allows you to safely store users' passwords and compare them.
119
- #
120
- # Example usage:
121
- #
122
- # include BCrypt
123
- #
124
- # # hash a user's password
125
- # @password = Password.create("my grand secret")
126
- # @password #=> "$2a$10$GtKs1Kbsig8ULHZzO1h2TetZfhO4Fmlxphp8bVKnUlZCBYYClPohG"
127
- #
128
- # # store it safely
129
- # @user.update_attribute(:password, @password)
130
- #
131
- # # read it back
132
- # @user.reload!
133
- # @db_password = Password.new(@user.password)
134
- #
135
- # # compare it after retrieval
136
- # @db_password == "my grand secret" #=> true
137
- # @db_password == "a paltry guess" #=> false
138
- #
139
- class Password < String
140
- # The hash portion of the stored password hash.
141
- attr_reader :checksum
142
- # The salt of the store password hash (including version and cost).
143
- attr_reader :salt
144
- # The version of the bcrypt() algorithm used to create the hash.
145
- attr_reader :version
146
- # The cost factor used to create the hash.
147
- attr_reader :cost
148
-
149
- class << self
150
- # Hashes a secret, returning a BCrypt::Password instance. Takes an optional <tt>:cost</tt> option, which is a
151
- # logarithmic variable which determines how computational expensive the hash is to calculate (a <tt>:cost</tt> of
152
- # 4 is twice as much work as a <tt>:cost</tt> of 3). The higher the <tt>:cost</tt> the harder it becomes for
153
- # attackers to try to guess passwords (even if a copy of your database is stolen), but the slower it is to check
154
- # users' passwords.
155
- #
156
- # Example:
157
- #
158
- # @password = BCrypt::Password.create("my secret", :cost => 13)
159
- def create(secret, options = { :cost => BCrypt::Engine::DEFAULT_COST })
160
- raise ArgumentError if options[:cost] > 31
161
- Password.new(BCrypt::Engine.hash_secret(secret, BCrypt::Engine.generate_salt(options[:cost]), options[:cost]))
162
- end
163
- end
164
-
165
- # Initializes a BCrypt::Password instance with the data from a stored hash.
166
- def initialize(raw_hash)
167
- if valid_hash?(raw_hash)
168
- self.replace(raw_hash)
169
- @version, @cost, @salt, @checksum = split_hash(self)
170
- else
171
- raise Errors::InvalidHash.new("invalid hash")
172
- end
173
- end
174
-
175
- # Compares a potential secret against the hash. Returns true if the secret is the original secret, false otherwise.
176
- def ==(secret)
177
- super(BCrypt::Engine.hash_secret(secret, @salt))
178
- end
179
- alias_method :is_password?, :==
180
-
181
- private
182
- # Returns true if +h+ is a valid hash.
183
- def valid_hash?(h)
184
- h =~ /^\$[0-9a-z]{2}\$[0-9]{2}\$[A-Za-z0-9\.\/]{53}$/
185
- end
186
-
187
- # call-seq:
188
- # split_hash(raw_hash) -> version, cost, salt, hash
189
- #
190
- # Splits +h+ into version, cost, salt, and hash and returns them in that order.
191
- def split_hash(h)
192
- _, v, c, mash = h.split('$')
193
- return v, c.to_i, h[0, 29].to_str, mash[-31, 31].to_str
194
- end
195
- end
196
- end
12
+ require 'bcrypt_ext'
13
+ require 'bcrypt/error'
14
+ require 'bcrypt/engine'
15
+ require 'bcrypt/password'
@@ -0,0 +1,120 @@
1
+ module BCrypt
2
+ # A Ruby wrapper for the bcrypt() C extension calls and the Java calls.
3
+ class Engine
4
+ # The default computational expense parameter.
5
+ DEFAULT_COST = 10
6
+ # The minimum cost supported by the algorithm.
7
+ MIN_COST = 4
8
+ # Maximum possible size of bcrypt() salts.
9
+ MAX_SALT_LENGTH = 16
10
+
11
+ if RUBY_PLATFORM != "java"
12
+ # C-level routines which, if they don't get the right input, will crash the
13
+ # hell out of the Ruby process.
14
+ private_class_method :__bc_salt
15
+ private_class_method :__bc_crypt
16
+ end
17
+
18
+ @cost = nil
19
+
20
+ # Returns the cost factor that will be used if one is not specified when
21
+ # creating a password hash. Defaults to DEFAULT_COST if not set.
22
+ def self.cost
23
+ @cost || DEFAULT_COST
24
+ end
25
+
26
+ # Set a default cost factor that will be used if one is not specified when
27
+ # creating a password hash.
28
+ #
29
+ # Example:
30
+ #
31
+ # BCrypt::Engine::DEFAULT_COST #=> 10
32
+ # BCrypt::Password.create('secret').cost #=> 10
33
+ #
34
+ # BCrypt::Engine.cost = 8
35
+ # BCrypt::Password.create('secret').cost #=> 8
36
+ #
37
+ # # cost can still be overridden as needed
38
+ # BCrypt::Password.create('secret', :cost => 6).cost #=> 6
39
+ def self.cost=(cost)
40
+ @cost = cost
41
+ end
42
+
43
+ # Given a secret and a valid salt (see BCrypt::Engine.generate_salt) calculates
44
+ # a bcrypt() password hash.
45
+ def self.hash_secret(secret, salt, cost = nil)
46
+ if valid_secret?(secret)
47
+ if valid_salt?(salt)
48
+ if cost.nil?
49
+ cost = autodetect_cost(salt)
50
+ end
51
+
52
+ if RUBY_PLATFORM == "java"
53
+ Java.bcrypt_jruby.BCrypt.hashpw(secret.to_s, salt.to_s)
54
+ else
55
+ __bc_crypt(secret.to_s, salt)
56
+ end
57
+ else
58
+ raise Errors::InvalidSalt.new("invalid salt")
59
+ end
60
+ else
61
+ raise Errors::InvalidSecret.new("invalid secret")
62
+ end
63
+ end
64
+
65
+ # Generates a random salt with a given computational cost.
66
+ def self.generate_salt(cost = self.cost)
67
+ cost = cost.to_i
68
+ if cost > 0
69
+ if cost < MIN_COST
70
+ cost = MIN_COST
71
+ end
72
+ if RUBY_PLATFORM == "java"
73
+ Java.bcrypt_jruby.BCrypt.gensalt(cost)
74
+ else
75
+ prefix = "$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW"
76
+ __bc_salt(prefix, cost, OpenSSL::Random.random_bytes(MAX_SALT_LENGTH))
77
+ end
78
+ else
79
+ raise Errors::InvalidCost.new("cost must be numeric and > 0")
80
+ end
81
+ end
82
+
83
+ # Returns true if +salt+ is a valid bcrypt() salt, false if not.
84
+ def self.valid_salt?(salt)
85
+ !!(salt =~ /^\$[0-9a-z]{2,}\$[0-9]{2,}\$[A-Za-z0-9\.\/]{22,}$/)
86
+ end
87
+
88
+ # Returns true if +secret+ is a valid bcrypt() secret, false if not.
89
+ def self.valid_secret?(secret)
90
+ secret.respond_to?(:to_s)
91
+ end
92
+
93
+ # Returns the cost factor which will result in computation times less than +upper_time_limit_in_ms+.
94
+ #
95
+ # Example:
96
+ #
97
+ # BCrypt::Engine.calibrate(200) #=> 10
98
+ # BCrypt::Engine.calibrate(1000) #=> 12
99
+ #
100
+ # # should take less than 200ms
101
+ # BCrypt::Password.create("woo", :cost => 10)
102
+ #
103
+ # # should take less than 1000ms
104
+ # BCrypt::Password.create("woo", :cost => 12)
105
+ def self.calibrate(upper_time_limit_in_ms)
106
+ 40.times do |i|
107
+ start_time = Time.now
108
+ Password.create("testing testing", :cost => i+1)
109
+ end_time = Time.now - start_time
110
+ return i if end_time * 1_000 > upper_time_limit_in_ms
111
+ end
112
+ end
113
+
114
+ # Autodetects the cost from the salt string.
115
+ def self.autodetect_cost(salt)
116
+ salt[4..5].to_i
117
+ end
118
+ end
119
+
120
+ end
@@ -0,0 +1,22 @@
1
+ module BCrypt
2
+
3
+ class Error < StandardError # :nodoc:
4
+ end
5
+
6
+ module Errors # :nodoc:
7
+
8
+ # The salt parameter provided to bcrypt() is invalid.
9
+ class InvalidSalt < BCrypt::Error; end
10
+
11
+ # The hash parameter provided to bcrypt() is invalid.
12
+ class InvalidHash < BCrypt::Error; end
13
+
14
+ # The cost parameter provided to bcrypt() is invalid.
15
+ class InvalidCost < BCrypt::Error; end
16
+
17
+ # The secret parameter provided to bcrypt() is invalid.
18
+ class InvalidSecret < BCrypt::Error; end
19
+
20
+ end
21
+
22
+ end
@@ -0,0 +1,87 @@
1
+ module BCrypt
2
+ # A password management class which allows you to safely store users' passwords and compare them.
3
+ #
4
+ # Example usage:
5
+ #
6
+ # include BCrypt
7
+ #
8
+ # # hash a user's password
9
+ # @password = Password.create("my grand secret")
10
+ # @password #=> "$2a$10$GtKs1Kbsig8ULHZzO1h2TetZfhO4Fmlxphp8bVKnUlZCBYYClPohG"
11
+ #
12
+ # # store it safely
13
+ # @user.update_attribute(:password, @password)
14
+ #
15
+ # # read it back
16
+ # @user.reload!
17
+ # @db_password = Password.new(@user.password)
18
+ #
19
+ # # compare it after retrieval
20
+ # @db_password == "my grand secret" #=> true
21
+ # @db_password == "a paltry guess" #=> false
22
+ #
23
+ class Password < String
24
+ # The hash portion of the stored password hash.
25
+ attr_reader :checksum
26
+ # The salt of the store password hash (including version and cost).
27
+ attr_reader :salt
28
+ # The version of the bcrypt() algorithm used to create the hash.
29
+ attr_reader :version
30
+ # The cost factor used to create the hash.
31
+ attr_reader :cost
32
+
33
+ class << self
34
+ # Hashes a secret, returning a BCrypt::Password instance. Takes an optional <tt>:cost</tt> option, which is a
35
+ # logarithmic variable which determines how computational expensive the hash is to calculate (a <tt>:cost</tt> of
36
+ # 4 is twice as much work as a <tt>:cost</tt> of 3). The higher the <tt>:cost</tt> the harder it becomes for
37
+ # attackers to try to guess passwords (even if a copy of your database is stolen), but the slower it is to check
38
+ # users' passwords.
39
+ #
40
+ # Example:
41
+ #
42
+ # @password = BCrypt::Password.create("my secret", :cost => 13)
43
+ def create(secret, options = {})
44
+ cost = options[:cost] || BCrypt::Engine.cost
45
+ raise ArgumentError if cost > 31
46
+ Password.new(BCrypt::Engine.hash_secret(secret, BCrypt::Engine.generate_salt(cost), cost))
47
+ end
48
+
49
+ def valid_hash?(h)
50
+ h =~ /^\$[0-9a-z]{2}\$[0-9]{2}\$[A-Za-z0-9\.\/]{53}$/
51
+ end
52
+ end
53
+
54
+ # Initializes a BCrypt::Password instance with the data from a stored hash.
55
+ def initialize(raw_hash)
56
+ if valid_hash?(raw_hash)
57
+ self.replace(raw_hash)
58
+ @version, @cost, @salt, @checksum = split_hash(self)
59
+ else
60
+ raise Errors::InvalidHash.new("invalid hash")
61
+ end
62
+ end
63
+
64
+ # Compares a potential secret against the hash. Returns true if the secret is the original secret, false otherwise.
65
+ def ==(secret)
66
+ super(BCrypt::Engine.hash_secret(secret, @salt))
67
+ end
68
+ alias_method :is_password?, :==
69
+
70
+ private
71
+
72
+ # Returns true if +h+ is a valid hash.
73
+ def valid_hash?(h)
74
+ self.class.valid_hash?(h)
75
+ end
76
+
77
+ # call-seq:
78
+ # split_hash(raw_hash) -> version, cost, salt, hash
79
+ #
80
+ # Splits +h+ into version, cost, salt, and hash and returns them in that order.
81
+ def split_hash(h)
82
+ _, v, c, mash = h.split('$')
83
+ return v, c.to_i, h[0, 29].to_str, mash[-31, 31].to_str
84
+ end
85
+ end
86
+
87
+ end
@@ -0,0 +1,37 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
2
+
3
+ describe "Errors" do
4
+
5
+ shared_examples "descends from StandardError" do
6
+ it "can be rescued as a StandardError" do
7
+ described_class.should < StandardError
8
+ end
9
+ end
10
+
11
+ shared_examples "descends from BCrypt::Error" do
12
+ it "can be rescued as a BCrypt::Error" do
13
+ described_class.should < BCrypt::Error
14
+ end
15
+ end
16
+
17
+ describe BCrypt::Error do
18
+ include_examples "descends from StandardError"
19
+ end
20
+
21
+ describe BCrypt::Errors::InvalidCost do
22
+ include_examples "descends from BCrypt::Error"
23
+ end
24
+
25
+ describe BCrypt::Errors::InvalidHash do
26
+ include_examples "descends from BCrypt::Error"
27
+ end
28
+
29
+ describe BCrypt::Errors::InvalidSalt do
30
+ include_examples "descends from BCrypt::Error"
31
+ end
32
+
33
+ describe BCrypt::Errors::InvalidSecret do
34
+ include_examples "descends from BCrypt::Error"
35
+ end
36
+
37
+ end
@@ -40,6 +40,38 @@ describe "Reading a hashed password" do
40
40
  }.should raise_error(ArgumentError)
41
41
  end
42
42
 
43
+ specify "the cost should be set to the default if nil" do
44
+ BCrypt::Password.create("hello", :cost => nil).cost.should equal(BCrypt::Engine::DEFAULT_COST)
45
+ end
46
+
47
+ specify "the cost should be set to the default if empty hash" do
48
+ BCrypt::Password.create("hello", {}).cost.should equal(BCrypt::Engine::DEFAULT_COST)
49
+ end
50
+
51
+ specify "the cost should be set to the passed value if provided" do
52
+ BCrypt::Password.create("hello", :cost => 5).cost.should equal(5)
53
+ end
54
+
55
+ specify "the cost should be set to the global value if set" do
56
+ BCrypt::Engine.cost = 5
57
+ BCrypt::Password.create("hello").cost.should equal(5)
58
+ # unset the global value to not affect other tests
59
+ BCrypt::Engine.cost = nil
60
+ end
61
+
62
+ specify "the cost should be set to an overridden constant for backwards compatibility" do
63
+ # suppress "already initialized constant" warning
64
+ old_verbose, $VERBOSE = $VERBOSE, nil
65
+ old_default_cost = BCrypt::Engine::DEFAULT_COST
66
+
67
+ BCrypt::Engine::DEFAULT_COST = 5
68
+ BCrypt::Password.create("hello").cost.should equal(5)
69
+
70
+ # reset default to not affect other tests
71
+ BCrypt::Engine::DEFAULT_COST = old_default_cost
72
+ $VERBOSE = old_verbose
73
+ end
74
+
43
75
  specify "should read the version, cost, salt, and hash" do
44
76
  password = BCrypt::Password.new(@hash)
45
77
  password.version.should eql("2a")
@@ -80,3 +112,12 @@ describe "Validating a generated salt" do
80
112
  BCrypt::Engine.valid_salt?(BCrypt::Engine.generate_salt).should eq(true)
81
113
  end
82
114
  end
115
+
116
+ describe "Validating a password hash" do
117
+ specify "should not accept an invalid password" do
118
+ BCrypt::Password.valid_hash?("i_am_so_not_valid").should be_false
119
+ end
120
+ specify "should accept a valid password" do
121
+ BCrypt::Password.valid_hash?(BCrypt::Password.create "i_am_so_valid").should be_true
122
+ end
123
+ end
metadata CHANGED
@@ -1,65 +1,83 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: bcrypt-ruby
3
- version: !ruby/object:Gem::Version
4
- hash: 5
3
+ version: !ruby/object:Gem::Version
4
+ version: 3.1.0
5
5
  prerelease:
6
- segments:
7
- - 3
8
- - 0
9
- - 1
10
- version: 3.0.1
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Coda Hale
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2011-09-12 00:00:00 Z
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
12
+ date: 2013-07-05 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
21
15
  name: rake-compiler
22
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
24
17
  none: false
25
- requirements:
26
- - - ">="
27
- - !ruby/object:Gem::Version
28
- hash: 3
29
- segments:
30
- - 0
31
- version: "0"
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
32
22
  type: :development
33
- version_requirements: *id001
34
- - !ruby/object:Gem::Dependency
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
35
31
  name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
36
39
  prerelease: false
37
- requirement: &id002 !ruby/object:Gem::Requirement
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rdoc
48
+ requirement: !ruby/object:Gem::Requirement
38
49
  none: false
39
- requirements:
40
- - - ">="
41
- - !ruby/object:Gem::Version
42
- hash: 3
43
- segments:
44
- - 0
45
- version: "0"
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
46
54
  type: :development
47
- version_requirements: *id002
48
- 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"
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: ! " bcrypt() is a sophisticated and secure hash algorithm designed
63
+ by The OpenBSD project\n for hashing passwords. bcrypt-ruby provides a simple,
64
+ humane wrapper for safely handling\n passwords.\n"
49
65
  email: coda.hale@gmail.com
50
66
  executables: []
51
-
52
- extensions:
67
+ extensions:
53
68
  - ext/mri/extconf.rb
54
- extra_rdoc_files:
69
+ extra_rdoc_files:
55
70
  - README.md
56
71
  - COPYING
57
72
  - CHANGELOG
73
+ - lib/bcrypt/engine.rb
74
+ - lib/bcrypt/error.rb
75
+ - lib/bcrypt/password.rb
58
76
  - lib/bcrypt.rb
59
- - lib/bcrypt_engine.rb
60
- files:
77
+ files:
61
78
  - .gitignore
62
79
  - .rspec
80
+ - .travis.yml
63
81
  - CHANGELOG
64
82
  - COPYING
65
83
  - Gemfile
@@ -77,48 +95,49 @@ files:
77
95
  - ext/mri/ow-crypt.h
78
96
  - ext/mri/wrapper.c
79
97
  - lib/bcrypt.rb
80
- - lib/bcrypt_engine.rb
98
+ - lib/bcrypt/engine.rb
99
+ - lib/bcrypt/error.rb
100
+ - lib/bcrypt/password.rb
81
101
  - spec/TestBCrypt.java
82
102
  - spec/bcrypt/engine_spec.rb
103
+ - spec/bcrypt/error_spec.rb
83
104
  - spec/bcrypt/password_spec.rb
84
105
  - spec/spec_helper.rb
85
106
  homepage: http://bcrypt-ruby.rubyforge.org
86
- licenses: []
87
-
107
+ licenses:
108
+ - MIT
88
109
  post_install_message:
89
- rdoc_options:
110
+ rdoc_options:
90
111
  - --title
91
112
  - bcrypt-ruby
92
113
  - --line-numbers
93
114
  - --inline-source
94
115
  - --main
95
116
  - README.md
96
- require_paths:
117
+ require_paths:
97
118
  - lib
98
- required_ruby_version: !ruby/object:Gem::Requirement
119
+ required_ruby_version: !ruby/object:Gem::Requirement
99
120
  none: false
100
- requirements:
101
- - - ">="
102
- - !ruby/object:Gem::Version
103
- hash: 3
104
- segments:
121
+ requirements:
122
+ - - ! '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ segments:
105
126
  - 0
106
- version: "0"
107
- required_rubygems_version: !ruby/object:Gem::Requirement
127
+ hash: -315332781
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
129
  none: false
109
- requirements:
110
- - - ">="
111
- - !ruby/object:Gem::Version
112
- hash: 3
113
- segments:
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ segments:
114
135
  - 0
115
- version: "0"
136
+ hash: -315332781
116
137
  requirements: []
117
-
118
138
  rubyforge_project: bcrypt-ruby
119
- rubygems_version: 1.8.8
139
+ rubygems_version: 1.8.25
120
140
  signing_key:
121
141
  specification_version: 3
122
142
  summary: OpenBSD's bcrypt() password hashing algorithm.
123
143
  test_files: []
124
-
@@ -1,34 +0,0 @@
1
- require 'ffi'
2
-
3
- module BCrypt
4
- class Engine
5
- extend FFI::Library
6
-
7
- BCRYPT_MAXSALT = 16
8
- BCRYPT_SALT_OUTPUT_SIZE = 7 + (BCRYPT_MAXSALT * 4 + 2) / 3 + 1
9
- BCRYPT_OUTPUT_SIZE = 128
10
-
11
- ffi_lib File.expand_path("../bcrypt_ext", __FILE__)
12
-
13
- attach_function :ruby_bcrypt, [:buffer_out, :string, :string], :string
14
- attach_function :ruby_bcrypt_gensalt, [:buffer_out, :uint8, :pointer], :string
15
-
16
- def self.__bc_salt(cost, seed)
17
- buffer_out = FFI::Buffer.alloc_out(BCRYPT_SALT_OUTPUT_SIZE, 1)
18
- seed_ptr = FFI::MemoryPointer.new(:uint8, BCRYPT_MAXSALT)
19
- seed.bytes.to_a.each_with_index { |b, i| seed_ptr.int8_put(i, b) }
20
- out = ruby_bcrypt_gensalt(buffer_out, cost, seed_ptr)
21
- seed_ptr.free
22
- buffer_out.free
23
- out || ""
24
- end
25
-
26
- def self.__bc_crypt(key, salt, cost)
27
- buffer_out = FFI::Buffer.alloc_out(BCRYPT_OUTPUT_SIZE, 1)
28
- out = ruby_bcrypt(buffer_out, key || "", salt)
29
- buffer_out.free
30
- out && out.any? ? out : nil
31
- end
32
- end
33
- end
34
-