Skip to content

Commit

Permalink
Add support for the SHA3 family
Browse files Browse the repository at this point in the history
  • Loading branch information
NelsonVides committed Feb 18, 2025
1 parent 8be271c commit 713e745
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 11 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ DerivedPassword = fast_pbkdf2:pbkdf2(Hash, Password, Salt, IterationCount)
```
where `Hash` is the underlying hash function chosen as described by
```erlang
-type sha_type() :: crypto:sha1() | crypto:sha2().
-type sha_type() :: crypto:sha1() | crypto:sha2() | crypto:sha3().
```

### Custom `dkLen`
Expand Down
83 changes: 83 additions & 0 deletions c_src/fast_pbkdf2.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,28 @@ typedef struct {
ERL_NIF_TERM atom_sha256;
ERL_NIF_TERM atom_sha384;
ERL_NIF_TERM atom_sha512;
ERL_NIF_TERM atom_sha3_224;
ERL_NIF_TERM atom_sha3_256;
ERL_NIF_TERM atom_sha3_384;
ERL_NIF_TERM atom_sha3_512;
EVP_MD *MD_NAME(sha1);
EVP_MD *MD_NAME(sha224);
EVP_MD *MD_NAME(sha256);
EVP_MD *MD_NAME(sha384);
EVP_MD *MD_NAME(sha512);
EVP_MD *MD_NAME(sha3_224);
EVP_MD *MD_NAME(sha3_256);
EVP_MD *MD_NAME(sha3_384);
EVP_MD *MD_NAME(sha3_512);
ErlNifResourceType *HMAC_CTX_ROUND_RES(sha1);
ErlNifResourceType *HMAC_CTX_ROUND_RES(sha224);
ErlNifResourceType *HMAC_CTX_ROUND_RES(sha256);
ErlNifResourceType *HMAC_CTX_ROUND_RES(sha384);
ErlNifResourceType *HMAC_CTX_ROUND_RES(sha512);
ErlNifResourceType *HMAC_CTX_ROUND_RES(sha3_224);
ErlNifResourceType *HMAC_CTX_ROUND_RES(sha3_256);
ErlNifResourceType *HMAC_CTX_ROUND_RES(sha3_384);
ErlNifResourceType *HMAC_CTX_ROUND_RES(sha3_512);
} pbkdf2_st;

static inline void write32_be(uint32_t n, uint8_t out[4]) {
Expand Down Expand Up @@ -315,6 +327,10 @@ typedef struct {
* SHA-256 | 64 | 32
* SHA-384 | 128 | 48^
* SHA-512 | 128 | 64
* SHA3-224 | 144 | 28
* SHA3-256 | 136 | 32
* SHA3-384 | 104 | 48
* SHA3-512 | 72 | 64
*/

/* On the following machine:
Expand All @@ -329,6 +345,8 @@ typedef struct {
* SHA1/3350-iterations 1.04 K 964.86 μs ±17.04% 911.91 μs 1728.66 μs
* SHA256/2100-iterations 1.01 K 988.12 μs ±15.21% 938.68 μs 1669.52 μs
* SHA512/1600-iterations 1.02 K 983.10 μs ±15.88% 933.49 μs 1668.32 μs
* SHA3_256/1060-iterations 1.04 K 958.75 μs ±14.25% 918.95 μs 1534.53 μs
* SHA3_512/1060-iterations 1.01 K 990.36 μs ±13.68% 957.71 μs 1547.29 μs
*
* Also, we want to report percentage every 5% (TIMESLICE_PERCENTAGE).
* We therefore get that a slot in between iterations should take MAX/SLICE iterations in a slot.
Expand All @@ -339,6 +357,10 @@ DECL_PBKDF2(sha224, SHA256_CBLOCK, SHA224_DIGEST_LENGTH, 2100 / SLICE)
DECL_PBKDF2(sha256, SHA256_CBLOCK, SHA256_DIGEST_LENGTH, 2100 / SLICE)
DECL_PBKDF2(sha384, SHA512_CBLOCK, SHA384_DIGEST_LENGTH, 1600 / SLICE)
DECL_PBKDF2(sha512, SHA512_CBLOCK, SHA512_DIGEST_LENGTH, 1600 / SLICE)
DECL_PBKDF2(sha3_224, 144, SHA224_DIGEST_LENGTH, 1060 / SLICE)
DECL_PBKDF2(sha3_256, 136, SHA256_DIGEST_LENGTH, 1060 / SLICE)
DECL_PBKDF2(sha3_384, 104, SHA384_DIGEST_LENGTH, 1080 / SLICE)
DECL_PBKDF2(sha3_512, 72, SHA512_DIGEST_LENGTH, 1080 / SLICE)

static int load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info) {
(void)load_info;
Expand All @@ -351,6 +373,10 @@ static int load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info) {
mod_st->atom_sha256 = enif_make_atom(env, "sha256");
mod_st->atom_sha384 = enif_make_atom(env, "sha384");
mod_st->atom_sha512 = enif_make_atom(env, "sha512");
mod_st->atom_sha3_224 = enif_make_atom(env, "sha3_224");
mod_st->atom_sha3_256 = enif_make_atom(env, "sha3_256");
mod_st->atom_sha3_384 = enif_make_atom(env, "sha3_384");
mod_st->atom_sha3_512 = enif_make_atom(env, "sha3_512");

mod_st->md_sha1 = EVP_MD_fetch(NULL, "SHA1", NULL);
if (NULL == mod_st->md_sha1)
Expand All @@ -367,6 +393,18 @@ static int load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info) {
mod_st->md_sha512 = EVP_MD_fetch(NULL, "SHA512", NULL);
if (NULL == mod_st->md_sha512)
goto cleanup;
mod_st->md_sha3_224 = EVP_MD_fetch(NULL, "SHA3-224", NULL);
if (NULL == mod_st->md_sha3_224)
goto cleanup;
mod_st->md_sha3_256 = EVP_MD_fetch(NULL, "SHA3-256", NULL);
if (NULL == mod_st->md_sha3_256)
goto cleanup;
mod_st->md_sha3_384 = EVP_MD_fetch(NULL, "SHA3-384", NULL);
if (NULL == mod_st->md_sha3_384)
goto cleanup;
mod_st->md_sha3_512 = EVP_MD_fetch(NULL, "SHA3-512", NULL);
if (NULL == mod_st->md_sha3_512)
goto cleanup;

mod_st->HMAC_CTX_ROUND_RES(sha1) = enif_open_resource_type(
env, NULL, HMAC_CTX_ROUND_NAME(sha1), NULL, ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER, NULL);
Expand All @@ -393,6 +431,27 @@ static int load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info) {
if (NULL == mod_st->HMAC_CTX_ROUND_RES(sha512))
goto cleanup;

mod_st->HMAC_CTX_ROUND_RES(sha3_224) =
enif_open_resource_type(env, NULL, HMAC_CTX_ROUND_NAME(sha3_224), NULL,
ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER, NULL);
if (NULL == mod_st->HMAC_CTX_ROUND_RES(sha3_224))
goto cleanup;
mod_st->HMAC_CTX_ROUND_RES(sha3_256) =
enif_open_resource_type(env, NULL, HMAC_CTX_ROUND_NAME(sha3_256), NULL,
ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER, NULL);
if (NULL == mod_st->HMAC_CTX_ROUND_RES(sha3_256))
goto cleanup;
mod_st->HMAC_CTX_ROUND_RES(sha3_384) =
enif_open_resource_type(env, NULL, HMAC_CTX_ROUND_NAME(sha3_384), NULL,
ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER, NULL);
if (NULL == mod_st->HMAC_CTX_ROUND_RES(sha3_384))
goto cleanup;
mod_st->HMAC_CTX_ROUND_RES(sha3_512) =
enif_open_resource_type(env, NULL, HMAC_CTX_ROUND_NAME(sha3_512), NULL,
ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER, NULL);
if (NULL == mod_st->HMAC_CTX_ROUND_RES(sha3_512))
goto cleanup;

*priv_data = (void *)mod_st;

return 0;
Expand All @@ -409,6 +468,14 @@ static int load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info) {
EVP_MD_free(mod_st->md_sha384);
if (mod_st->md_sha512 != NULL)
EVP_MD_free(mod_st->md_sha512);
if (mod_st->md_sha3_224 != NULL)
EVP_MD_free(mod_st->md_sha3_224);
if (mod_st->md_sha3_256 != NULL)
EVP_MD_free(mod_st->md_sha3_256);
if (mod_st->md_sha3_384 != NULL)
EVP_MD_free(mod_st->md_sha3_384);
if (mod_st->md_sha3_512 != NULL)
EVP_MD_free(mod_st->md_sha3_512);
return 1;
}

Expand All @@ -432,6 +499,10 @@ static void unload(ErlNifEnv *env, void *priv) {
EVP_MD_free(mod_st->md_sha256);
EVP_MD_free(mod_st->md_sha384);
EVP_MD_free(mod_st->md_sha512);
EVP_MD_free(mod_st->md_sha3_224);
EVP_MD_free(mod_st->md_sha3_256);
EVP_MD_free(mod_st->md_sha3_384);
EVP_MD_free(mod_st->md_sha3_512);
enif_free(priv);
return;
}
Expand Down Expand Up @@ -477,6 +548,18 @@ static ERL_NIF_TERM pbkdf2_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv
} else if (enif_is_identical(argv[0], mod_st->atom_sha512)) {
return PBKDF2(sha512)(env, password.data, password.size, salt.data, salt.size,
iteration_count, counter);
} else if (enif_is_identical(argv[0], mod_st->atom_sha3_224)) {
return PBKDF2(sha3_224)(env, password.data, password.size, salt.data, salt.size,
iteration_count, counter);
} else if (enif_is_identical(argv[0], mod_st->atom_sha3_256)) {
return PBKDF2(sha3_256)(env, password.data, password.size, salt.data, salt.size,
iteration_count, counter);
} else if (enif_is_identical(argv[0], mod_st->atom_sha3_384)) {
return PBKDF2(sha3_384)(env, password.data, password.size, salt.data, salt.size,
iteration_count, counter);
} else if (enif_is_identical(argv[0], mod_st->atom_sha3_512)) {
return PBKDF2(sha3_512)(env, password.data, password.size, salt.data, salt.size,
iteration_count, counter);
} else {
return mk_error(env, "bad_hash");
}
Expand Down
4 changes: 3 additions & 1 deletion src/fast_pbkdf2.erl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
-on_load(load/0).
-nifs([pbkdf2_block/5]).

-type sha_type() :: crypto:sha1() | crypto:sha2().
%% Taken from unexported crypto:sha3().
-type sha3() :: sha3_224 | sha3_256 | sha3_384 | sha3_512.
-type sha_type() :: crypto:sha1() | crypto:sha2() | sha3().

-export([pbkdf2/4, pbkdf2/5]).

Expand Down
4 changes: 3 additions & 1 deletion test/erl_pbkdf2.erl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

-export([pbkdf2_oneblock/4]).

-type sha_type() :: crypto:sha1() | crypto:sha2().
%% Taken from unexported crypto:sha3().
-type sha3() :: sha3_224 | sha3_256 | sha3_384 | sha3_512.
-type sha_type() :: crypto:sha1() | crypto:sha2() | sha3().

-spec pbkdf2_oneblock(sha_type(), binary(), binary(), non_neg_integer()) -> binary().
pbkdf2_oneblock(Sha, Password, Salt, 1) ->
Expand Down
49 changes: 41 additions & 8 deletions test/pbkdf2_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@
erlang_and_nif_are_equivalent_sha224/1,
erlang_and_nif_are_equivalent_sha256/1,
erlang_and_nif_are_equivalent_sha384/1,
erlang_and_nif_are_equivalent_sha512/1
erlang_and_nif_are_equivalent_sha512/1,
erlang_and_nif_are_equivalent_sha3_224/1,
erlang_and_nif_are_equivalent_sha3_256/1,
erlang_and_nif_are_equivalent_sha3_384/1,
erlang_and_nif_are_equivalent_sha3_512/1
]).
-export([
test_vector_sha1_1/1,
Expand Down Expand Up @@ -49,7 +53,11 @@ groups() ->
erlang_and_nif_are_equivalent_sha224,
erlang_and_nif_are_equivalent_sha256,
erlang_and_nif_are_equivalent_sha384,
erlang_and_nif_are_equivalent_sha512
erlang_and_nif_are_equivalent_sha512,
erlang_and_nif_are_equivalent_sha3_224,
erlang_and_nif_are_equivalent_sha3_256,
erlang_and_nif_are_equivalent_sha3_384,
erlang_and_nif_are_equivalent_sha3_512
]},
{test_vectors, [parallel],
[
Expand Down Expand Up @@ -99,21 +107,33 @@ end_per_testcase(_TestCase, _Config) ->
%%%===================================================================

erlang_and_nif_are_equivalent_sha1(_Config) ->
erlang_and_nif_are_equivalent_(sha).
crypto_and_erlang_and_nif_are_equivalent_(sha).

erlang_and_nif_are_equivalent_sha224(_Config) ->
erlang_and_nif_are_equivalent_(sha224).
crypto_and_erlang_and_nif_are_equivalent_(sha224).

erlang_and_nif_are_equivalent_sha256(_Config) ->
erlang_and_nif_are_equivalent_(sha256).
crypto_and_erlang_and_nif_are_equivalent_(sha256).

erlang_and_nif_are_equivalent_sha384(_Config) ->
erlang_and_nif_are_equivalent_(sha384).
crypto_and_erlang_and_nif_are_equivalent_(sha384).

erlang_and_nif_are_equivalent_sha512(_Config) ->
erlang_and_nif_are_equivalent_(sha512).
crypto_and_erlang_and_nif_are_equivalent_(sha512).

erlang_and_nif_are_equivalent_(Sha) ->
erlang_and_nif_are_equivalent_sha3_224(_Config) ->
erlang_and_nif_are_equivalent_(sha3_224).

erlang_and_nif_are_equivalent_sha3_256(_Config) ->
erlang_and_nif_are_equivalent_(sha3_256).

erlang_and_nif_are_equivalent_sha3_384(_Config) ->
erlang_and_nif_are_equivalent_(sha3_384).

erlang_and_nif_are_equivalent_sha3_512(_Config) ->
erlang_and_nif_are_equivalent_(sha3_512).

crypto_and_erlang_and_nif_are_equivalent_(Sha) ->
Prop = ?FORALL({Pass, Salt, Count},
{binary(), binary(), range(2,20000)},
begin
Expand All @@ -128,6 +148,19 @@ erlang_and_nif_are_equivalent_(Sha) ->
{numtests, 500}, {numworkers, erlang:system_info(schedulers_online)}],
?assert(proper:quickcheck(Prop, Opts)).

erlang_and_nif_are_equivalent_(Sha) ->
Prop = ?FORALL({Pass, Salt, Count},
{binary(), binary(), range(2,20000)},
begin
This = fast_pbkdf2:pbkdf2(Sha, Pass, Salt, Count),
PureErl = erl_pbkdf2:pbkdf2_oneblock(Sha, Pass, Salt, Count),
This =:= PureErl
end),
Opts = [verbose, long_result,
{start_size, 2}, {max_size, 128},
{numtests, 500}, {numworkers, erlang:system_info(schedulers_online)}],
?assert(proper:quickcheck(Prop, Opts)).


%% Taken from the official RFC https://www.ietf.org/rfc/rfc6070.txt

Expand Down

0 comments on commit 713e745

Please sign in to comment.