Skip to content

Commit

Permalink
Hash passwords asynchronously in NIF
Browse files Browse the repository at this point in the history
  • Loading branch information
Hunter Morris committed Mar 12, 2012
1 parent 5751fea commit bc8fb23
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 32 deletions.
56 changes: 41 additions & 15 deletions c_src/bcrypt_nif.c
Expand Up @@ -14,6 +14,7 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Expand All @@ -40,15 +41,32 @@ task_t* alloc_task(task_type_t type)
return task;
}

task_t* alloc_init_task(task_type_t type, ERL_NIF_TERM ref)
task_t* alloc_init_task(task_type_t type, ERL_NIF_TERM ref, ErlNifPid pid, int num_orig_terms, const ERL_NIF_TERM orig_terms[])
{
task_t* task = alloc_task(type);
task->pid = pid;
task->env = enif_alloc_env();
if (task->env == NULL) {
free_task(task);
return NULL;
}

if (type == HASH) {
assert(num_orig_terms == 2);
if (!enif_inspect_iolist_as_binary(
task->env, enif_make_copy(task->env, orig_terms[0]),
&task->data.hash.salt)) {
free_task(task);
return NULL;
}
if (!enif_inspect_iolist_as_binary(
task->env, enif_make_copy(task->env, orig_terms[1]),
&task->data.hash.password)) {
free_task(task);
return NULL;
}
}

task->ref = enif_make_copy(task->env, ref);
return task;
}
Expand Down Expand Up @@ -97,6 +115,7 @@ void* async_worker(void* arg)
task = (task_t*)async_queue_pop(ctx->queue);

if (task->type == SHUTDOWN) {
free_task(task);
break;
} else if (task->type == HASH) {
result = hashpw(task);
Expand All @@ -108,8 +127,6 @@ void* async_worker(void* arg)
free_task(task);
}

// Cleanup the shutdown task
free_task(task);
return NULL;
}

