bcrypt-ruby 2.0.5 → 2.1.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.

@@ -1,6 +1,11 @@
1
1
  /* $OpenBSD: bcrypt.c,v 1.22 2007/02/20 01:44:16 ray Exp $ */
2
2
 
3
3
  /*
4
+ * Modified by <hongli@phusion.nl> on 2009-08-05:
5
+ *
6
+ * - Got rid of the global variables; they're not thread-safe.
7
+ * Modified the functions to accept local buffers instead.
8
+ *
4
9
  * Modified by <coda.hale@gmail.com> on 2007-02-27:
5
10
  *
6
11
  * - Changed bcrypt_gensalt to accept a random seed as a parameter,
@@ -62,27 +67,17 @@
62
67
  #include <sys/types.h>
63
68
  #include <string.h>
64
69
  #include "blf.h"
70
+ #include "bcrypt.h"
65
71
 
66
72
  /* This implementation is adaptable to current computing power.
67
73
  * You can have up to 2^31 rounds which should be enough for some
68
74
  * time to come.
69
75
  */
70
76
 
71
- #define BCRYPT_VERSION '2'
72
- #define BCRYPT_MAXSALT 16 /* Precomputation is just so nice */
73
- #define BCRYPT_BLOCKS 6 /* Ciphertext blocks */
74
- #define BCRYPT_MINROUNDS 16 /* we have log2(rounds) in salt */
75
-
76
- char *bcrypt_gensalt(u_int8_t, u_int8_t *);
77
-
78
77
  static void encode_salt(char *, u_int8_t *, u_int16_t, u_int8_t);
79
78
  static void encode_base64(u_int8_t *, u_int8_t *, u_int16_t);
80
79
  static void decode_base64(u_int8_t *, u_int16_t, u_int8_t *);
81
80
 
82
- static char encrypted[_PASSWORD_LEN];
83
- static char gsalt[7 + (BCRYPT_MAXSALT * 4 + 2) / 3 + 1];
84
- static char error[] = ":";
85
-
86
81
  const static u_int8_t Base64Code[] =
87
82
  "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
88
83
 
@@ -155,22 +150,22 @@ encode_salt(char *salt, u_int8_t *csalt, u_int16_t clen, u_int8_t logr)
155
150
  seems sensible.
156
151
  */
157
152
 
158
- char *
159
- bcrypt_gensalt(u_int8_t log_rounds, u_int8_t *rseed)
153
+ char *
154
+ bcrypt_gensalt(char *output, u_int8_t log_rounds, u_int8_t *rseed)
160
155
  {
161
156
  if (log_rounds < 4)
162
157
  log_rounds = 4;
163
158
  else if (log_rounds > 31)
164
159
  log_rounds = 31;
165
160
 
166
- encode_salt(gsalt, rseed, BCRYPT_MAXSALT, log_rounds);
167
- return gsalt;
161
+ encode_salt(output, rseed, BCRYPT_MAXSALT, log_rounds);
162
+ return output;
168
163
  }
169
164
  /* We handle $Vers$log2(NumRounds)$salt+passwd$
170
165
  i.e. $2$04$iwouldntknowwhattosayetKdJ6iFtacBqJdKe6aW7ou */
171
166
 
172
167
  char *
173
- bcrypt(const char *key, const char *salt)
168
+ bcrypt(char *output, const char *key, const char *salt)
174
169
  {
175
170
  blf_ctx state;
176
171
  u_int32_t rounds, i, k;
@@ -185,8 +180,7 @@ bcrypt(const char *key, const char *salt)
185
180
  salt++;
186
181
 
187
182
  if (*salt > BCRYPT_VERSION) {
188
- /* How do I handle errors ? Return ':' */
189
- return error;
183
+ return NULL;
190
184
  }
191
185
 
192
186
  /* Check for minor versions */
@@ -198,7 +192,7 @@ bcrypt(const char *key, const char *salt)
198
192
  salt++;
199
193
  break;
200
194
  default:
201
- return error;
195
+ return NULL;
202
196
  }
203
197
  } else
204
198
  minor = 0;
@@ -208,21 +202,21 @@ bcrypt(const char *key, const char *salt)
208
202
 
209
203
  if (salt[2] != '$')
210
204
  /* Out of sync with passwd entry */
