From 8f0605b31e5512e39651647be11d9c9024235940 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Mon, 17 Feb 2025 16:38:19 +0100 Subject: [PATCH 1/9] Add Clangd formatting rules --- .clang-format | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..1bfafbc --- /dev/null +++ b/.clang-format @@ -0,0 +1,2 @@ +IndentWidth: 4 +ColumnLimit: 100 From 20537bceae84af5fd9f944d8e6cb64d14c309982 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Tue, 18 Feb 2025 11:57:39 +0100 Subject: [PATCH 2/9] Remove mix files, mix knows how to compile from rebar --- mix.exs | 18 ------------------ mix.lock | 4 ---- 2 files changed, 22 deletions(-) delete mode 100644 mix.exs delete mode 100644 mix.lock diff --git a/mix.exs b/mix.exs deleted file mode 100644 index b581df4..0000000 --- a/mix.exs +++ /dev/null @@ -1,18 +0,0 @@ -defmodule Fast_Pbkdf2.MixProject do - use Mix.Project - - def project do - [ - app: :fast_pbkdf2, - version: "0.1.0", - elixir: "~> 1.10", - deps: deps() - ] - end - - defp deps do - [ - {:benchee, "~> 1.0", only: :dev} - ] - end -end diff --git a/mix.lock b/mix.lock deleted file mode 100644 index 5aa28d0..0000000 --- a/mix.lock +++ /dev/null @@ -1,4 +0,0 @@ -%{ - "benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm", "3ad58ae787e9c7c94dd7ceda3b587ec2c64604563e049b2a0e8baafae832addb"}, - "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, -} From ac18e65a04162b245a0105278c68b1e318315678 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Wed, 19 Feb 2025 07:42:35 +0100 Subject: [PATCH 3/9] Update ex_doc configuration in the rebar.config --- rebar.config | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/rebar.config b/rebar.config index bf4299b..f72d818 100644 --- a/rebar.config +++ b/rebar.config @@ -67,8 +67,13 @@ rebar3_ex_doc ]}. -{hex, [{doc, ex_doc}]}. - +{hex, [ + {doc, #{provider => ex_doc}} +]}. {ex_doc, [ - {source_url, <<"https://github.com/esl/fast_pbkdf2">>} + {source_url, <<"https://github.com/esl/fast_pbkdf2">>}, + {main, <<"readme">>}, + {extras, [{'README.md', #{title => <<"README">>}}, + {'LICENSE', #{title => <<"License">>}} + ]} ]}. From 4174fc4946544ead405703175e29c9a35920d6d2 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Wed, 19 Feb 2025 07:41:41 +0100 Subject: [PATCH 4/9] Update README and badges --- README.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 3b01ff1..3c8f2c1 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ # Fast PBKDF2 -[![Actions Status](https://github.com/esl/fast_pbkdf2/workflows/ci/badge.svg)](https://github.com/esl/fast_pbkdf2/actions) -[![codecov](https://codecov.io/gh/esl/fast_pbkdf2/branch/main/graph/badge.svg)](https://codecov.io/gh/esl/fast_pbkdf2) -[![Hex](http://img.shields.io/hexpm/v/fast_pbkdf2.svg)](https://hex.pm/packages/fast_pbkdf2) +[![Hex pm](https://img.shields.io/hexpm/v/fast_pbkdf2.svg)](https://hex.pm/packages/fast_pbkdf2) +[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/fast_pbkdf2/) +[![Downloads](https://img.shields.io/hexpm/dt/fast_pbkdf2.svg)](https://hex.pm/packages/fast_pbkdf2) +[![GitHub Actions](https://github.com/esl/fast_pbkdf2/workflows/ci/badge.svg?branch=main)](https://github.com/esl/fast_pbkdf2/actions?query=workflow%3Aci+branch%3Amain) +[![Codecov](https://codecov.io/gh/esl/fast_pbkdf2/branch/main/graph/badge.svg)](https://codecov.io/gh/esl/fast_pbkdf2) +[![License](https://img.shields.io/hexpm/l/fast_pbkdf2.svg)](https://github.com/esl/fast_pbkdf2/blob/main/LICENSE) `fast_pbkdf2` is an Erlang implementation of [PBKDF2][PBKDF2], where the algorithm is a carefully-optimised NIF that uses timeslicing and nif scheduling to respect the latency properties of the BEAM. -All OTP versions from OTP18 have been tested manually and should work correctly, but on CI we support only from 21.3 ## Building `fast_pbkdf2` is a rebar3-compatible OTP application, that uses the [port_compiler](https://github.com/blt/port_compiler) for the C part of the code. @@ -13,8 +15,7 @@ All OTP versions from OTP18 have been tested manually and should work correctly, Building is as easy as `rebar3 compile`, and using it in your projects as ```erlang {deps, - [{fast_pbkdf2, "1.0.0"}]}. -{plugins, [pc, rebar3_hex]}. + [{fast_pbkdf2, "~> 2.0"}]}. {provider_hooks, [{pre, [{compile, {pc, compile}}, @@ -43,7 +44,7 @@ PBKDF2 is a challenge derivation method, that is, it forces the client to comput Is partial. We don't expect to have the fastest implementation, as that would be purely C code on GPUs, so unfortunately an attacker will pretty much always have better chances there. _But_ we can make the computation cheap enough for us that other computations —like the load of a session establishment— will be more relevant than that of the challenge; and also that other defence mechanisms like IP blacklisting or traffic shaping, will fire in good time. ### The outcome -On average it's 10x faster on the machines I've tested it (you can compare using the provided module in `./benchmarks/bench.ex`), but while the erlang implementation consumes memory linearly to the iteration count (1M it count with 120 clients quickly allocated 7GB of RAM, and 1M is common for password managers for example), the NIF implementation does not allocate any more memory. Also, the NIFS spend all of their time in user level alone, while the erlang one jumps to system calls in around ~2% of the time (I'd guess due to some heavy allocation and garbage collection patterns). +On average it's 30% faster than the pure OpenSSL implementation, which `crypto:pbkdf2_hmac/5` calls without yielding, and 10x times faster (and x3N less memory, where N is the iteration count!) than a pure erlang equivalent (you can compare using the provided module in `./benchmarks/bench.ex`). ## Credit where credit is due The initial algorithm and optimisations were taken from Joseph Birr-Pixton's @@ -51,8 +52,8 @@ The initial algorithm and optimisations were taken from Joseph Birr-Pixton's ## Read more: * Password-Based Cryptography Specification (PBKDF2): [RFC8018](https://tools.ietf.org/html/rfc8018#section-5.2) -* HMAC: [RFC2104]( https://tools.ietf.org/html/rfc2104) -* SHAs and HMAC-SHA: [RFC6234](https://tools.ietf.org/html/rfc6234) +* HMAC: [RFC2104](https://datatracker.ietf.org/doc/html/rfc2104) +* SHAs and HMAC-SHA: [RFC6234](https://datatracker.ietf.org/doc/html/rfc6234) [MIM]: https://github.com/esl/MongooseIM [PBKDF2]: https://tools.ietf.org/html/rfc8018#section-5.2 From 053224af90c90c56ea5cf67195caffc857734322 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Tue, 18 Feb 2025 09:13:57 +0100 Subject: [PATCH 5/9] Reimplement using OpenSSL high-level API This is the main rework. I initially wrote the standard functions for SHA1, and only later refactored to extract all SHA1 specifics into macro parameters, so that all other algorithms could be generated by the macro. It follows the same idea that the previous code, however it is not a step-by-step refactoring but a rewrite from scratch. --- c_src/fast_pbkdf2.c | 914 ++++++++++++++++++++------------------------ 1 file changed, 423 insertions(+), 491 deletions(-) diff --git a/c_src/fast_pbkdf2.c b/c_src/fast_pbkdf2.c index 8c05ea3..27275b6 100644 --- a/c_src/fast_pbkdf2.c +++ b/c_src/fast_pbkdf2.c @@ -1,6 +1,7 @@ /* * fast-pbkdf2 - Optimal PBKDF2-HMAC calculation * Written in 2015 by Joseph Birr-Pixton + * Rewritten in 2025 by Nelson Vides * * To the extent possible under law, the author(s) have dedicated all * copyright and related and neighboring rights to this software to the @@ -13,77 +14,34 @@ */ #include "erl_nif.h" - -#ifndef TIMESLICE_PERCENTAGE -#define TIMESLICE_PERCENTAGE 5 // announce a timeslice of 5 percent when indicated -#define ITERS_PER_SLOT 6 -/* On the single core of an 2,2 GHz Quad-Core Intel Core i7, in slightly below 1ms - * we achieve around 120 iterations for sha512. - * Also, we want to report percentage every 5% (TIMESLICE_PERCENTAGE). - * We therefore get that a slot in between iterations should take 6 iterations (ITERS_PER_SLOT). - */ -#endif - #include -#include -#include #include +#include +#include #if defined(__GNUC__) #include #endif - +#include #include -/* --- Common useful things --- */ - -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 -} - -static inline void write64_be(uint64_t n, uint8_t out[8]) -{ -#if defined(__GNUC__) && __GNUC__ >= 4 && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ - *(uint64_t *)(out) = __builtin_bswap64(n); -#else - write32_be((n >> 32) & 0xffffffff, out); - write32_be(n & 0xffffffff, out + 4); -#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 all sha1 and sha2 algorithms). */ -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); -} +// announce a timeslice of 5 percent when indicated +#define SLICE 20 +#define TIMESLICE_PERCENTAGE 5 -/* Internal function/type names for hash-specific things. */ #define XSTRINGIFY(s) STRINGIFY(s) #define STRINGIFY(s) #s -#define HMAC_CTX(_name) HMAC_ ## _name ## _ctx -#define HMAC_INIT(_name) HMAC_ ## _name ## _init -#define HMAC_UPDATE(_name) HMAC_ ## _name ## _update -#define HMAC_FINAL(_name) HMAC_ ## _name ## _final - -#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) 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 PBKDF2_F(_name) pbkdf2_f_ ## _name -#define PBKDF2(_name) pbkdf2_ ## _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; @@ -91,375 +49,337 @@ typedef struct { ERL_NIF_TERM atom_sha256; ERL_NIF_TERM atom_sha384; ERL_NIF_TERM atom_sha512; - 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); + 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); + 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); } 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) - * _ctx hash context type (e.g. SHA256_Init) - * _init hash context initialisation function (e.g. SHA256_Update) - * args: (_ctx *c) - * _update hash context update function (e.g. SHA256_Update) - * args: (_ctx *c, const void *data, size_t ndata) - * _final hash context finish function (e.g. SHA256_Final) - * args: (void *out, _ctx *c) - * _xform hash context raw block update function (e.g. SHA256_Transform) - * args: (_ctx *c, const void *data) - * _xcpy hash context raw copy function (only need copy hash state) (e.g. sha256_cpy) - * args: (_ctx * restrict out, const _ctx *restrict in) - * _xtract hash context state extraction (e.g. sha256_extract) - * args: args (_ctx *restrict c, uint8_t *restrict out) - * _xxor hash context xor function (only need xor hash state) (e.g. sha256_xor) - * args: (_ctx *restrict out, const _ctx *restrict in) + * _iters_per_slot, the number of iterations per 5% of a timeslice (e.g. 200) * - * The resulting function is named PBKDF2(_name). + * 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, _ctx, \ - _init, _update, _xform, _final, _xcpy, _xtract, _xxor) \ - \ - typedef struct { \ - _ctx inner; \ - _ctx outer; \ - } HMAC_CTX(_name); \ - \ - typedef struct { \ - HMAC_CTX(_name) startctx; \ - HMAC_CTX(_name) ctx; \ - _ctx result; \ - uint8_t Ublock[_blocksz]; \ - uint32_t iterations; \ - } HMAC_CTX_ROUND(_name); \ - \ - static inline void HMAC_INIT(_name)(HMAC_CTX(_name) *ctx, \ - const uint8_t *key, size_t nkey) \ - { \ - /* Prepare key: */ \ - uint8_t k[_blocksz]; \ - \ - /* Shorten long keys. */ \ - if (nkey > _blocksz) \ - { \ - _init(&ctx->inner); \ - _update(&ctx->inner, key, nkey); \ - _final(k, &ctx->inner); \ - 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 (size_t i = 0; i < _blocksz; i++) \ - { \ - blk_inner[i] = 0x36 ^ k[i]; \ - blk_outer[i] = 0x5c ^ k[i]; \ - } \ - \ - _init(&ctx->inner); \ - _update(&ctx->inner, blk_inner, sizeof blk_inner); \ - \ - /* And outer. */ \ - _init(&ctx->outer); \ - _update(&ctx->outer, blk_outer, sizeof blk_outer); \ - } \ - \ - static inline void HMAC_UPDATE(_name)(HMAC_CTX(_name) *ctx, \ - const void *data, size_t ndata) \ - { \ - _update(&ctx->inner, data, ndata); \ - } \ - \ - static inline void HMAC_FINAL(_name)(HMAC_CTX(_name) *ctx, \ - uint8_t out[_hashsz]) \ - { \ - _final(out, &ctx->inner); \ - _update(&ctx->outer, out, _hashsz); \ - _final(out, &ctx->outer); \ - } \ - \ - /* --- PBKDF2 --- */ \ - ERL_NIF_TERM PBKDF2_F(_name)(ErlNifEnv *env, \ - int argc, const ERL_NIF_TERM argv[]) \ - { \ - pbkdf2_st *mod_st = enif_priv_data(env); \ - HMAC_CTX_ROUND(_name) *round_st; \ - enif_get_resource(env, argv[0], \ - mod_st->HMAC_CTX_ROUND_RES(_name), \ - ((void*) (&round_st))); \ - \ - while (1) { \ - for (uint32_t i = 0; i < ITERS_PER_SLOT && i < round_st->iterations-1; ++i) \ - { \ - /* Complete inner hash with previous U */ \ - _xcpy(&round_st->ctx.inner, &round_st->startctx.inner); \ - _xform(&round_st->ctx.inner, round_st->Ublock); \ - _xtract(&round_st->ctx.inner, round_st->Ublock); \ - /* Complete outer hash with inner output */ \ - _xcpy(&round_st->ctx.outer, &round_st->startctx.outer); \ - _xform(&round_st->ctx.outer, round_st->Ublock); \ - _xtract(&round_st->ctx.outer, round_st->Ublock); \ - _xxor(&round_st->result, &round_st->ctx.outer); \ - } \ - if (round_st->iterations <= ITERS_PER_SLOT) break; \ - round_st->iterations -= ITERS_PER_SLOT; \ - \ - /* Schedule again but with iterations decremented */ \ - if (enif_consume_timeslice(env, TIMESLICE_PERCENTAGE)) { \ - return enif_schedule_nif(env, HMAC_CTX_ROUND_NAME(_name), 0, \ - PBKDF2_F(_name), argc, argv); \ - } \ - } \ - \ - /* We're done, so we can release the resource */ \ - enif_release_resource(round_st); \ - /* Reform result into output buffer. */ \ - ERL_NIF_TERM erl_result; \ - unsigned char *output = enif_make_new_binary(env, _hashsz, &erl_result); \ - _xtract(&round_st->result, output); \ - return erl_result; \ - } \ - \ - static inline ERL_NIF_TERM PBKDF2(_name)(ErlNifEnv *env, \ - const uint8_t *pw, size_t npw, \ - const uint8_t *salt, size_t nsalt, \ - uint32_t iterations, uint32_t counter) \ - { \ - /* Retrieve the state resource descriptor from our priv data, */ \ - /* and allocate a new resource structure */ \ - pbkdf2_st *mod_st = enif_priv_data(env); \ - HMAC_CTX_ROUND(_name) *round_st = enif_alloc_resource( \ - mod_st->HMAC_CTX_ROUND_RES(_name), \ - sizeof(HMAC_CTX_ROUND(_name)) \ - ); \ - \ - HMAC_INIT(_name)(&round_st->startctx, pw, npw); \ - 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)) \ - */ \ - round_st->ctx = round_st->startctx; \ - HMAC_UPDATE(_name)(&round_st->ctx, salt, nsalt); \ - HMAC_UPDATE(_name)(&round_st->ctx, countbuf, sizeof countbuf); \ - HMAC_FINAL(_name)(&round_st->ctx, round_st->Ublock); \ - round_st->result = round_st->ctx.outer; \ - round_st->iterations = iterations; \ - \ - ERL_NIF_TERM state_term = enif_make_resource(env, round_st); \ - const ERL_NIF_TERM tmp_argv[] = {state_term}; \ - return PBKDF2_F(_name)(env, 1, tmp_argv); \ - } - - -static inline void sha1_extract(SHA_CTX *restrict ctx, uint8_t *restrict out) -{ - write32_be(ctx->h0, out); - write32_be(ctx->h1, out + 4); - write32_be(ctx->h2, out + 8); - write32_be(ctx->h3, out + 12); - write32_be(ctx->h4, out + 16); -} - -static inline void sha1_cpy(SHA_CTX *restrict out, const SHA_CTX *restrict in) -{ - out->h0 = in->h0; - out->h1 = in->h1; - out->h2 = in->h2; - out->h3 = in->h3; - out->h4 = in->h4; -} - -static inline void sha1_xor(SHA_CTX *restrict out, const SHA_CTX *restrict in) -{ - out->h0 ^= in->h0; - out->h1 ^= in->h1; - out->h2 ^= in->h2; - out->h3 ^= in->h3; - out->h4 ^= in->h4; -} - -DECL_PBKDF2(sha1, - SHA_CBLOCK, - SHA_DIGEST_LENGTH, - SHA_CTX, - SHA1_Init, - SHA1_Update, - SHA1_Transform, - SHA1_Final, - sha1_cpy, - sha1_extract, - sha1_xor) - -static inline void sha224_extract(SHA256_CTX *restrict ctx, uint8_t *restrict out) -{ - write32_be(ctx->h[0], out); - write32_be(ctx->h[1], out + 4); - write32_be(ctx->h[2], out + 8); - write32_be(ctx->h[3], out + 12); - write32_be(ctx->h[4], out + 16); - write32_be(ctx->h[5], out + 20); - write32_be(ctx->h[6], out + 24); -} - -static inline void sha256_extract(SHA256_CTX *restrict ctx, uint8_t *restrict out) -{ - write32_be(ctx->h[0], out); - write32_be(ctx->h[1], out + 4); - write32_be(ctx->h[2], out + 8); - write32_be(ctx->h[3], out + 12); - write32_be(ctx->h[4], out + 16); - write32_be(ctx->h[5], out + 20); - write32_be(ctx->h[6], out + 24); - write32_be(ctx->h[7], out + 28); -} - -static inline void sha256_cpy(SHA256_CTX *restrict out, const SHA256_CTX *restrict in) -{ - out->h[0] = in->h[0]; - out->h[1] = in->h[1]; - out->h[2] = in->h[2]; - out->h[3] = in->h[3]; - out->h[4] = in->h[4]; - out->h[5] = in->h[5]; - out->h[6] = in->h[6]; - out->h[7] = in->h[7]; -} - -static inline void sha256_xor(SHA256_CTX *restrict out, const SHA256_CTX *restrict in) -{ - out->h[0] ^= in->h[0]; - out->h[1] ^= in->h[1]; - out->h[2] ^= in->h[2]; - out->h[3] ^= in->h[3]; - out->h[4] ^= in->h[4]; - out->h[5] ^= in->h[5]; - out->h[6] ^= in->h[6]; - out->h[7] ^= in->h[7]; -} - -DECL_PBKDF2(sha224, - SHA256_CBLOCK, - SHA224_DIGEST_LENGTH, - SHA256_CTX, - SHA224_Init, - SHA224_Update, - SHA256_Transform, - SHA224_Final, - sha256_cpy, - sha224_extract, - sha256_xor) - -DECL_PBKDF2(sha256, - SHA256_CBLOCK, - SHA256_DIGEST_LENGTH, - SHA256_CTX, - SHA256_Init, - SHA256_Update, - SHA256_Transform, - SHA256_Final, - sha256_cpy, - sha256_extract, - sha256_xor) - -static inline void sha384_extract(SHA512_CTX *restrict ctx, uint8_t *restrict out) -{ - write64_be(ctx->h[0], out); - write64_be(ctx->h[1], out + 8); - write64_be(ctx->h[2], out + 16); - write64_be(ctx->h[3], out + 24); - write64_be(ctx->h[4], out + 32); - write64_be(ctx->h[5], out + 40); -} +#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) *const restrict round_st; \ + if (!enif_get_resource(env, argv[0], mod_st->HMAC_CTX_ROUND_RES(_name), \ + (void *)(&round_st))) { \ + return mk_error(env, "bad_resource"); \ + } \ + \ + 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); \ + } -static inline void sha512_extract(SHA512_CTX *restrict ctx, uint8_t *restrict out) -{ - write64_be(ctx->h[0], out); - write64_be(ctx->h[1], out + 8); - write64_be(ctx->h[2], out + 16); - write64_be(ctx->h[3], out + 24); - write64_be(ctx->h[4], out + 32); - write64_be(ctx->h[5], out + 40); - write64_be(ctx->h[6], out + 48); - write64_be(ctx->h[7], out + 56); -} +/* 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 + */ -static inline void sha512_cpy(SHA512_CTX *restrict out, const SHA512_CTX *restrict in) -{ - out->h[0] = in->h[0]; - out->h[1] = in->h[1]; - out->h[2] = in->h[2]; - out->h[3] = in->h[3]; - out->h[4] = in->h[4]; - out->h[5] = in->h[5]; - out->h[6] = in->h[6]; - out->h[7] = in->h[7]; -} +/* 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 + * + * 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. + */ -static inline void sha512_xor(SHA512_CTX *restrict out, const SHA512_CTX *restrict in) -{ - out->h[0] ^= in->h[0]; - out->h[1] ^= in->h[1]; - out->h[2] ^= in->h[2]; - out->h[3] ^= in->h[3]; - out->h[4] ^= in->h[4]; - out->h[5] ^= in->h[5]; - out->h[6] ^= in->h[6]; - out->h[7] ^= in->h[7]; -} +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(sha384, - SHA512_CBLOCK, - SHA384_DIGEST_LENGTH, - SHA512_CTX, - SHA384_Init, - SHA384_Update, - SHA512_Transform, - SHA384_Final, - sha512_cpy, - sha384_extract, - sha512_xor) - -DECL_PBKDF2(sha512, - SHA512_CBLOCK, - SHA512_DIGEST_LENGTH, - SHA512_CTX, - SHA512_Init, - SHA512_Update, - SHA512_Transform, - SHA512_Final, - sha512_cpy, - sha512_extract, - sha512_xor) - -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) { +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"); @@ -467,65 +387,94 @@ static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) mod_st->atom_sha384 = enif_make_atom(env, "sha384"); mod_st->atom_sha512 = enif_make_atom(env, "sha512"); - 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 - ); - - 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 - ); - - 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 - ); - - 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 - ); - - 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 - ); + /* 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; - *priv_data = (void*) mod_st; + 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; + + *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)); + enif_free(mod_st); + return 1; } -static int reload(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) -{ +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) -{ +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) -{ +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)); enif_free(priv); return; } -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) - ); -} - -static ERL_NIF_TERM -pbkdf2_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{ - if(argc != 5) +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; @@ -548,45 +497,28 @@ pbkdf2_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) if (counter <= 0) return mk_error(env, "bad_block_counter"); - /** Calculates PBKDF2-HMAC-SHA - * @p npw bytes at @p pw are the password input. - * @p nsalt bytes at @p salt are the salt input. - * @p iterations is the PBKDF2 iteration count and must be non-zero. - */ - 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); + 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 { return mk_error(env, "bad_hash"); } } -static ErlNifFunc fastpbkdf2_nif_funcs[] = { - {"pbkdf2_block", 5, pbkdf2_nif} -}; +static ErlNifFunc fastpbkdf2_nif_funcs[] = {{"pbkdf2_block", 5, pbkdf2_nif, 0}}; ERL_NIF_INIT(fast_pbkdf2, fastpbkdf2_nif_funcs, load, reload, upgrade, unload); From ced22daa6b63e88fb9bc201791a7606e80965093 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Tue, 18 Feb 2025 09:07:56 +0100 Subject: [PATCH 6/9] Add support for the SHA3 family --- README.md | 2 +- c_src/fast_pbkdf2.c | 83 +++++++++++++++++++++++++++++++++++++++++++ src/fast_pbkdf2.erl | 4 ++- test/erl_pbkdf2.erl | 4 ++- test/pbkdf2_SUITE.erl | 49 ++++++++++++++++++++----- 5 files changed, 131 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 3c8f2c1..52e9ebe 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,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` diff --git a/c_src/fast_pbkdf2.c b/c_src/fast_pbkdf2.c index 27275b6..293a2c8 100644 --- a/c_src/fast_pbkdf2.c +++ b/c_src/fast_pbkdf2.c @@ -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]) { @@ -341,6 +353,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: @@ -355,6 +371,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. @@ -365,6 +383,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; @@ -386,6 +408,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"); /* Pre-fetch all the hash functions */ mod_st->MD_NAME(sha1) = EVP_MD_fetch(NULL, "SHA1", NULL); @@ -403,6 +429,18 @@ static int load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info) { 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); @@ -429,6 +467,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; @@ -445,6 +504,14 @@ static int load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info) { 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; } @@ -469,6 +536,10 @@ static void unload(ErlNifEnv *env, void *priv) { 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; } @@ -514,6 +585,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"); } diff --git a/src/fast_pbkdf2.erl b/src/fast_pbkdf2.erl index cbf458a..59fbf99 100644 --- a/src/fast_pbkdf2.erl +++ b/src/fast_pbkdf2.erl @@ -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]). diff --git a/test/erl_pbkdf2.erl b/test/erl_pbkdf2.erl index 0bb860e..280073d 100644 --- a/test/erl_pbkdf2.erl +++ b/test/erl_pbkdf2.erl @@ -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) -> diff --git a/test/pbkdf2_SUITE.erl b/test/pbkdf2_SUITE.erl index 8ca5f82..e39bdc2 100644 --- a/test/pbkdf2_SUITE.erl +++ b/test/pbkdf2_SUITE.erl @@ -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, @@ -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], [ @@ -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 @@ -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 From dbed4bfa5a68c5fa8c55cbe0c93e6e1eab7a1a35 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Wed, 19 Feb 2025 15:02:32 +0100 Subject: [PATCH 7/9] Take into account error tuples returning from the NIF --- c_src/fast_pbkdf2.c | 2 +- src/fast_pbkdf2.erl | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/c_src/fast_pbkdf2.c b/c_src/fast_pbkdf2.c index 293a2c8..3491b07 100644 --- a/c_src/fast_pbkdf2.c +++ b/c_src/fast_pbkdf2.c @@ -213,7 +213,7 @@ typedef struct { HMAC_CTX_ROUND(_name) *const restrict round_st; \ if (!enif_get_resource(env, argv[0], mod_st->HMAC_CTX_ROUND_RES(_name), \ (void *)(&round_st))) { \ - return mk_error(env, "bad_resource"); \ + return enif_make_badarg(env); \ } \ \ while (1) { \ diff --git a/src/fast_pbkdf2.erl b/src/fast_pbkdf2.erl index 59fbf99..928d1a8 100644 --- a/src/fast_pbkdf2.erl +++ b/src/fast_pbkdf2.erl @@ -11,13 +11,14 @@ %%% @doc %%% This function calculates the pbkdf2 algorithm where dkLen is simply assumed to be that %%% of the underlying hash function, a sane default. --spec pbkdf2(sha_type(), binary(), binary(), non_neg_integer()) -> binary(). +-spec pbkdf2(sha_type(), binary(), binary(), non_neg_integer()) -> binary() | {error, atom()}. pbkdf2(Hash, Password, Salt, IterationCount) -> pbkdf2_block(Hash, Password, Salt, IterationCount, 1). %%% @doc %%% This function allows to customise the desired dkLen parameter for pbkdf2. --spec pbkdf2(sha_type(), binary(), binary(), non_neg_integer(), non_neg_integer()) -> binary(). +-spec pbkdf2(sha_type(), binary(), binary(), non_neg_integer(), non_neg_integer()) -> + binary() | {error, atom()}. pbkdf2(Hash, Password, Salt, IterationCount, DkLen) -> pbkdf2(Hash, Password, Salt, IterationCount, DkLen, 1, [], 0). @@ -28,13 +29,19 @@ pbkdf2(_Hash, _Password, _Salt, _IterationCount, DkLen, _BlockIndex, Acc, Len) w Bin = iolist_to_binary(lists:reverse(Acc)), binary:part(Bin, 0, DkLen); pbkdf2(Hash, Password, Salt, IterationCount, DkLen, BlockIndex, Acc, Len) -> - Block = pbkdf2_block(Hash, Password, Salt, IterationCount, BlockIndex), - pbkdf2(Hash, Password, Salt, IterationCount, DkLen, BlockIndex + 1, [Block | Acc], byte_size(Block) + Len). + case pbkdf2_block(Hash, Password, Salt, IterationCount, BlockIndex) of + {error, Reason} -> {error, Reason}; + Block -> + pbkdf2(Hash, Password, Salt, IterationCount, DkLen, BlockIndex + 1, + [Block | Acc], + byte_size(Block) + Len) + end. %%%=================================================================== %%% NIF %%%=================================================================== --spec pbkdf2_block(sha_type(), binary(), binary(), non_neg_integer(), non_neg_integer()) -> binary(). +-spec pbkdf2_block(sha_type(), binary(), binary(), non_neg_integer(), non_neg_integer()) -> + binary() | {error, atom()}. pbkdf2_block(_Hash, _Password, _Salt, _IterationCount, _BlockSize) -> erlang:nif_error(not_loaded). From 5809820914e0ff8790fd67163091adab8b47a4e3 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Wed, 19 Feb 2025 07:42:11 +0100 Subject: [PATCH 8/9] Update compilation flags in rebar.config There was an issue in MacOS where in 15.x the dynamic linker would give preference to the system's cached `libboringssl.dylib`, instead of the OpenSSL's one put in the path. For the dynamic linker to resolve the right library correctly, the static linker would need to add strict resolution rules by being provided more strict flags. --- rebar.config | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/rebar.config b/rebar.config index f72d818..087c06f 100644 --- a/rebar.config +++ b/rebar.config @@ -13,16 +13,15 @@ ]}, {cover_enabled, true}, {cover_export_enabled, true}, - {plugins, [ - {rebar3_codecov, "0.7.0"}]}, - {port_env, + {plugins, [{rebar3_codecov, "0.7.0"}]}, + {port_env, [ {"(linux|solaris|freebsd|netbsd|openbsd|dragonfly|darwin|gnu)", - "CFLAGS", "$CFLAGS -std=c99 -O0 -g -Wall -Wextra -Wundef -Werror=undef -fPIC -I/opt/homebrew/include -I/usr/local/include --coverage"}, + "CFLAGS", "$CFLAGS -O0 -g --coverage"}, {"(linux|solaris|freebsd|netbsd|openbsd|dragonfly|darwin|gnu)", - "LDLIBS", "$LDLIBS -lcrypto -L/opt/homebrew/lib/ -L/usr/local/lib --coverage"} + "LDLIBS", "$LDLIBS --coverage"} ] - } + } ]} ] }. @@ -32,15 +31,17 @@ {port_env, [ {"(linux|solaris|freebsd|netbsd|openbsd|dragonfly|darwin|gnu)", - "CFLAGS", "$CFLAGS -std=c99 -O3 -g -Wall -Wextra -Wundef -Werror=undef -fPIC -I/opt/homebrew/include -I/usr/local/include"}, + "CFLAGS", "$CFLAGS -std=c99 -O3 -Wall -Wextra -Werror -fPIC"}, {"(linux|solaris|freebsd|netbsd|openbsd|dragonfly|darwin|gnu)", - "LDLIBS", "$LDLIBS -lcrypto -L/opt/homebrew/lib/ -L/usr/local/lib"}, - {"win32", "CFLAGS", "$CFLAGS /I${OPENSSL_INSTALL_DIR}/include /O2 /DNDEBUG /Wall"}, - {"win32", "LDLIBS", "$LDLIBS /LIBPATH:${OPENSSL_INSTALL_DIR}/lib libcrypto.lib"}, + "LDFLAGS", "$LDFLAGS -lssl -lcrypto"}, + {"win32", "CFLAGS", "$CFLAGS /O2 /DNDEBUG /Wall"}, + {"win32", "LDFLAGS", "$LDFLAGS libssl.lib libcrypto.lib"}, + {"darwin", "DRV_LDFLAGS", "-bundle -bundle_loader \"${BINDIR}/beam.smp\" $ERL_LDFLAGS"}, {"DRV_LINK_TEMPLATE", "$DRV_LINK_TEMPLATE $LDLIBS"} ] }. + {port_specs, [ { From 5e1e46d589511257d70a62463cc19a832a6867fe Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Wed, 19 Feb 2025 08:26:39 +0100 Subject: [PATCH 9/9] Refactor CI entirely --- .github/workflows/ci.yml | 133 ++++++++++++++++++++++++++------------- 1 file changed, 89 insertions(+), 44 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f302034..af63473 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,8 @@ on: workflow_dispatch: jobs: - test: + + Ubuntu: name: OTP ${{matrix.otp}} / rebar3 ${{matrix.rebar3}} / OS ${{matrix.os}} strategy: fail-fast: false @@ -20,44 +21,13 @@ jobs: - otp: '24' rebar3: '3.22.1' os: 'ubuntu-22.04' - - otp: '27' - rebar3: '3.24.0' - os: 'windows-2022' - - otp: '27' - rebar3: '3.24.0' - os: 'macos-latest' runs-on: ${{matrix.os}} steps: - uses: actions/checkout@v4 - - # OS setups - - name: Ubuntu/Windows – Prepare Erlang - uses: erlef/setup-beam@v1 + - uses: erlef/setup-beam@v1 with: otp-version: ${{matrix.otp}} rebar3-version: ${{matrix.rebar3}} - if: ${{ matrix.os != 'macos-latest' }} - - name: Windows - Enable Developer Command Prompt - uses: ilammy/msvc-dev-cmd@v1 - if: ${{ matrix.os == 'windows-2022' }} - - name: Windows - Install openssl - shell: pwsh - run: | - choco install openssl - echo "OPENSSL_INSTALL_DIR=""C:\Program Files\OpenSSL""" >> $env:GITHUB_ENV - if: ${{ matrix.os == 'windows-2022' }} - - name: MacOS – Prepare Brew - run: | - brew --version - brew cleanup --prune=all -s - brew autoremove - brew untap homebrew/cask homebrew/core - brew update - if: ${{ matrix.os == 'macos-latest' }} - - name: MacOS - Prepare Erlang - run: brew install erlang rebar3 - if: ${{ matrix.os == 'macos-latest' }} - # caches - name: Restore _build uses: actions/cache@v4 @@ -69,7 +39,6 @@ jobs: with: path: ~/.cache/rebar3 key: rebar3-cache-for-os-${{matrix.os}}-otp-${{matrix.otp}}-rebar3-${{matrix.rebar3}}-hash-${{hashFiles('rebar.lock')}} - # tests - run: rebar3 as test get-deps - run: rebar3 as test compile @@ -87,13 +56,51 @@ jobs: env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - test-on-s390x: + MacOS: + name: OTP ${{matrix.otp}} / rebar3 ${{matrix.rebar3}} / OS ${{matrix.os}} + strategy: + fail-fast: false + matrix: + os: ['macos-14', 'macos-15'] + otp: ['27'] + rebar3: ['3.24.0'] + runs-on: ${{matrix.os}} + steps: + - uses: actions/checkout@v4 + # OS setups + - name: Prepare Brew + run: | + brew --version + brew cleanup --prune=all -s + brew autoremove + brew untap homebrew/cask homebrew/core + brew update + - run: brew install erlang rebar3 + # caches + - name: Restore _build + uses: actions/cache@v4 + with: + path: _build + key: _build-cache-for-os-${{matrix.os}}-otp-${{matrix.otp}}-rebar3-${{matrix.rebar3}}-hash-${{hashFiles('rebar.lock')}} + - name: Restore rebar3's cache + uses: actions/cache@v4 + with: + path: ~/.cache/rebar3 + key: rebar3-cache-for-os-${{matrix.os}}-otp-${{matrix.otp}}-rebar3-${{matrix.rebar3}}-hash-${{hashFiles('rebar.lock')}} + # tests + - run: rebar3 as test get-deps + - run: > + CFLAGS="-I/opt/homebrew/opt/openssl/include" + LDFLAGS="-L/opt/homebrew/opt/openssl/lib" + rebar3 as test compile + - run: rebar3 as test ct + + s390x: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - name: Setup emulator - run: | - sudo docker run --rm --privileged tonistiigi/binfmt:qemu-v9.2.0 + run: sudo docker run --rm --privileged tonistiigi/binfmt:qemu-v9.2.0 - name: Run build uses: uraimo/run-on-arch-action@v2.8.1 with: @@ -102,10 +109,48 @@ jobs: install: | apt-get update -y DEBIAN_FRONTEND=noninteractive apt-get install -y rebar3 gcc libssl-dev - run: | - echo "---rebar3 as test get-deps---" - rebar3 as test get-deps - echo "---rebar3 as test compile---" - rebar3 as test compile - echo "---rebar3 as test ct---" - rebar3 as test ct + run: rebar3 as test do get-deps, compile, ct + + # Windows: + # name: OTP ${{matrix.otp}} / rebar3 ${{matrix.rebar3}} / OS ${{matrix.os}} + # strategy: + # fail-fast: false + # matrix: + # os: ['windows-2022'] + # otp: ['27'] + # rebar3: ['3.24.0'] + # runs-on: ${{matrix.os}} + # steps: + # - uses: actions/checkout@v4 + # - uses: erlef/setup-beam@v1 + # with: + # otp-version: ${{matrix.otp}} + # rebar3-version: ${{matrix.rebar3}} + # - name: Windows - Enable Developer Command Prompt + # uses: ilammy/msvc-dev-cmd@v1 + # if: ${{ matrix.os == 'windows-2022' }} + # - name: Windows - Install openssl + # shell: pwsh + # run: | + # choco install openssl + # echo "OPENSSL_INSTALL_DIR=""C:\Program Files\OpenSSL""" >> $env:GITHUB_ENV + # if: ${{ matrix.os == 'windows-2022' }} + # # caches + # - name: Restore _build + # uses: actions/cache@v4 + # with: + # path: _build + # key: _build-cache-for-os-${{matrix.os}}-otp-${{matrix.otp}}-rebar3-${{matrix.rebar3}}-hash-${{hashFiles('rebar.lock')}} + # - name: Restore rebar3's cache + # uses: actions/cache@v4 + # with: + # path: ~/.cache/rebar3 + # key: rebar3-cache-for-os-${{matrix.os}}-otp-${{matrix.otp}}-rebar3-${{matrix.rebar3}}-hash-${{hashFiles('rebar.lock')}} + # # tests + # - run: dir "C:\Program Files\OpenSSL\" + # - run: rebar3 as test get-deps + # - run: > + # set CFLAGS="/I${OPENSSL_INSTALL_DIR}/include" & + # set LDFLAGS="/LIBPATH:${OPENSSL_INSTALL_DIR}/lib" & + # rebar3 as test compile + # - run: rebar3 as test ct