Commit 8caa1cce2f45c862ce127c2cbdeea0490b55d57f
When on Ruby 1.9, unlock the global interpreter lock while calculating 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>Hongli Lai (Phusion) authored on 8/5/2009, 7:21:03 AM
Coda Hale committed on 8/12/2009, 9:55:36 PM
Parent: 8f7acf49d55b45dbc448f40547d9087762263ddb
Files changed
Rakefile | changed |
ext/bcrypt.c | changed |
ext/bcrypt_ext.c | changed |
ext/bcrypt.h | added |
Rakefile | ||
---|---|---|
@@ -113,9 +113,9 @@ | ||
113 | 113 | desc "Run a set of benchmarks on the compiled extension." |
114 | 114 | task :benchmark do |
115 | 115 | TESTS = 100 |
116 | 116 | TEST_PWD = "this is a test" |
117 | - require "lib/bcrypt" | |
117 | + require File.expand_path(File.join(File.dirname(__FILE__), "lib", "bcrypt")) | |
118 | 118 | Benchmark.bmbm do |results| |
119 | 119 | 4.upto(10) do |n| |
120 | 120 | results.report("cost #{n}:") { TESTS.times { BCrypt::Password.create(TEST_PWD, :cost => n) } } |
121 | 121 | end |
ext/bcrypt.c | ||
---|---|---|
@@ -1,7 +1,12 @@ | ||
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, |
7 | 12 | * to remove the code's dependency on arc4random(), which isn't |
@@ -61,29 +66,19 @@ | ||
61 | 66 | |
62 | 67 | |
63 | 68 | |
64 | 69 | |
70 | + | |
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 | - | |
72 | - | |
73 | - | |
74 | - | |
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 | |
89 | 84 | const static u_int8_t index_64[128] = { |
@@ -154,24 +149,24 @@ | ||
154 | 149 | Since versions may change. Keeping this here |
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; |
177 | 172 | u_int16_t j; |
@@ -184,10 +179,9 @@ | ||
184 | 179 | /* Discard "$" identifier */ |
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 */ |
193 | 187 | if (salt[1] != '$') { |
@@ -197,9 +191,9 @@ | ||
197 | 191 | minor = salt[1]; |
198 | 192 | salt++; |
199 | 193 | break; |
200 | 194 | default: |
201 | - return error; | |
195 | + return NULL; | |
202 | 196 | } |
203 | 197 | } else |
204 | 198 | minor = 0; |
205 | 199 | |
@@ -207,23 +201,23 @@ | ||
207 | 201 | salt += 2; |
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); |
229 | 223 | salt_len = BCRYPT_MAXSALT; |
@@ -258,20 +252,20 @@ | ||
258 | 252 | } |
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 |
277 | 271 | encode_base64(u_int8_t *buffer, u_int8_t *data, u_int16_t len) |
ext/bcrypt_ext.c | ||
---|---|---|
@@ -1,33 +1,78 @@ | ||
1 | 1 | |
2 | - | |
2 | + | |
3 | 3 | |
4 | -char *bcrypt_gensalt(u_int8_t, u_int8_t *); | |
5 | -char *bcrypt(const char *, const char *); | |
4 | +static VALUE mBCrypt; | |
5 | +static VALUE cBCryptEngine; | |
6 | 6 | |
7 | -VALUE mBCrypt; | |
8 | -VALUE cBCryptEngine; | |
9 | - | |
10 | 7 | /* Define RSTRING_PTR for Ruby 1.8.5, ruby-core's idea of a point release is |
11 | 8 | insane. */ |
12 | 9 | |
13 | 10 | |
14 | 11 | |
15 | 12 | |
13 | + | |
14 | + | |
15 | + | |
16 | + | |
17 | + | |
18 | + | |
19 | + /* When on Ruby 1.9+, we will want to unlock the GIL while performing | |
20 | + * expensive calculations, for greater concurrency. | |
21 | + */ | |
22 | + | |
23 | + typedef struct { | |
24 | + char *output; | |
25 | + const char *key; | |
26 | + const char *salt; | |
27 | + } BCryptArguments; | |
28 | + | |
29 | + static VALUE bcrypt_wrapper(void *_args) { | |
30 | + BCryptArguments *args = (BCryptArguments *)_args; | |
31 | + return (VALUE)bcrypt(args->output, args->key, args->salt); | |
32 | + } | |
33 | + | |
34 | + | |
35 | + | |
16 | 36 | /* Given a logarithmic cost parameter, generates a salt for use with +bc_crypt+. |
17 | 37 | */ |
18 | 38 | static VALUE bc_salt(VALUE self, VALUE cost, VALUE seed) { |
19 | - return rb_str_new2((char *)bcrypt_gensalt(NUM2INT(cost), (u_int8_t *)RSTRING_PTR(seed))); | |
39 | + int icost = NUM2INT(cost); | |
40 | + char salt[BCRYPT_SALT_OUTPUT_SIZE]; | |
41 | + | |
42 | + bcrypt_gensalt(salt, icost, (u_int8_t *)RSTRING_PTR(seed)); | |
43 | + return rb_str_new2(salt); | |
20 | 44 | } |
21 | 45 | |
22 | 46 | /* Given a secret and a salt, generates a salted hash (which you can then store safely). |
23 | 47 | */ |
24 | 48 | static VALUE bc_crypt(VALUE self, VALUE key, VALUE salt) { |
25 | 49 | const char * safeguarded = RSTRING_PTR(key) ? RSTRING_PTR(key) : ""; |
26 | - return rb_str_new2((char *)bcrypt(safeguarded, (char *)RSTRING_PTR(salt))); | |
50 | + char output[BCRYPT_OUTPUT_SIZE]; | |
51 | + | |
52 | + | |
53 | + BCryptArguments args; | |
54 | + VALUE ret; | |
55 | + | |
56 | + args.output = output; | |
57 | + args.key = safeguarded; | |
58 | + args.salt = RSTRING_PTR(salt); | |
59 | + ret = rb_thread_blocking_region(bcrypt_wrapper, &args, RUBY_UBF_IO, 0); | |
60 | + if (ret != (VALUE) 0) { | |
61 | + return rb_str_new2(output); | |
62 | + } else { | |
63 | + return Qnil; | |
64 | + } | |
65 | + | |
66 | + if (bcrypt(output, safeguarded, (char *)RSTRING_PTR(salt)) != NULL) { | |
67 | + return rb_str_new2(output); | |
68 | + } else { | |
69 | + return Qnil; | |
70 | + } | |
71 | + | |
27 | 72 | } |
28 | 73 | |
29 | -/* Create the BCrypt and BCrypt::Internals modules, and populate them with methods. */ | |
74 | +/* Create the BCrypt and BCrypt::Engine modules, and populate them with methods. */ | |
30 | 75 | void Init_bcrypt_ext(){ |
31 | 76 | mBCrypt = rb_define_module("BCrypt"); |
32 | 77 | cBCryptEngine = rb_define_class_under(mBCrypt, "Engine", rb_cObject); |
33 | 78 |
ext/bcrypt.h | ||
---|---|---|
@@ -1,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 | + | |
32 | + | |
33 | + | |
34 | + | |
35 | + | |
36 | + | |
37 | + | |
38 | + | |
39 | + | |
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 | + |
Built with git-ssb-web