211
- return error;
205
+ return NULL;
212
206
 
213
207
  /* Computer power doesn't increase linear, 2^x should be fine */
214
208
  n = atoi(salt);
215
209
  if (n > 31 || n < 0)
216
- return error;
210
+ return NULL;
217
211
  logr = (u_int8_t)n;
218
212
  if ((rounds = (u_int32_t) 1 << logr) < BCRYPT_MINROUNDS)
219
- return error;
213
+ return NULL;
220
214
 
221
215
  /* Discard num rounds + "$" identifier */
222
216
  salt += 3;
223
217
 
224
218
  if (strlen(salt) * 3 / 4 < BCRYPT_MAXSALT)
225
- return error;
219
+ return NULL;
226
220
 
227
221
  /* We dont want the base64 salt but the raw data */
228
222
  decode_base64(csalt, BCRYPT_MAXSALT, (u_int8_t *) salt);
@@ -259,18 +253,18 @@ bcrypt(const char *key, const char *salt)
259
253
 
260
254
 
261
255
  i = 0;
262
- encrypted[i++] = '$';
263
- encrypted[i++] = BCRYPT_VERSION;
256
+ output[i++] = '$';
257
+ output[i++] = BCRYPT_VERSION;
264
258
  if (minor)
265
- encrypted[i++] = minor;
266
- encrypted[i++] = '$';
259
+ output[i++] = minor;
260
+ output[i++] = '$';
267
261
 
268
- snprintf(encrypted + i, 4, "%2.2u$", logr);
262
+ snprintf(output + i, 4, "%2.2u$", logr);
269
263
 
270
- encode_base64((u_int8_t *) encrypted + i + 3, csalt, BCRYPT_MAXSALT);
271
- encode_base64((u_int8_t *) encrypted + strlen(encrypted), ciphertext,
264
+ encode_base64((u_int8_t *) output + i + 3, csalt, BCRYPT_MAXSALT);
265
+ encode_base64((u_int8_t *) output + strlen(output), ciphertext,
272
266
  4 * BCRYPT_BLOCKS - 1);
273
- return encrypted;
267
+ return output;
274
268
  }
275
269
 
276
270
  static void
