blob: e1ca2f43d85243d30f76646df8f942091d074514 [file] [log] [blame]
%% @author Hunter Morris <huntermorris@gmail.com>
%% @copyright 2008 Hunter Morris
%%
%% @doc Wrapper around the OpenBSD Blowfish password hashing algorithm, as
%% described in "A Future-Adaptable Password Scheme" by Niels Provos and
%% David Mazieres: http://www.openbsd.org/papers/bcrypt-paper.ps
%% @end
%%
%% Permission to use, copy, modify, and distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(bcrypt).
-author('Hunter Morris <huntermorris@gmail.com>').
-behaviour(gen_server).
-export([init/1, handle_request/2]).
%% API
-export([start_link/1, stop/1]).
-export([gen_salt/1, gen_salt/2]).
-export([hashpw/3]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
code_change/3]).
-define(CMD_SALT, 0).
-define(CMD_HASHPW, 1).
-record(state, {port}).
-define(BCRYPT_ERROR(F, D), error_logger:error_msg(F, D)).
-define(BCRYPT_WARNING(F, D), error_logger:warning_msg(F, D)).
-define(DEFAULT_LOG_ROUNDS, 12).
-define(MAX_LOG_ROUNDS(L), L < 32).
-define(MIN_LOG_ROUNDS(L), L > 3).
%%====================================================================
%% API
%%====================================================================
start_link(Filename) ->
gen_server:start_link(?MODULE, [{filename, Filename}], []).
stop(Pid) when is_pid(Pid) ->
gen_server:call(Pid, stop).
gen_salt(Pid) when is_pid(Pid) ->
gen_salt(Pid, ?DEFAULT_LOG_ROUNDS).
gen_salt(Pid, LogRounds) when is_pid(Pid), is_integer(LogRounds),
?MAX_LOG_ROUNDS(LogRounds),
?MIN_LOG_ROUNDS(LogRounds) ->
R = crypto:rand_bytes(16),
gen_server:call(Pid, {encode_salt, R, LogRounds}).
hashpw(Pid, Password, Salt) when is_pid(Pid) ->
gen_server:call(Pid, {hashpw, Password, Salt}).
%%====================================================================
%% gen_server callbacks
%%====================================================================
init(L) ->
Filename = proplists:get_value(filename, L),
case file:read_file_info(Filename) of
{ok, _Info} ->
Port = open_port({spawn, Filename}, [{packet, 2}, binary, exit_status]),
{ok, #state{port=Port}};
{error, Reason} ->
?BCRYPT_ERROR("Can't open file ~p: ~p", [Filename, Reason]),
error
end.
terminate(_Reason, #state{port=Port}) ->
catch port_close(Port),
ok.
handle_call({encode_salt, R, LogRounds}, From, State) ->
Port = State#state.port,
Data = term_to_binary({?CMD_SALT, From, {R, LogRounds}}),
port_command(Port, Data),
{noreply, State};
handle_call({hashpw, Password, Salt}, From, State) ->
Port = State#state.port,
Data = term_to_binary({?CMD_HASHPW, From, {Password, Salt}}),
port_command(Port, Data),
{noreply, State};
handle_call(stop, _From, State) ->
{stop, normal, ok, State};
handle_call(_Request, _From, State) ->
{reply, bad_request, State}.
handle_info({Port, {data, Data}}, #state{port=Port}=State) ->
case binary_to_term(Data) of
{Cmd, To, Reply} when Cmd == ?CMD_SALT; Cmd == ?CMD_HASHPW ->
gen_server:reply(To, Reply);
Err ->
?BCRYPT_ERROR("Got invalid reply from ~p: ~p", [Port, Err])
end,
{noreply, State};
handle_info({Port, {exit_status, Status}}, #state{port=Port}=State) ->
%% Rely on whomever is supervising this process to restart.
?BCRYPT_WARNING("Port died: ~p", [Status]),
{stop, port_died, State};
handle_info(Msg, State) ->
?BCRYPT_WARNING("Got unexpected message: ~p", [Msg]),
{noreply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.