bcrypt:hashpw runs quite slow
simonxuhao opened this issue · comments
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".
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.
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.
@Nestor5 you can make bcrypt twice faster with -O2 optimization.
Like this: egobrain@c59a4bc