Current section

Files

Jump to
fast_pbkdf2 c_src fast_pbkdf2.c
Raw

c_src/fast_pbkdf2.c

/*
* fast-pbkdf2 - Optimal PBKDF2-HMAC calculation
* Written in 2015 by Joseph Birr-Pixton <jpixton@gmail.com>
* Rewritten in 2025 by Nelson Vides <nelson.vides@erlang-solutions.com>
*
* To the extent possible under law, the author(s) have dedicated all
* copyright and related and neighboring rights to this software to the
* public domain worldwide. This software is distributed without any
* warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication
* along with this software. If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include "erl_nif.h"
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#if defined(__GNUC__)
#include <sys/types.h>
#endif
#include <openssl/evp.h>
#include <openssl/sha.h>
// announce a timeslice of 5 percent when indicated
#define SLICE 20
#define TIMESLICE_PERCENTAGE 5
#define XSTRINGIFY(s) STRINGIFY(s)
#define STRINGIFY(s) #s
#define HMAC_CTX_ROUND(_name) HMAC_##_name##_ctx_round // C struct
#define HMAC_CTX_ROUND_RES(_name) res_HMAC_##_name##_ctx_round // Erlang Resource definition
#define HMAC_CTX_ROUND_NAME(_name) XSTRINGIFY(HMAC_CTX_ROUND(_name)) // Erlang atom-name
#define MD_NAME(_name) md_##_name
#define HMAC_INIT(_name) HMAC_##_name##_init
#define CLEANUP(_name) cleanup_rount_st_##_name // C struct
#define PBKDF2_F_MD(_name) pbkdf2_f_md##_name
#define PBKDF2_F(_name) pbkdf2_f_##_name
#define PBKDF2(_name) pbkdf2_##_name
typedef struct {
ERL_NIF_TERM atom_sha;
ERL_NIF_TERM atom_sha224;
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]) {
#if defined(__GNUC__) && __GNUC__ >= 4 && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
*(uint32_t *)(out) = __builtin_bswap32(n);
#else
out[0] = (n >> 24) & 0xff;
out[1] = (n >> 16) & 0xff;
out[2] = (n >> 8) & 0xff;
out[3] = n & 0xff;
#endif
}
/* Prepare block (of blocksz bytes) to contain md padding denoting a msg-size
* message (in bytes). block has a prefix of used bytes.
* Message length is expressed in 32 bits (so suitable for sha1, sha256, sha512). */
static inline void md_pad(uint8_t *block, size_t blocksz, size_t used, size_t msg) {
memset(block + used, 0, blocksz - used - 4);
block[used] = 0x80;
block += blocksz - 4;
write32_be((uint32_t)(msg * 8), block);
}
ERL_NIF_TERM mk_error(ErlNifEnv *env, const char *error_msg) {
return enif_make_tuple2(env, enif_make_atom(env, "error"), enif_make_atom(env, error_msg));
}
typedef struct {
EVP_MD_CTX *inner;
EVP_MD_CTX *outer;
} HMAC_md_ctx;
/* This macro expands to decls for the whole implementation for a given
* hash function. Arguments are:
*
* _name like 'sha1', added to symbol names (e.g. sha256)
* _blocksz block size, in bytes (e.g. SHA256_CBLOCK)
* _hashsz digest output, in bytes (e.g. SHA256_DIGEST_LENGTH)
* _iters_per_slot, the number of iterations per 5% of a timeslice (e.g. 200)
*
* This macro generates the following functions:
* - HMAC_CTX_ROUND(_name) - C struct to store the state of the iterations
* - CLEANUP(_name): for example cleanup_round_st_sha256
* - HMAC_INIT(_name) - C function to initialize the HMAC_CTX_ROUND(_name)
* - PBKDF2_F_MD(_name) - Erlang function to iterate over the HMAC_CTX_ROUND(_name)
* - PBKDF2_F(_name) - C function to iterate over the HMAC_CTX_ROUND(_name)
* and call PBKDF2_F_MD(_name)
* - PBKDF2(_name) - Erlang function to call PBKDF2_F(_name)
*/
#define DECL_PBKDF2(_name, _blocksz, _hashsz, _iters_per_slot) \
\
typedef struct { \
HMAC_md_ctx startctx; /* Cache the `2` part of the `2+2i` optimisation */ \
HMAC_md_ctx ctx; /* Carry the `2i` of the algorithm */ \
uint8_t result[_hashsz]; /* Carry the XOR of each iteration and then the final output */ \
uint8_t Ublock[_blocksz]; /* Carry the intermediate hashing of every HMAC on every iter */ \
uint32_t iterations; /* Carry the number of iterations left */ \
} HMAC_CTX_ROUND(_name); \
\
/* Free the EVP_MD_CTX and nif resource allocated previously, if any */ \
static void CLEANUP(_name)(HMAC_CTX_ROUND(_name) *const restrict round_st) { \
if (round_st->ctx.inner) \
EVP_MD_CTX_free(round_st->ctx.inner); \
if (round_st->ctx.outer) \
EVP_MD_CTX_free(round_st->ctx.outer); \
if (round_st->startctx.inner) \
EVP_MD_CTX_free(round_st->startctx.inner); \
if (round_st->startctx.outer) \
EVP_MD_CTX_free(round_st->startctx.outer); \
enif_release_resource(round_st); \
} \
\
/* Initialise the startctx parts (the `2` in the `2+2i` optimisation) */ \
/* - If the key is longer than the block size, it is hashed first.*/ \
/* - The key is padded to the block size if necessary.*/ \
/* - The inner and outer contexts are initialized with the padded key.*/ \
static inline int HMAC_INIT(_name)(HMAC_CTX_ROUND(_name) *restrict round_st, \
const EVP_MD *restrict type, const uint8_t *restrict key, \
size_t nkey) { \
/* Prepare key: */ \
uint8_t k[_blocksz]; \
\
/* Shorten long keys */ \
if (nkey > _blocksz) { \
round_st->startctx.inner = EVP_MD_CTX_new(); \
if (!round_st->startctx.inner) { \
return 1; \
} \
if (!EVP_DigestInit_ex2(round_st->startctx.inner, type, NULL) || \
!EVP_DigestUpdate(round_st->startctx.inner, key, nkey) || \
!EVP_DigestFinal_ex(round_st->startctx.inner, k, NULL)) { \
return 1; \
} \
EVP_MD_CTX_free(round_st->startctx.inner); \
round_st->startctx.inner = NULL; \
key = k; \
nkey = _hashsz; \
} \
\
/* Standard doesn't cover case where blocksz < hashsz */ \
assert(nkey <= _blocksz); \
\
/* Right zero-pad short keys */ \
if (k != key) \
memcpy(k, key, nkey); \
if (_blocksz > nkey) \
memset(k + nkey, 0, _blocksz - nkey); \
\
/* Start inner hash computation */ \
uint8_t blk_inner[_blocksz]; \
uint8_t blk_outer[_blocksz]; \
\
for (uint_fast8_t i = 0; i < _blocksz; i++) { \
blk_inner[i] = 0x36 ^ k[i]; \
blk_outer[i] = 0x5c ^ k[i]; \
} \
\
round_st->startctx.inner = EVP_MD_CTX_new(); \
if (!round_st->startctx.inner || \
!EVP_DigestInit_ex2(round_st->startctx.inner, type, NULL) || \
!EVP_DigestUpdate(round_st->startctx.inner, blk_inner, sizeof blk_inner)) \
return 1; \
\
/* And outer */ \
round_st->startctx.outer = EVP_MD_CTX_new(); \
if (!round_st->startctx.outer || \
!EVP_DigestInit_ex2(round_st->startctx.outer, type, NULL) || \
!EVP_DigestUpdate(round_st->startctx.outer, blk_outer, sizeof blk_outer)) \
return 1; \
\
return 0; \
} \
\
/* Run the actual iterations, possibly yielding the NIF or finally returning the result */ \
/* - It iterates over the number of iterations, updating the context and XORing the results */ \
/* - If the iterations exceed a certain threshold, it schedules the function to run again */ \
/* - The final result is copied to the output buffer and returned */ \
ERL_NIF_TERM PBKDF2_F_MD(_name)(ErlNifEnv * env, const int argc, const ERL_NIF_TERM argv[]) { \
const pbkdf2_st *const mod_st = enif_priv_data(env); \
HMAC_CTX_ROUND(_name) *restrict round_st; \
if (!enif_get_resource(env, argv[0], mod_st->HMAC_CTX_ROUND_RES(_name), \
(void *)(&round_st))) { \
return enif_make_badarg(env); \
} \
\
while (1) { \
for (uint32_t i = 0; i < _iters_per_slot && i < round_st->iterations; ++i) { \
/* Complete inner hash with previous U */ \
if (!EVP_MD_CTX_copy_ex(round_st->ctx.inner, round_st->startctx.inner) || \
!EVP_DigestUpdate(round_st->ctx.inner, round_st->Ublock, _hashsz) || \
!EVP_DigestFinal_ex(round_st->ctx.inner, round_st->Ublock, NULL)) { \
goto error; \
} \
\
/* Complete outer hash with inner output */ \
if (!EVP_MD_CTX_copy_ex(round_st->ctx.outer, round_st->startctx.outer) || \
!EVP_DigestUpdate(round_st->ctx.outer, round_st->Ublock, _hashsz) || \
!EVP_DigestFinal_ex(round_st->ctx.outer, round_st->Ublock, NULL)) { \
goto error; \
} \
\
/* XOR the outer hash into the result */ \
for (uint_fast8_t j = 0; j < _hashsz; ++j) { \
round_st->result[j] ^= round_st->Ublock[j]; \
} \
} \
if (round_st->iterations <= _iters_per_slot) { \
break; \
}; \
\
/* Schedule again but with iterations decremented */ \
round_st->iterations -= _iters_per_slot; \
if (enif_consume_timeslice(env, TIMESLICE_PERCENTAGE)) { \
return enif_schedule_nif(env, HMAC_CTX_ROUND_NAME(_name), 0, PBKDF2_F_MD(_name), \
argc, argv); \
} \
} \
\
/* Reform result into output buffer */ \
ERL_NIF_TERM erl_result; \
unsigned char *output = enif_make_new_binary(env, _hashsz, &erl_result); \
if (output == NULL) { \
CLEANUP(_name)(round_st); \
return enif_make_badarg(env); \
} \
memcpy(output, &round_st->result, _hashsz); \
/* We're done, so we can release the resource */ \
CLEANUP(_name)(round_st); \
return erl_result; \
\
error: \
CLEANUP(_name)(round_st); \
return enif_make_badarg(env); \
} \
\
/* Initialises the first iteration and prepares the state for PBKDF2_F_MD */ \
/* allocates the resource for the HMAC context and calls `PBKDF2_F_sha1` */ \
static inline ERL_NIF_TERM PBKDF2_F(_name)( \
ErlNifEnv * env, HMAC_CTX_ROUND(_name) *const restrict round_st, \
const EVP_MD *const restrict type, const uint8_t *const restrict pw, const size_t npw, \
const uint8_t *const restrict salt, const size_t nsalt, const uint32_t counter) { \
if (HMAC_INIT(_name)(round_st, type, pw, npw) != 0) { \
CLEANUP(_name)(round_st); \
return mk_error(env, "hmac_init_failed"); \
} \
\
round_st->ctx.inner = EVP_MD_CTX_new(); \
round_st->ctx.outer = EVP_MD_CTX_new(); \
if (!round_st->ctx.inner || !round_st->ctx.outer) { \
CLEANUP(_name)(round_st); \
return mk_error(env, "ctx_allocation_failed"); \
} \
\
if (!EVP_DigestInit_ex2(round_st->ctx.inner, type, NULL) || \
!EVP_DigestInit_ex2(round_st->ctx.outer, type, NULL)) { \
CLEANUP(_name)(round_st); \
return mk_error(env, "digest_init_failed"); \
} \
\
uint8_t countbuf[4]; \
write32_be(counter, countbuf); \
/* Prepare loop-invariant padding block. */ \
md_pad(round_st->Ublock, _blocksz, _hashsz, _blocksz + _hashsz); \
/* First iteration: \
* U_1 = PRF(P, S || INT_32_BE(i)) \
*/ \
if (!EVP_MD_CTX_copy_ex(round_st->ctx.inner, round_st->startctx.inner) || \
!EVP_MD_CTX_copy_ex(round_st->ctx.outer, round_st->startctx.outer)) { \
CLEANUP(_name)(round_st); \
return mk_error(env, "ctx_copy_failed"); \
} \
\
if (!EVP_DigestUpdate(round_st->ctx.inner, salt, nsalt) || \
!EVP_DigestUpdate(round_st->ctx.inner, countbuf, sizeof(countbuf)) || \
!EVP_DigestFinal_ex(round_st->ctx.inner, round_st->Ublock, NULL)) { \
CLEANUP(_name)(round_st); \
return mk_error(env, "digest_update_failed"); \
} \
\
if (!EVP_DigestUpdate(round_st->ctx.outer, round_st->Ublock, _hashsz) || \
!EVP_DigestFinal_ex(round_st->ctx.outer, round_st->Ublock, NULL)) { \
CLEANUP(_name)(round_st); \
return mk_error(env, "digest_final_failed"); \
} \
\
if (!EVP_DigestInit_ex2(round_st->ctx.inner, NULL, NULL) || \
!EVP_DigestInit_ex2(round_st->ctx.outer, NULL, NULL)) { \
CLEANUP(_name)(round_st); \
return mk_error(env, "digest_init_ex2_failed"); \
} \
/* We have ran one iteration already */ \
--(round_st->iterations); \
memcpy(round_st->result, round_st->Ublock, _hashsz); \
ERL_NIF_TERM state_term = enif_make_resource(env, round_st); \
const ERL_NIF_TERM tmp_argv[] = {state_term}; \
return PBKDF2_F_MD(_name)(env, 1, tmp_argv); \
} \
\
/* Entry point, chooses the algorithm and initialises all values */ \
static inline ERL_NIF_TERM PBKDF2(_name)(ErlNifEnv * env, const uint8_t *restrict pw, \
const size_t npw, const uint8_t *restrict salt, \
const size_t nsalt, const uint32_t iterations, \
const uint32_t counter) { \
const pbkdf2_st *mod_st = enif_priv_data(env); \
const EVP_MD *const type = mod_st->MD_NAME(_name); \
HMAC_CTX_ROUND(_name) *const restrict round_st = \
enif_alloc_resource(mod_st->HMAC_CTX_ROUND_RES(_name), sizeof(HMAC_CTX_ROUND(_name))); \
if (round_st == NULL) \
return mk_error(env, "alloc_failed"); \
round_st->ctx.inner = NULL; \
round_st->ctx.outer = NULL; \
round_st->startctx.inner = NULL; \
round_st->startctx.outer = NULL; \
round_st->iterations = iterations; \
return PBKDF2_F(_name)(env, round_st, type, pw, npw, salt, nsalt, counter); \
}
/* Hash method | Blocksize (in bytes) | Hash length (in bytes)
* SHA-224 | 64 | 28^
* 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:
* - CPU Information: Intel(R) Core(TM) i9-8950HK CPU @ 2.90GHz
* - Number of Available Cores: 12
* - Available memory: 30.97 GB
* - Elixir 1.18.2
* - Erlang 27.2.2
*
* We look for how many iterations we can do in a slot of 1ms:
* ips average deviation median 99th %
* 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.
*/
DECL_PBKDF2(sha1, SHA_CBLOCK, SHA_DIGEST_LENGTH, 3350 / SLICE)
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;
pbkdf2_st *mod_st = enif_alloc(sizeof(pbkdf2_st));
if (mod_st == NULL)
return 1;
mod_st->MD_NAME(sha1) = NULL;
mod_st->MD_NAME(sha224) = NULL;
mod_st->MD_NAME(sha256) = NULL;
mod_st->MD_NAME(sha384) = NULL;
mod_st->MD_NAME(sha512) = NULL;
mod_st->MD_NAME(sha3_224) = NULL;
mod_st->MD_NAME(sha3_256) = NULL;
mod_st->MD_NAME(sha3_384) = NULL;
mod_st->MD_NAME(sha3_512) = NULL;
mod_st->atom_sha = enif_make_atom(env, "sha");
mod_st->atom_sha224 = enif_make_atom(env, "sha224");
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");
/* Pre-fetch all the hash functions */
mod_st->MD_NAME(sha1) = EVP_MD_fetch(NULL, "SHA1", NULL);
if (NULL == mod_st->MD_NAME(sha1))
goto cleanup;
mod_st->MD_NAME(sha224) = EVP_MD_fetch(NULL, "SHA224", NULL);
if (NULL == mod_st->MD_NAME(sha224))
goto cleanup;
mod_st->MD_NAME(sha256) = EVP_MD_fetch(NULL, "SHA256", NULL);
if (NULL == mod_st->MD_NAME(sha256))
goto cleanup;
mod_st->MD_NAME(sha384) = EVP_MD_fetch(NULL, "SHA384", NULL);
if (NULL == mod_st->MD_NAME(sha384))
goto cleanup;
mod_st->MD_NAME(sha512) = EVP_MD_fetch(NULL, "SHA512", NULL);
if (NULL == mod_st->MD_NAME(sha512))
goto cleanup;
mod_st->MD_NAME(sha3_224) = EVP_MD_fetch(NULL, "SHA3-224", NULL);
if (NULL == mod_st->MD_NAME(sha3_224))
goto cleanup;
mod_st->MD_NAME(sha3_256) = EVP_MD_fetch(NULL, "SHA3-256", NULL);
if (NULL == mod_st->MD_NAME(sha3_256))
goto cleanup;
mod_st->MD_NAME(sha3_384) = EVP_MD_fetch(NULL, "SHA3-384", NULL);
if (NULL == mod_st->MD_NAME(sha3_384))
goto cleanup;
mod_st->MD_NAME(sha3_512) = EVP_MD_fetch(NULL, "SHA3-512", NULL);
if (NULL == mod_st->MD_NAME(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);
if (NULL == mod_st->HMAC_CTX_ROUND_RES(sha1))
goto cleanup;
mod_st->HMAC_CTX_ROUND_RES(sha224) =
enif_open_resource_type(env, NULL, HMAC_CTX_ROUND_NAME(sha224), NULL,
ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER, NULL);
if (NULL == mod_st->HMAC_CTX_ROUND_RES(sha224))
goto cleanup;
mod_st->HMAC_CTX_ROUND_RES(sha256) =
enif_open_resource_type(env, NULL, HMAC_CTX_ROUND_NAME(sha256), NULL,
ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER, NULL);
if (NULL == mod_st->HMAC_CTX_ROUND_RES(sha256))
goto cleanup;
mod_st->HMAC_CTX_ROUND_RES(sha384) =
enif_open_resource_type(env, NULL, HMAC_CTX_ROUND_NAME(sha384), NULL,
ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER, NULL);
if (NULL == mod_st->HMAC_CTX_ROUND_RES(sha384))
goto cleanup;
mod_st->HMAC_CTX_ROUND_RES(sha512) =
enif_open_resource_type(env, NULL, HMAC_CTX_ROUND_NAME(sha512), NULL,
ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER, NULL);
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;
cleanup:
// Cleanup allocated memory in case of failure
if (mod_st->MD_NAME(sha1) != NULL)
EVP_MD_free(mod_st->MD_NAME(sha1));
if (mod_st->MD_NAME(sha224) != NULL)
EVP_MD_free(mod_st->MD_NAME(sha224));
if (mod_st->MD_NAME(sha256) != NULL)
EVP_MD_free(mod_st->MD_NAME(sha256));
if (mod_st->MD_NAME(sha384) != NULL)
EVP_MD_free(mod_st->MD_NAME(sha384));
if (mod_st->MD_NAME(sha512) != NULL)
EVP_MD_free(mod_st->MD_NAME(sha512));
if (mod_st->MD_NAME(sha3_224) != NULL)
EVP_MD_free(mod_st->MD_NAME(sha3_224));
if (mod_st->MD_NAME(sha3_256) != NULL)
EVP_MD_free(mod_st->MD_NAME(sha3_256));
if (mod_st->MD_NAME(sha3_384) != NULL)
EVP_MD_free(mod_st->MD_NAME(sha3_384));
if (mod_st->MD_NAME(sha3_512) != NULL)
EVP_MD_free(mod_st->MD_NAME(sha3_512));
enif_free(mod_st);
return 1;
}
static int reload(ErlNifEnv *env, void **priv, ERL_NIF_TERM info) {
(void)env;
(void)priv;
(void)info;
return 0;
}
static int upgrade(ErlNifEnv *env, void **priv, void **old_priv, ERL_NIF_TERM info) {
(void)old_priv;
return load(env, priv, info);
}
static void unload(ErlNifEnv *env, void *priv) {
(void)env;
pbkdf2_st *mod_st = (pbkdf2_st *)priv;
EVP_MD_free(mod_st->MD_NAME(sha1));
EVP_MD_free(mod_st->MD_NAME(sha224));
EVP_MD_free(mod_st->MD_NAME(sha256));
EVP_MD_free(mod_st->MD_NAME(sha384));
EVP_MD_free(mod_st->MD_NAME(sha512));
EVP_MD_free(mod_st->MD_NAME(sha3_224));
EVP_MD_free(mod_st->MD_NAME(sha3_256));
EVP_MD_free(mod_st->MD_NAME(sha3_384));
EVP_MD_free(mod_st->MD_NAME(sha3_512));
enif_free(priv);
return;
}
static ERL_NIF_TERM pbkdf2_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
if (argc != 5)
return enif_make_badarg(env);
ErlNifBinary password;
if (!enif_inspect_binary(env, argv[1], &password))
return mk_error(env, "bad_password");
ErlNifBinary salt;
if (!enif_inspect_binary(env, argv[2], &salt))
return mk_error(env, "bad_salt");
int iteration_count;
if (!enif_get_int(env, argv[3], &iteration_count))
return mk_error(env, "bad_iteration_count");
if (iteration_count <= 0)
return mk_error(env, "bad_iteration_count");
int counter;
if (!enif_get_int(env, argv[4], &counter))
return mk_error(env, "bad_block_counter");
if (counter <= 0)
return mk_error(env, "bad_block_counter");
pbkdf2_st *mod_st = (pbkdf2_st *)enif_priv_data(env);
if (enif_is_identical(argv[0], mod_st->atom_sha)) {
return PBKDF2(sha1)(env, password.data, password.size, salt.data, salt.size,
iteration_count, counter);
} else if (enif_is_identical(argv[0], mod_st->atom_sha224)) {
return PBKDF2(sha224)(env, password.data, password.size, salt.data, salt.size,
iteration_count, counter);
} else if (enif_is_identical(argv[0], mod_st->atom_sha256)) {
return PBKDF2(sha256)(env, password.data, password.size, salt.data, salt.size,
iteration_count, counter);
} else if (enif_is_identical(argv[0], mod_st->atom_sha384)) {
return PBKDF2(sha384)(env, password.data, password.size, salt.data, salt.size,
iteration_count, counter);
} 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");
}
}
static ErlNifFunc fastpbkdf2_nif_funcs[] = {{"pbkdf2_block", 5, pbkdf2_nif, 0}};
ERL_NIF_INIT(fast_pbkdf2, fastpbkdf2_nif_funcs, load, reload, upgrade, unload);