data/ext/mri/bcrypt.h ADDED
@@ -0,0 +1,65 @@
1
+ /*
2
+ * Copyright 1997 Niels Provos <provos@physnet.uni-hamburg.de>
3
+ * All rights reserved.
4
+ *
5
+ * Redistribution and use in source and binary forms, with or without
6
+ * modification, are permitted provided that the following conditions
7
+ * are met:
8
+ * 1. Redistributions of source code must retain the above copyright
9
+ * notice, this list of conditions and the following disclaimer.
10
+ * 2. Redistributions in binary form must reproduce the above copyright
11
+ * notice, this list of conditions and the following disclaimer in the
12
+ * documentation and/or other materials provided with the distribution.
13
+ * 3. All advertising materials mentioning features or use of this software
14
+ * must display the following acknowledgement:
15
+ * This product includes software developed by Niels Provos.
16
+ * 4. The name of the author may not be used to endorse or promote products
17
+ * derived from this software without specific prior written permission.
18
+ *
19
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
+ */
30
+
31
+ #ifndef _BCRYPT_H_
32
+ #define _BCRYPT_H_
33
+
34
+ #define BCRYPT_VERSION '2'
35
+ #define BCRYPT_MAXSALT 16 /* Precomputation is just so nice */
36
+ #define BCRYPT_BLOCKS 6 /* Ciphertext blocks */
37
+ #define BCRYPT_MINROUNDS 16 /* we have log2(rounds) in salt */
38
+ #define BCRYPT_SALT_OUTPUT_SIZE (7 + (BCRYPT_MAXSALT * 4 + 2) / 3 + 1)
39
+ #define BCRYPT_OUTPUT_SIZE 128
40
+
41
+ /*
42
+ * Given a logarithmic cost parameter, generates a salt for use with bcrypt().
43
+ *
44
+ * output: the computed salt will be stored here. This buffer must be
45
+ * at least BCRYPT_SALT_OUTPUT_SIZE bytes. The result will be
46
+ * null-terminated.
47
+ * log_rounds: the logarithmic cost.
48
+ * rseed: a seed of BCRYPT_MAXSALT bytes. Should be obtained from a
49
+ * cryptographically secure random source.
50
+ * Returns: output
51
+ */
52
+ char *bcrypt_gensalt(char *output, u_int8_t log_rounds, u_int8_t *rseed);
53
+
54
+ /*
55
+ * Given a secret and a salt, generates a salted hash (which you can then store safely).
56
+ *
57
+ * output: the computed salted hash will be stored here. This buffer must
58
+ * be at least BCRYPT_OUTPUT_SIZE bytes, and will become null-terminated.
59
+ * key: A null-terminated secret.
60
+ * salt: The salt, as generated by bcrypt_gensalt().
61
+ * Returns: output on success, NULL on error.
62
+ */
63
+ char *bcrypt(char *output, const char *key, const char *salt);
64
+
65
+ #endif /* _BCRYPT_H_ */
@@ -0,0 +1,87 @@
1
+ #include "ruby.h"
2
+ #include "bcrypt.h"
3
+
4
+ static VALUE mBCrypt;
5
+ static VALUE cBCryptEngine;
6
+
7
+ /* Define RSTRING_PTR for Ruby 1.8.5, ruby-core's idea of a point release is
8
+ insane. */
9
+ #ifndef RSTRING_PTR
10
+ # define RSTRING_PTR(s) (RSTRING(s)->ptr)
11
+ #endif
12
+
13
+ #ifdef RUBY_VM
14
+ # define RUBY_1_9
15
+ #endif
16
+
17
+ #ifdef RUBY_1_9
18
+
19
+ /* When on Ruby 1.9+, we will want to unlock the GIL while performing
20
+ * expensive calculations, for greater concurrency. Do not do this for
21
+ * cheap calculations because locking/unlocking the GIL incurs some overhead as well.
22
+ */
23
+ #define GIL_UNLOCK_COST_THRESHOLD 9
24
+
25
+ typedef struct {
26
+ char *output;
27
+ const char *key;
28
+ const char *salt;
29
+ } BCryptArguments;
30
+
31
+ static VALUE bcrypt_wrapper(void *_args) {
32
+ BCryptArguments *args = (BCryptArguments *)_args;
33
+ return (VALUE)bcrypt(args->output, args->key, args->salt);
34
+ }
35
+
36
+ #endif /* RUBY_1_9 */
37
+
38
+ /* Given a logarithmic cost parameter, generates a salt for use with +bc_crypt+.
39
+ */
40
+ static VALUE bc_salt(VALUE self, VALUE cost, VALUE seed) {
41
+ int icost = NUM2INT(cost);
42
+ char salt[BCRYPT_SALT_OUTPUT_SIZE];
43
+
44
+ bcrypt_gensalt(salt, icost, (u_int8_t *)RSTRING_PTR(seed));
45
+ return rb_str_new2(salt);
46
+ }
47
+
48
+ /* Given a secret and a salt, generates a salted hash (which you can then store safely).
49
+ */
50
+ static VALUE bc_crypt(VALUE self, VALUE key, VALUE salt, VALUE cost) {
51
+ const char * safeguarded = RSTRING_PTR(key) ? RSTRING_PTR(key) : "";
52
+ char output[BCRYPT_OUTPUT_SIZE];
53
+
54
+ #ifdef RUBY_1_9
55
+ int icost = NUM2INT(cost);
56
+ if (icost >= GIL_UNLOCK_COST_THRESHOLD) {
57
+ BCryptArguments args;
58
+ VALUE ret;
59
+
60
+ args.output = output;
61
+ args.key = safeguarded;
62
+ args.salt = RSTRING_PTR(salt);
63
+ ret = rb_thread_blocking_region(bcrypt_wrapper, &args, RUBY_UBF_IO, 0);
64
+ if (ret != (VALUE) 0) {
65
+ return rb_str_new2(output);
66
+ } else {
67
+ return Qnil;
68
+ }
69
+ }
70
+ /* otherwise, fallback to the non-GIL-unlocking code, just like on Ruby 1.8 */
71
+ #endif
72
+
73
+ if (bcrypt(output, safeguarded, (char *)RSTRING_PTR(salt)) != NULL) {
74
+ return rb_str_new2(output);
75
+ } else {
76
+ return Qnil;
77
+ }
78
+ }
79
+
80
+ /* Create the BCrypt and BCrypt::Engine modules, and populate them with methods. */
81
+ void Init_bcrypt_ext(){
82
+ mBCrypt = rb_define_module("BCrypt");
83
+ cBCryptEngine = rb_define_class_under(mBCrypt, "Engine", rb_cObject);
84
+
85
+ rb_define_singleton_method(cBCryptEngine, "__bc_salt", bc_salt, 2);
86
+ rb_define_singleton_method(cBCryptEngine, "__bc_crypt", bc_crypt, 3);
87
+ }
File without changes
File without changes
@@ -0,0 +1,18 @@
1
+ if RUBY_PLATFORM == "java"
2
+ # Don't do anything when run in JRuby; this allows gem installation to pass.
3
+ # We need to write a dummy Makefile so that RubyGems doesn't think compilation
4
+ # failed.
5
+ File.open('Makefile', 'w') do |f|
6
+ f.puts "all:"
7
+ f.puts "\t@true"
8
+ f.puts "install:"
9
+ f.puts "\t@true"
10
+ end
11
+ exit 0
12
+ else
13
+ require "mkmf"
14
+ dir_config("bcrypt_ext")
15
+ # enable this when we're feeling nitpicky
16
+ # CONFIG['CC'] << " -Wall "
17
+ create_makefile("bcrypt_ext")
18
+ end
data/lib/bcrypt.rb CHANGED
@@ -1,8 +1,13 @@
1
1
  # A wrapper for OpenBSD's bcrypt/crypt_blowfish password-hashing algorithm.
