erlang-bcrypt is a wrapper around the OpenBSD Blowfish password hashing algorithm, as described in A Future-Adaptable Password Scheme by Niels Provos and David Mazieres.
This bcrypt repository at erlangpack is in active maintainance and used as the basis of the Hex package.
erlang-bcrypt is compatible with OTP 21.3 to 29.
Use version 1.0.3 on OTP versions before 21.3
In version 1.1.0 support for OTP 21.2 and earlier is removed due to the removal of erl_interface in OTP 23.
erlang-bcrypt is on Hex:
{deps, [
{bcrypt, "~> 2.0"}
]}.To use the master branch:
{deps, [
{bcrypt, {git, ".*", {git, "https://github.com/erlangpack/bcrypt.git", {branch, "master"}}}
]}.-
Build it (project uses rebar3, a Makefile is included):
make
-
Run it (simple way, starting sasl, crypto, poolboy and bcrypt):
$ ./rebar3 shell ===> Verifying dependencies... ===> Compiling bcrypt make: Nothing to be done for `all'. Erlang/OTP 28 [erts-16.4] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] Eshell V16.4 (press Ctrl+G to abort, type help(). for help) 1> application:ensure_all_started(bcrypt). {ok,[poolboy,bcrypt] 2>
Hash a password using a salt with the default number of rounds:
1> {ok, Salt} = bcrypt:gen_salt().
{ok,"$2b$12$JrJ8HaRoDZaCALBL08XAie"}
2> {ok, Hash} = bcrypt:hashpw("foo", Salt).
{ok,"$2b$12$JrJ8HaRoDZaCALBL08XAie1PQi.8QII9K0f05qhVSaHGOW56jnYW2"}Verify the password:
3> {ok, Hash} =:= bcrypt:hashpw("foo", Hash).
true
4> {ok, Hash} =:= bcrypt:hashpw("bar", Hash).
falseGenerate a PHP-compatible $2y$ hash using gen_salt/2:
{ok, PhpSalt} = bcrypt:gen_salt(12, '2y'),
{ok, PhpHash} = bcrypt:hashpw(<<"password">>, PhpSalt).Store PhpHash as the password hash; PHP can verify it with
password_verify().
The bcrypt application is configured by changing values in the application's environment:
default_log_rounds
Sets the default number of rounds which define the complexity of the
hash function. Defaults to 12.
mechanism
Selects nif (the default) or port. The NIF hashes inside the Erlang VM
on OTP dirty CPU schedulers. The port backend hashes in a pool of external
OS processes. Set this option before starting bcrypt; restart the bcrypt
application after changing it.
pool_size
Sets the maximum number of port workers when using port. Defaults to 4.
nif_pool_size
Sets the number of Erlang workers in the NIF pool when using nif.
Defaults to 4. This does not set the number of OTP dirty CPU schedulers.
nif_pool_max_overflow
Sets the maximum number of additional temporary NIF pool workers when
the regular workers are busy. Defaults to 10.
New salts use $2b$. Hashing uses at most the first 72 password bytes,
following OpenBSD bcrypt. Passwords up to 1,024 bytes are accepted; longer
inputs return {error, invalid_password_length}. Malformed salts return
{error, invalid_salt}. These rules apply to both the NIF and port backends.
$2b$ and $2y$ reject embedded NUL bytes with {error, invalid_password}. Pass text
passwords as encoded bytes; do not pass raw binary prehashes containing NULs.
Existing $2a$ hashes retain historical password-length wraparound and NUL
termination semantics for verification. After a successful legacy verification,
generate a fresh salt and rehash with $2b$; do not just change the prefix.
Legacy hashes affected by those behaviors may warrant a password reset.
PHP $2y$ hashes are supported by both backends with the same hashing
semantics as $2b$. Pass the stored hash directly to bcrypt:hashpw/2;
the returned hash retains $2y$, so the usual verification pattern works
without rewriting stored prefixes:
%% StoredHash is the PHP hash as a byte list; Password contains the same
%% encoded bytes used in PHP (for example, a UTF-8 binary).
{ok, StoredHash} =:= bcrypt:hashpw(Password, StoredHash).gen_salt/0 and gen_salt/1 generate $2b$ salts. To generate a PHP
$2y$ hash directly, select the version with gen_salt(Rounds, Version):
{ok, Salt} = bcrypt:gen_salt(12, '2y'),
{ok, PhpHash} = bcrypt:hashpw(<<"password">>, Salt).The version must be the atom '2b' or '2y', and the cost must be an integer
from 4 to 31. Invalid options raise function_clause, as with gen_salt/1.
The option works with both backends. PhpHash retains $2y$ and can be
verified using PHP's password_verify().
The same 1,024-byte input limit and NUL-byte rejection apply to $2y$;
$2x$ remains unsupported.
The NIF uses OTP dirty CPU schedulers. Its asynchronous hashpw/5 interface
uses an Erlang helper process and an opaque reference from create_ctx/0;
there are no custom native threads, task queues, or blocking destructors.
The public pool still limits concurrent requests. Killing a caller does not
cancel an already running hash; applications should restrict accepted costs
and rate-limit authentication requests. Contexts created before this change
must be recreated when upgrading the application.
The C core incorporates OpenBSD bcrypt.c revision 1.58, including $2b$,
strict salt parsing, decoder error checking, output bounds, and secure clearing,
with a bounded input adapter shared by both backends. Upstream source:
OpenBSD bcrypt.c.
Run make security-test for native boundary checks under ASan/UBSan. Run
rebar3 as test eunit for compatibility, malformed-input, and lifecycle tests.
The lifecycle regression can also be run with ERL_FLAGS="+S 1" to verify
that owner exit does not block the sole normal scheduler.
To run the eunit and proper tests use:
make testTo test all exported function of a module use:
$ ./rebar3 as test shell
===> Verifying dependencies...
===> Analyzing applications...
===> Compiling bcrypt
make: Nothing to be done for all.
Erlang/OTP 28 [erts-16.4] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit]
Eshell V16.4 (press Ctrl+G to abort, type help(). for help)
1> application:ensure_all_started(bcrypt).
{ok,[poolboy,bcrypt]}
2> proper:check_specs(bcrypt).
Testing bcrypt:gen_salt/0
....................................................................................................
OK: Passed 100 test(s).
Testing bcrypt:is_worker_available/0
....................................................................................................
OK: Passed 100 test(s).
Testing bcrypt:gen_salt/2
....................................................................................................
OK: Passed 100 test(s).
Testing bcrypt:hashpw/2
....................................................................................................
OK: Passed 100 test(s).
Testing bcrypt:gen_salt/1
....................................................................................................
OK: Passed 100 test(s).
Testing bcrypt:mechanism/0
....................................................................................................
OK: Passed 100 test(s).
[]
4> rebar3 edoc
rebar3 as edoc_private edoc
rebar3 ex_doc --output edoc
Hunter Morris & Mrinal Wadhwa.