smarkets / erlang-bcrypt

Erlang wrapper for OpenBSD's Blowfish password hashing code
Other
86 stars 47 forks source link

bcrypt:hashpw runs quite slow #15

Closed simonxuhao closed 8 years ago

simonxuhao commented 8 years ago

In my Ubuntu virtual machine (Intel(R) Xeon(R) CPU E5-2430 0 @ 2.20GHz 16 cores, 64G memory), bcrypt:hashpw runs for more than 1 second. Is this normal? I worry that this is not fast enough for production use when registered users are more than half million.

8> {ok, Salt} = bcrypt:gen_salt(). {ok,"$2a$12$BWhbfsZTZI50EVRmsc2/q."} 9> {ok, Hash} = bcrypt:hashpw("foo", Salt). (more than 1 second) {ok,"$2a$12$BWhbfsZTZI50EVRmsc2/q.emrJQ6J9Av4semi4/X4x11USlhypCIq"} 10> {ok, Hash} =:= bcrypt:hashpw("foo", Hash). (more than 1 second) true

My env:

Ubuntu 14.04.3 LTS Kernel: 3.13.0-74-generic x86_64 Erlang: Package: esl-erlang Priority: extra Section: interpreters Installed-Size: 124643 Maintainer: Erlang Solutions Ltd support@erlang-solutions.com Architecture: amd64 Source: esl-erlang Version: 1:18.2

I starts erlang by "erl -pa ebin -boot start_sasl -s crypto -s bcrypt".

grayj commented 8 years ago

Bcrypt is slow by design, this is how it avoids brute-force attacks. It's also adjustably slow, if computers become 10x faster then you can update the parameters to make it be 10x slower and maintain a similar level of hardening.

Odds are that it won't be a huge problem at 500k users, because that still translates to a very small number of login events (thus bcrypt hashes) per unit time. Any post-authentication request should only be hitting a session key lookup, via Redis or whichever, with very little computation.

If it really ends up being a problem in production, toss it on its own server. Or ten servers. Hashing passwords for login attempts is 100% horizontally scalable, in the rare even that it's a blocking issue.

rbruggem commented 8 years ago

Hi @gularg , I am not familiar with bcrypt, but I took a look at the code to check where the slowness originates. The slowness comes from https://github.com/smarkets/erlang-bcrypt/blob/master/c_src/bcrypt_nif.c#L90.

Timed as follows:

diff --git a/c_src/bcrypt_nif.c b/c_src/bcrypt_nif.c
index c551f6f..9691608 100644
--- a/c_src/bcrypt_nif.c
+++ b/c_src/bcrypt_nif.c
@@ -19,6 +19,8 @@
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include <inttypes.h>
+#include <time.h>

 #include "erl_nif.h"
 #include "erl_blf.h"
@@ -87,6 +89,10 @@ static ERL_NIF_TERM hashpw(task_t* task)
         salt_sz = task->data.hash.salt.size;
     (void)memcpy(&salt, task->data.hash.salt.data, salt_sz);

+    struct timespec ts_start, ts_end;
+    clock_gettime(CLOCK_MONOTONIC, &ts_start);
+    const uint64_t start = (ts_start.tv_sec * 1000000000) + ts_start.tv_nsec;
+
     if (bcrypt(encrypted, password, salt)) {
         return enif_make_tuple3(
             task->env,
@@ -95,6 +101,11 @@ static ERL_NIF_TERM hashpw(task_t* task)
             enif_make_string(task->env, "bcrypt failed", ERL_NIF_LATIN1));
     }

+    clock_gettime(CLOCK_MONOTONIC, &ts_end);
+    const uint64_t end = (ts_end.tv_sec * 1000000000) + ts_end.tv_nsec;
+    const uint64_t interval = end - start;
+    printf("interval %"PRIu64"ns\n", interval);
+
     return enif_make_tuple3(
         task->env,
         enif_make_atom(task->env, "ok"),

This, together with bcrypt articles I have read in the meantime, leads me to confirm what @grayj has said.

egobrain commented 7 years ago

@nestor5 you can make bcrypt twice faster with -O2 optimization. Like this: https://github.com/egobrain/erlang-bcrypt/commit/c59a4bc4a21cd49f786a7fdc20b2259bb2c635a1