Permalink
Browse files

When on Ruby 1.9, unlock the global interpreter lock while calculatin…

…g bcrypt hashes, for greater concurrency.

The bcrypt sources have been modified and made reentrant so that they
don't cause any problems when accessed by multiple threads concurrently.

Signed-off-by: Coda Hale <coda.hale@gmail.com>
  • Loading branch information...
1 parent 8f7acf4 commit 8caa1cce2f45c862ce127c2cbdeea0490b55d57f @FooBarWidget FooBarWidget committed with Aug 5, 2009
Showing with 145 additions and 41 deletions.
  1. +1 −1 Rakefile
  2. +25 −31 ext/bcrypt.c
  3. +65 −0 ext/bcrypt.h
  4. +54 −9 ext/bcrypt_ext.c
View
@@ -114,7 +114,7 @@ desc "Run a set of benchmarks on the compiled extension."
task :benchmark do
TESTS = 100
TEST_PWD = "this is a test"
- require "lib/bcrypt"
+ require File.expand_path(File.join(File.dirname(__FILE__), "lib", "bcrypt"))
Benchmark.bmbm do |results|
4.upto(10) do |n|
results.report("cost #{n}:") { TESTS.times { BCrypt::Password.create(TEST_PWD, :cost => n) } }
View
@@ -1,6 +1,11 @@
/* $OpenBSD: bcrypt.c,v 1.22 2007/02/20 01:44:16 ray Exp $ */
/*
+ * Modified by <hongli@phusion.nl> on 2009-08-05:
+ *
+ * - Got rid of the global variables; they're not thread-safe.
+ * Modified the functions to accept local buffers instead.
+ *
* Modified by <coda.hale@gmail.com> on 2007-02-27:
*
* - Changed bcrypt_gensalt to accept a random seed as a parameter,
@@ -62,27 +67,17 @@
#include <sys/types.h>
#include <string.h>
#include "blf.h"
+#include "bcrypt.h"
/* This implementation is adaptable to current computing power.
* You can have up to 2^31 rounds which should be enough for some
* time to come.
*/
-#define BCRYPT_VERSION '2'
-#define BCRYPT_MAXSALT 16 /* Precomputation is just so nice */
-#define BCRYPT_BLOCKS 6 /* Ciphertext blocks */
-#define BCRYPT_MINROUNDS 16 /* we have log2(rounds) in salt */
-
-char *bcrypt_gensalt(u_int8_t, u_int8_t *);
-
static void encode_salt(char *, u_int8_t *, u_int16_t, u_int8_t);
static void encode_base64(u_int8_t *, u_int8_t *, u_int16_t);
static void decode_base64(u_int8_t *, u_int16_t, u_int8_t *);
-static char encrypted[_PASSWORD_LEN];
-static char gsalt[7 + (BCRYPT_MAXSALT * 4 + 2) / 3 + 1];
-static char error[] = ":";
-
const static u_int8_t Base64Code[] =
"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
@@ -155,22 +150,22 @@ encode_salt(char *salt, u_int8_t *csalt, u_int16_t clen, u_int8_t logr)
seems sensible.
*/
-char *
-bcrypt_gensalt(u_int8_t log_rounds, u_int8_t *rseed)
+char *
+bcrypt_gensalt(char *output, u_int8_t log_rounds, u_int8_t *rseed)
{
if (log_rounds < 4)
log_rounds = 4;
else if (log_rounds > 31)
log_rounds = 31;
- encode_salt(gsalt, rseed, BCRYPT_MAXSALT, log_rounds);
- return gsalt;
+ encode_salt(output, rseed, BCRYPT_MAXSALT, log_rounds);
+ return output;
}
/* We handle $Vers$log2(NumRounds)$salt+passwd$
i.e. $2$04$iwouldntknowwhattosayetKdJ6iFtacBqJdKe6aW7ou */
char *
-bcrypt(const char *key, const char *salt)
+bcrypt(char *output, const char *key, const char *salt)
{
blf_ctx state;
u_int32_t rounds, i, k;
@@ -185,8 +180,7 @@ bcrypt(const char *key, const char *salt)
salt++;
if (*salt > BCRYPT_VERSION) {
- /* How do I handle errors ? Return ':' */
- return error;
+ return NULL;
}
/* Check for minor versions */
@@ -198,7 +192,7 @@ bcrypt(const char *key, const char *salt)
salt++;
break;
default:
- return error;
+ return NULL;
}
} else
minor = 0;
@@ -208,21 +202,21 @@ bcrypt(const char *key, const char *salt)
if (salt[2] != '$')
/* Out of sync with passwd entry */
- return error;
+ return NULL;
/* Computer power doesn't increase linear, 2^x should be fine */
n = atoi(salt);
if (n > 31 || n < 0)
- return error;
+ return NULL;
logr = (u_int8_t)n;
if ((rounds = (u_int32_t) 1 << logr) < BCRYPT_MINROUNDS)
- return error;
+ return NULL;
/* Discard num rounds + "$" identifier */
salt += 3;
if (strlen(salt) * 3 / 4 < BCRYPT_MAXSALT)
- return error;
+ return NULL;
/* We dont want the base64 salt but the raw data */
decode_base64(csalt, BCRYPT_MAXSALT, (u_int8_t *) salt);
@@ -259,18 +253,18 @@ bcrypt(const char *key, const char *salt)
i = 0;
- encrypted[i++] = '$';
- encrypted[i++] = BCRYPT_VERSION;
+ output[i++] = '$';
+ output[i++] = BCRYPT_VERSION;
if (minor)
- encrypted[i++] = minor;
- encrypted[i++] = '$';
+ output[i++] = minor;
+ output[i++] = '$';
- snprintf(encrypted + i, 4, "%2.2u$", logr);
+ snprintf(output + i, 4, "%2.2u$", logr);
- encode_base64((u_int8_t *) encrypted + i + 3, csalt, BCRYPT_MAXSALT);
- encode_base64((u_int8_t *) encrypted + strlen(encrypted), ciphertext,
+ encode_base64((u_int8_t *) output + i + 3, csalt, BCRYPT_MAXSALT);
+ encode_base64((u_int8_t *) output + strlen(output), ciphertext,
4 * BCRYPT_BLOCKS - 1);
- return encrypted;
+ return output;
}
static void
View
@@ -0,0 +1,65 @@
+/*
+ * Copyright 1997 Niels Provos <provos@physnet.uni-hamburg.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by Niels Provos.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _BCRYPT_H_
+#define _BCRYPT_H_
+
+#define BCRYPT_VERSION '2'
+#define BCRYPT_MAXSALT 16 /* Precomputation is just so nice */
+#define BCRYPT_BLOCKS 6 /* Ciphertext blocks */
+#define BCRYPT_MINROUNDS 16 /* we have log2(rounds) in salt */
+#define BCRYPT_SALT_OUTPUT_SIZE (7 + (BCRYPT_MAXSALT * 4 + 2) / 3 + 1)
+#define BCRYPT_OUTPUT_SIZE 128
+
+/*
+ * Given a logarithmic cost parameter, generates a salt for use with bcrypt().
+ *
+ * output: the computed salt will be stored here. This buffer must be
+ * at least BCRYPT_SALT_OUTPUT_SIZE bytes. The result will be
+ * null-terminated.
+ * log_rounds: the logarithmic cost.
+ * rseed: a seed of BCRYPT_MAXSALT bytes. Should be obtained from a
+ * cryptographically secure random source.
+ * Returns: output
+ */
+char *bcrypt_gensalt(char *output, u_int8_t log_rounds, u_int8_t *rseed);
+
+/*
+ * Given a secret and a salt, generates a salted hash (which you can then store safely).
+ *
+ * output: the computed salted hash will be stored here. This buffer must
+ * be at least BCRYPT_OUTPUT_SIZE bytes, and will become null-terminated.
+ * key: A null-terminated secret.
+ * salt: The salt, as generated by bcrypt_gensalt().
+ * Returns: output on success, NULL on error.
+ */
+char *bcrypt(char *output, const char *key, const char *salt);
+
+#endif /* _BCRYPT_H_ */
View
@@ -1,32 +1,77 @@
#include "ruby.h"
-#include "blf.h"
+#include "bcrypt.h"
-char *bcrypt_gensalt(u_int8_t, u_int8_t *);
-char *bcrypt(const char *, const char *);
-
-VALUE mBCrypt;
-VALUE cBCryptEngine;
+static VALUE mBCrypt;
+static VALUE cBCryptEngine;
/* Define RSTRING_PTR for Ruby 1.8.5, ruby-core's idea of a point release is
insane. */
#ifndef RSTRING_PTR
# define RSTRING_PTR(s) (RSTRING(s)->ptr)
#endif
+#ifdef RUBY_VM
+# define RUBY_1_9
+#endif
+
+#ifdef RUBY_1_9
+
+ /* When on Ruby 1.9+, we will want to unlock the GIL while performing
+ * expensive calculations, for greater concurrency.
+ */
+
+ typedef struct {
+ char *output;
+ const char *key;
+ const char *salt;
+ } BCryptArguments;
+
+ static VALUE bcrypt_wrapper(void *_args) {
+ BCryptArguments *args = (BCryptArguments *)_args;
+ return (VALUE)bcrypt(args->output, args->key, args->salt);
+ }
+
+#endif /* RUBY_1_9 */
+
/* Given a logarithmic cost parameter, generates a salt for use with +bc_crypt+.
*/
static VALUE bc_salt(VALUE self, VALUE cost, VALUE seed) {
- return rb_str_new2((char *)bcrypt_gensalt(NUM2INT(cost), (u_int8_t *)RSTRING_PTR(seed)));
+ int icost = NUM2INT(cost);
+ char salt[BCRYPT_SALT_OUTPUT_SIZE];
+
+ bcrypt_gensalt(salt, icost, (u_int8_t *)RSTRING_PTR(seed));
+ return rb_str_new2(salt);
}
/* Given a secret and a salt, generates a salted hash (which you can then store safely).
*/
static VALUE bc_crypt(VALUE self, VALUE key, VALUE salt) {
const char * safeguarded = RSTRING_PTR(key) ? RSTRING_PTR(key) : "";
- return rb_str_new2((char *)bcrypt(safeguarded, (char *)RSTRING_PTR(salt)));
+ char output[BCRYPT_OUTPUT_SIZE];
+
+ #ifdef RUBY_1_9
+ BCryptArguments args;
+ VALUE ret;
+
+ args.output = output;
+ args.key = safeguarded;
+ args.salt = RSTRING_PTR(salt);
+ ret = rb_thread_blocking_region(bcrypt_wrapper, &args, RUBY_UBF_IO, 0);
+ if (ret != (VALUE) 0) {
+ return rb_str_new2(output);
+ } else {
+ return Qnil;
+ }
+ #else
+ if (bcrypt(output, safeguarded, (char *)RSTRING_PTR(salt)) != NULL) {
+ return rb_str_new2(output);
+ } else {
+ return Qnil;
+ }
+ #endif
}
-/* Create the BCrypt and BCrypt::Internals modules, and populate them with methods. */
+/* Create the BCrypt and BCrypt::Engine modules, and populate them with methods. */
void Init_bcrypt_ext(){
mBCrypt = rb_define_module("BCrypt");
cBCryptEngine = rb_define_class_under(mBCrypt, "Engine", rb_cObject);

0 comments on commit 8caa1cc

Please sign in to comment.