2
2
 
3
- $: << "ext"
4
- require "bcrypt_ext"
5
- require "openssl"
3
+ if RUBY_PLATFORM == "java"
4
+ require 'java'
5
+ $CLASSPATH << File.expand_path(File.join(File.dirname(__FILE__), "..", "ext", "jruby"))
6
+ else
7
+ $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "ext", "mri")))
8
+ require "bcrypt_ext"
9
+ require "openssl"
10
+ end
6
11
 
7
12
  # A Ruby library implementing OpenBSD's bcrypt()/crypt_blowfish algorithm for
8
13
  # hashing passwords.
@@ -14,24 +19,36 @@ module BCrypt
14
19
  class InvalidSecret < StandardError; end # The secret parameter provided to bcrypt() is invalid.
15
20
  end
16
21
 
17
- # A Ruby wrapper for the bcrypt() extension calls.
22
+ # A Ruby wrapper for the bcrypt() C extension calls and the Java calls.
18
23
  class Engine
19
24
  # The default computational expense parameter.
20
25
  DEFAULT_COST = 10
26
+ # The minimum cost supported by the algorithm.
27
+ MIN_COST = 4
21
28
  # Maximum possible size of bcrypt() salts.
22
29
  MAX_SALT_LENGTH = 16
23
30
 
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
31
+ if RUBY_PLATFORM != "java"
32
+ # C-level routines which, if they don't get the right input, will crash the
33
+ # hell out of the Ruby process.
34
+ private_class_method :__bc_salt
35
+ private_class_method :__bc_crypt
36
+ end
28
37
 
29
38
  # Given a secret and a valid salt (see BCrypt::Engine.generate_salt) calculates
30
39
  # a bcrypt() password hash.
31
- def self.hash_secret(secret, salt)
40
+ def self.hash_secret(secret, salt, cost = nil)
32
41
  if valid_secret?(secret)
33
42
  if valid_salt?(salt)
34
- __bc_crypt(secret.to_s, salt)
43
+ if cost.nil?
44
+ cost = autodetect_cost(salt)
45
+ end
46
+
47
+ if RUBY_PLATFORM == "java"
48
+ Java.bcrypt_jruby.BCrypt.hashpw(secret.to_s, salt.to_s)
49
+ else
50
+ __bc_crypt(secret.to_s, salt, cost)
51
+ end
35
52
  else
36
53
  raise Errors::InvalidSalt.new("invalid salt")
37
54
  end
@@ -42,8 +59,16 @@ module BCrypt
42
59
 
43
60
  # Generates a random salt with a given computational cost.
44
61
  def self.generate_salt(cost = DEFAULT_COST)