Expand Down Expand Up @@ -140,24 +157,33 @@ static ERL_NIF_TERM bcrypt_encode_salt(ErlNifEnv* env, int argc, const ERL_NIF_T

static ERL_NIF_TERM bcrypt_hashpw(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
char pw[1024];
char salt[1024];
char *ret = NULL;
ctx_t *ctx;
task_t *task;
ErlNifPid pid;

(void)memset(&pw, '\0', sizeof(pw));
(void)memset(&salt, '\0', sizeof(salt));
if (argc != 5)
return enif_make_badarg(env);

if (enif_get_string(env, argv[0], pw, sizeof(pw), ERL_NIF_LATIN1) < 1)
bcrypt_privdata_t *priv = (bcrypt_privdata_t*)enif_priv_data(env);

if (!enif_get_resource(env, argv[0], priv->bcrypt_rt, (void**)(&ctx)))
return enif_make_badarg(env);

if (enif_get_string(env, argv[1], salt, sizeof(salt), ERL_NIF_LATIN1) < 1)
if (!enif_is_ref(env, argv[1]))
return enif_make_badarg(env);

if (NULL == (ret = bcrypt(pw, salt)) || 0 == strcmp(ret, ":")) {
if (!enif_get_local_pid(env, argv[2], &pid))
return enif_make_badarg(env);
}

return enif_make_string(env, ret, ERL_NIF_LATIN1);
ERL_NIF_TERM orig_terms[] = { argv[4], argv[3] };
task = alloc_init_task(HASH, argv[1], pid, 2, orig_terms);

if (!task)
return enif_make_badarg(env);

async_queue_push(ctx->queue, task);

return enif_make_atom(env, "ok");
}

static ERL_NIF_TERM bcrypt_create_ctx(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
Expand All @@ -181,7 +207,7 @@ static ERL_NIF_TERM bcrypt_create_ctx(ErlNifEnv* env, int argc, const ERL_NIF_TE
static ErlNifFunc bcrypt_nif_funcs[] =
{
{"encode_salt", 2, bcrypt_encode_salt},
{"hashpw", 2, bcrypt_hashpw},
{"hashpw", 5, bcrypt_hashpw},
{"create_ctx", 0, bcrypt_create_ctx},
};

Expand All @@ -191,7 +217,7 @@ static void bcrypt_rt_dtor(ErlNifEnv* env, void* obj)
task_t *task = alloc_task(SHUTDOWN);
void *result = NULL;

async_queue_push(ctx->queue, (void*)task);
async_queue_push(ctx->queue, task);
enif_thread_join(ctx->tid, &result);
async_queue_destroy(ctx->queue);
enif_thread_opts_destroy(ctx->topts);
Expand Down
6 changes: 1 addition & 5 deletions src/bcrypt.erl
Expand Up @@ -6,7 +6,7 @@
%% API
-export([start/0, stop/0]).
-export([mechanism/0]).
-export([gen_salt/0, gen_salt/1, hashpw/2, create_ctx/0]).
-export([gen_salt/0, gen_salt/1, hashpw/2]).

start() -> application:start(bcrypt).
stop() -> application:stop(bcrypt).
Expand All @@ -18,7 +18,6 @@ mechanism() ->
gen_salt() -> do_gen_salt(mechanism()).
gen_salt(Rounds) -> do_gen_salt(mechanism(), Rounds).
hashpw(Password, Salt) -> do_hashpw(mechanism(), Password, Salt).
create_ctx() -> do_create_ctx(mechanism()).

do_gen_salt(nif) -> bcrypt_nif_worker:gen_salt();
do_gen_salt(port) -> bcrypt_pool:gen_salt().
Expand All @@ -28,6 +27,3 @@ do_gen_salt(port, Rounds) -> bcrypt_pool:gen_salt(Rounds).

do_hashpw(nif, Password, Salt) -> bcrypt_nif_worker:hashpw(Password, Salt);
do_hashpw(port, Password, Salt) -> bcrypt_pool:hashpw(Password, Salt).

do_create_ctx(nif) -> bcrypt_nif_worker:create_ctx();
do_create_ctx(port) -> ok.
10 changes: 7 additions & 3 deletions src/bcrypt_nif.erl
Expand Up @@ -22,7 +22,7 @@

%% API
-export([init/0]).
-export([gen_salt/1, hashpw/2, create_ctx/0]).
-export([gen_salt/1, hashpw/5, create_ctx/0]).

-on_load(init/0).

Expand Down Expand Up @@ -71,10 +71,14 @@ create_ctx() ->
%%--------------------------------------------------------------------
%% @doc Hash the specified password and the salt using the OpenBSD
%% Blowfish password hashing algorithm. Returns the hashed password.
%% @spec hashpw(Password::binary(), Salt::binary()) -> string()
%% @spec hashpw(Ctx::term(),
%% Ref::reference(),
%% Pid::pid(),
%% Password::binary(),
%% Salt::binary()) -> string()
%% @end
%%--------------------------------------------------------------------
hashpw(_Password, _Salt) ->
hashpw(_Ctx, _Ref, _Pid, _Password, _Salt) ->
nif_stub_error(?LINE).

nif_stub_error(Line) ->
Expand Down
24 changes: 15 additions & 9 deletions src/bcrypt_nif_worker.erl
Expand Up @@ -8,13 +8,15 @@
-export([start_link/0]).
-export([gen_salt/0, gen_salt/1]).
-export([hashpw/2]).
-export([create_ctx/0]).

%% gen_server
-export([init/1, code_change/3, terminate/2,
handle_call/3, handle_cast/2, handle_info/2]).

-record(state, {default_log_rounds}).
-record(state, {
default_log_rounds,
context
}).

start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

Expand All @@ -23,23 +25,27 @@ gen_salt(Rounds) ->
gen_server:call(?MODULE, {gen_salt, Rounds}, infinity).
hashpw(Password, Salt) ->
gen_server:call(?MODULE, {hashpw, Password, Salt}, infinity).
create_ctx() ->
gen_server:call(?MODULE, create_ctx, infinity).

init([]) ->
{ok, Default} = application:get_env(bcrypt, default_log_rounds),
{ok, #state{default_log_rounds = Default}}.
Ctx = bcrypt_nif:create_ctx(),
{ok, #state{default_log_rounds = Default, context = Ctx}}.

terminate(shutdown, _) -> ok.

handle_call(gen_salt, _From, #state{default_log_rounds = R} = State) ->
{reply, {ok, bcrypt_nif:gen_salt(R)}, State};
handle_call({gen_salt, R}, _From, State) ->
{reply, {ok, bcrypt_nif:gen_salt(R)}, State};
handle_call({hashpw, Password, Salt}, _From, State) ->
{reply, {ok, bcrypt_nif:hashpw(Password, Salt)}, State};
handle_call(create_ctx, _From, State) ->
{reply, {ok, bcrypt_nif:create_ctx()}, State};
handle_call({hashpw, Password, Salt}, _From, #state{context=Ctx}=State) ->
Ref = make_ref(),
ok = bcrypt_nif:hashpw(Ctx, Ref, self(), Password, Salt),
receive
{ok, Ref, Result} ->
{reply, {ok, Result}, State};
{error, Ref, Result} ->
{reply, {error, Result}, State}
end;
handle_call(Msg, _, _) -> exit({unknown_call, Msg}).
handle_cast(Msg, _) -> exit({unknown_cast, Msg}).
handle_info(Msg, _) -> exit({unknown_info, Msg}).
Expand Down

0 comments on commit bc8fb23

Please sign in to comment.