bcrypt-ruby 2.0.5 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.

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 = [