45
- if cost.to_i > 0
46
- __bc_salt(cost, OpenSSL::Random.random_bytes(MAX_SALT_LENGTH))
62
+ cost = cost.to_i
63
+ if cost > 0
64
+ if cost < MIN_COST
65
+ cost = MIN_COST
66
+ end
67
+ if RUBY_PLATFORM == "java"
68
+ Java.bcrypt_jruby.BCrypt.gensalt(cost)
69
+ else
70
+ __bc_salt(cost, OpenSSL::Random.random_bytes(MAX_SALT_LENGTH))
71
+ end
47
72
  else
48
73
  raise Errors::InvalidCost.new("cost must be numeric and > 0")
49
74
  end
@@ -79,6 +104,11 @@ module BCrypt
79
104
  return i if end_time * 1_000 > upper_time_limit_in_ms
80
105
  end
81
106
  end
107
+
108
+ # Autodetects the cost from the salt string.
109
+ def self.autodetect_cost(salt)
110
+ salt[4..5].to_i
111
+ end
82
112
  end
83
113
 
84
114
  # A password management class which allows you to safely store users' passwords and compare them.
@@ -123,7 +153,7 @@ module BCrypt
123
153
  #
124
154
  # @password = BCrypt::Password.create("my secret", :cost => 13)
125
155
  def create(secret, options = { :cost => BCrypt::Engine::DEFAULT_COST })
126
- Password.new(BCrypt::Engine.hash_secret(secret, BCrypt::Engine.generate_salt(options[:cost])))
156
+ Password.new(BCrypt::Engine.hash_secret(secret, BCrypt::Engine.generate_salt(options[:cost]), options[:cost]))
127
157
  end
128
158
  end
129
159
 
@@ -1,10 +1,10 @@
1
- require File.join(File.dirname(__FILE__), "..", "spec_helper")
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
2
2
 
3
3
  context "The BCrypt engine" do
4
4
  specify "should calculate the optimal cost factor to fit in a specific time" do
5
5
  first = BCrypt::Engine.calibrate(100)
6
- second = BCrypt::Engine.calibrate(300)
7
- second.should >(first + 1)
6
+ second = BCrypt::Engine.calibrate(400)
7
+ second.should > first
8
8
  end
9
9
  end
10
10
 
@@ -27,9 +27,23 @@ context "Generating BCrypt salts" do
27
27
  end
28
28
  end
29
29
 
30
+ context "Autodetecting of salt cost" do
31
+
32
+ specify "should work" do
33
+ BCrypt::Engine.autodetect_cost("$2a$08$hRx2IVeHNsTSYYtUWn61Ou").should == 8
34
+ BCrypt::Engine.autodetect_cost("$2a$05$XKd1bMnLgUnc87qvbAaCUu").should == 5
35
+ BCrypt::Engine.autodetect_cost("$2a$13$Lni.CZ6z5A7344POTFBBV.").should == 13
36
+ end
37
+
38
+ end
39
+
30
40
  context "Generating BCrypt hashes" do
31
41
 
32
- setup do
42
+ class MyInvalidSecret
43
+ undef to_s
44
+ end
45
+
46
+ before :each do
33
47
  @salt = BCrypt::Engine.generate_salt(4)
34
48
  @password = "woo"
35
49
  end
@@ -43,10 +57,15 @@ context "Generating BCrypt hashes" do
43
57
  end
44
58
 
45
59
  specify "should raise an InvalidSecret error if the secret is invalid" do
60
+ lambda { BCrypt::Engine.hash_secret(MyInvalidSecret.new, @salt) }.should raise_error(BCrypt::Errors::InvalidSecret)
46
61
  lambda { BCrypt::Engine.hash_secret(nil, @salt) }.should_not raise_error(BCrypt::Errors::InvalidSecret)
47
62
  lambda { BCrypt::Engine.hash_secret(false, @salt) }.should_not raise_error(BCrypt::Errors::InvalidSecret)
48
63
  end
49
64
 
65
+ specify "should call #to_s on the secret and use the return value as the actual secret data" do
66
+ BCrypt::Engine.hash_secret(false, @salt).should == BCrypt::Engine.hash_secret("false", @salt)
67
+ end
68
+
50
69
  specify "should be interoperable with other implementations" do
51
70
  # test vectors from the OpenWall implementation <http://www.openwall.com/crypt/>
52
71
  test_vectors = [