diff --git a/Makefile b/Makefile index aa3e984..d0c840b 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,10 @@ REBAR=rebar3 compile: $(REBAR) compile | sed -e 's|_build/default/lib/enacl/||g' +.PHONE: console +console: compile + $(REBAR) shell + .PHONY: clean clean: $(REBAR) clean diff --git a/c_src/enacl_nif.c b/c_src/enacl_nif.c index e78f058..191f2d5 100644 --- a/c_src/enacl_nif.c +++ b/c_src/enacl_nif.c @@ -4,6 +4,9 @@ #include +#define ATOM_OK "ok" +#define ATOM_ERROR "error" + /* Errors */ static ERL_NIF_TERM nacl_error_tuple(ErlNifEnv *env, char *error_atom) { @@ -13,8 +16,7 @@ ERL_NIF_TERM nacl_error_tuple(ErlNifEnv *env, char *error_atom) { /* Initialization */ static int enif_crypto_load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info) { - sodium_init(); - return 0; + return sodium_init(); } /* Low-level functions (Hashing, String Equality, ...) */ @@ -1198,6 +1200,50 @@ ERL_NIF_TERM enif_scramble_block_16(ErlNifEnv *env, int argc, ERL_NIF_TERM const return enif_make_binary(env, &out); } +static +ERL_NIF_TERM enif_crypto_pwhash(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + ErlNifBinary h, p, s; + + // Validate the arguments + if( (argc != 2) || + (!enif_inspect_iolist_as_binary(env, argv[0], &p)) || + (!enif_inspect_binary(env, argv[1], &s)) ) { + return enif_make_badarg(env); + } + + // Check Salt size + if(s.size != crypto_pwhash_SALTBYTES) { + return nacl_error_tuple(env, "invalid_salt_size"); + } + + // Allocate memory for return binary + if( !enif_alloc_binary(crypto_box_SEEDBYTES, &h) ) { + return nacl_error_tuple(env, "alloc_failed"); + } + + if( crypto_pwhash(h.data, h.size, p.data, p.size, s.data, + crypto_pwhash_OPSLIMIT_INTERACTIVE, crypto_pwhash_MEMLIMIT_INTERACTIVE, crypto_pwhash_ALG_DEFAULT) != 0) { + /* out of memory */ + enif_release_binary(&h); + return nacl_error_tuple(env, "out_of_memory"); + } + + ERL_NIF_TERM ok = enif_make_atom(env, ATOM_OK); + ERL_NIF_TERM ret = enif_make_binary(env, &h); + + return enif_make_tuple2(env, ok, ret); +} + +static +ERL_NIF_TERM enif_crypto_pwhash_str(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + return nacl_error_tuple(env, "not_implemented"); +} + +static +ERL_NIF_TERM enif_crypto_pwhash_str_verify(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + return nacl_error_tuple(env, "not_implemented"); +} + /* Tie the knot to the Erlang world */ static ErlNifFunc nif_funcs[] = { {"crypto_box_NONCEBYTES", 0, enif_crypto_box_NONCEBYTES}, @@ -1280,6 +1326,10 @@ static ErlNifFunc nif_funcs[] = { {"crypto_verify_32", 2, enif_crypto_verify_32}, {"sodium_memzero", 1, enif_sodium_memzero}, + {"crypto_pwhash", 2, enif_crypto_pwhash}, + {"crypto_pwhash_str", 1, enif_crypto_pwhash_str}, + {"crypto_pwhash_str_verify", 2, enif_crypto_pwhash_str_verify}, + {"crypto_curve25519_scalarmult", 2, enif_crypto_curve25519_scalarmult}, {"crypto_sign_ed25519_keypair", 0, enif_crypto_sign_ed25519_keypair}, diff --git a/src/enacl.erl b/src/enacl.erl index 04a3572..eb45248 100644 --- a/src/enacl.erl +++ b/src/enacl.erl @@ -106,6 +106,13 @@ kx_session_key_size/0 ]). +%% Password Hashing - Argon2 Algorithm +-export([ + pwhash/2, + pwhash_str/1, + pwhash_str_verify/2 +]). + %% Libsodium specific functions (which are also part of the "undocumented" interface to NaCl -export([ randombytes/1 @@ -249,6 +256,32 @@ unsafe_memzero(X) when is_binary(X) -> unsafe_memzero(_) -> error(badarg). + +%% @doc pwhash/2 hash a password +%% +%% This function generates a fixed size salted hash of a user defined password. +%% @end +-spec pwhash(iodata(), binary()) -> {ok, binary()} | {error, term()}. +pwhash(Password, Salt) -> + enacl_nif:crypto_pwhash(Password, Salt). + +%% @doc pwhash_str_verify/2 generates a ASCII encoded hash of a password +%% +%% This function generates a fixed size, salted, ASCII encoded hash of a user defined password. +%% @end +-spec pwhash_str(iodata()) -> {ok, iodata()} | {error, term()}. +pwhash_str(Password) -> + enacl_nif:crypto_pwhash_str(Password). + +%% @doc pwhash_str_verify/2 compares a password with a hash +%% +%% This function verifies that the hash is generated from the password. The +%% function returns true if the verifcate succeeds, false otherwise +%% @end +-spec pwhash_str_verify(binary(), iodata()) -> boolean(). +pwhash_str_verify(HashPassword, Password) -> + enacl_nif:crypto_pwhash_str_verify(HashPassword, Password). + %% Public Key Crypto %% --------------------- %% @doc box_keypair/0 creates a new Public/Secret keypair. @@ -913,6 +946,8 @@ kx_public_key_size() -> kx_secret_key_size() -> enacl_nif:crypto_kx_SECRETKEYBYTES(). + + %% Obtaining random bytes %% @doc randombytes/1 produces a stream of random bytes of the given size diff --git a/src/enacl_nif.erl b/src/enacl_nif.erl index 71e7cfa..a2e1908 100644 --- a/src/enacl_nif.erl +++ b/src/enacl_nif.erl @@ -122,6 +122,13 @@ sodium_memzero/1 ]). +%% Password Hashing - Argon2 Algorithm +-export([ + crypto_pwhash/2, + crypto_pwhash_str/1, + crypto_pwhash_str_verify/2 +]). + %% Access to the RNG -export([ randombytes/1 @@ -146,6 +153,10 @@ init() -> SoName = filename:join(Dir, atom_to_list(?MODULE)), erlang:load_nif(SoName, 0). +crypto_pwhash(Password, Salt) -> erlang:nif_error(nif_not_loaded). +crypto_pwhash_str(Password) -> erlang:nif_error(nif_not_loaded). +crypto_pwhash_str_verify(HashedPassword, Password) -> erlang:nif_error(nif_not_loaded). + crypto_box_NONCEBYTES() -> erlang:nif_error(nif_not_loaded). crypto_box_ZEROBYTES() -> erlang:nif_error(nif_not_loaded). crypto_box_BOXZEROBYTES() -> erlang:nif_error(nif_not_loaded).