Packages

Cryptography library for Gleam targeting Erlang and JavaScript

Current section

Files

Jump to
kryptos src kryptos@crypto.erl
Raw

src/kryptos@crypto.erl

-module(kryptos@crypto).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/kryptos/crypto.gleam").
-export([hash/2, hmac/3, hkdf/5, concat_kdf/4, pbkdf2/5, random_bytes/1, random_uuid/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" The crypto module provides cryptographic primitives.\n"
"\n"
" - **One-shot hashing** via `hash()` and **HMAC** via `hmac()`\n"
" - **Key derivation**: HKDF (RFC 5869), PBKDF2 (RFC 8018), Concat KDF (NIST SP 800-56A)\n"
" - **Random bytes** via `random_bytes()` and **UUID v4** via `random_uuid()`\n"
" - **Constant-time comparison** via `constant_time_equal()`\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import kryptos/crypto\n"
"\n"
" // Generate 32 random bytes (suitable for a 256-bit key)\n"
" let key = crypto.random_bytes(32)\n"
" ```\n"
).
-file("src/kryptos/crypto.gleam", 49).
?DOC(
" Computes the hash digest of input data in one call.\n"
"\n"
" ## Parameters\n"
" - `algorithm`: The hash algorithm to use\n"
" - `data`: The data to hash\n"
"\n"
" ## Returns\n"
" `Ok(BitArray)` containing the hash digest, or `Error(Nil)` if the hash\n"
" algorithm is not supported by the runtime.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import kryptos/crypto\n"
" import kryptos/hash\n"
"\n"
" let assert Ok(digest) = crypto.hash(hash.Sha256, <<\"hello\":utf8>>)\n"
" ```\n"
).
-spec hash(kryptos@hash:hash_algorithm(), bitstring()) -> {ok, bitstring()} |
{error, nil}.
hash(Algorithm, Data) ->
gleam@result:'try'(
kryptos@hash:new(Algorithm),
fun(Hasher) -> _pipe = Hasher,
_pipe@1 = kryptos_ffi:hash_update(_pipe, Data),
_pipe@2 = kryptos_ffi:hash_final(_pipe@1),
{ok, _pipe@2} end
).
-file("src/kryptos/crypto.gleam", 76).
?DOC(
" Computes the HMAC of input data in one call.\n"
"\n"
" ## Parameters\n"
" - `algorithm`: The hash algorithm to use for the HMAC\n"
" - `key`: The secret key for authentication\n"
" - `data`: The data to authenticate\n"
"\n"
" ## Returns\n"
" `Ok(BitArray)` containing the message authentication code, or\n"
" `Error(Nil)` if the hash algorithm is not supported.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import kryptos/crypto\n"
" import kryptos/hash\n"
"\n"
" let assert Ok(mac) = crypto.hmac(hash.Sha256, key: <<\"secret\":utf8>>, data: <<\"hello\":utf8>>)\n"
" ```\n"
).
-spec hmac(kryptos@hash:hash_algorithm(), bitstring(), bitstring()) -> {ok,
bitstring()} |
{error, nil}.
hmac(Algorithm, Key, Data) ->
gleam@result:'try'(
kryptos@hmac:new(Algorithm, Key),
fun(Hmac) -> _pipe = Hmac,
_pipe@1 = crypto:mac_update(_pipe, Data),
_pipe@2 = crypto:mac_final(_pipe@1),
{ok, _pipe@2} end
).
-file("src/kryptos/crypto.gleam", 118).
?DOC(
" Derives key material using HKDF (RFC 5869).\n"
"\n"
" HKDF combines an extract-then-expand approach to derive cryptographically\n"
" strong key material from input key material.\n"
"\n"
" ## Parameters\n"
" - `algorithm`: The hash algorithm to use (must be HMAC-compatible)\n"
" - `input`: Input key material (IKM) - the source keying material\n"
" - `salt`: Optional salt value (None uses hash-length zeros per RFC 5869)\n"
" - `info`: Context and application specific information\n"
" - `length`: Desired output length in bytes (max: 255 * hash_length)\n"
"\n"
" ## Returns\n"
" `Ok(BitArray)` containing the derived key material of the requested\n"
" length, or `Error(Nil)` if the algorithm is not supported or length\n"
" exceeds the maximum.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import gleam/option\n"
" import kryptos/crypto\n"
" import kryptos/hash\n"
"\n"
" let ikm = crypto.random_bytes(32)\n"
" let salt = option.Some(crypto.random_bytes(16))\n"
" let assert Ok(derived) =\n"
" crypto.hkdf(hash.Sha256, input: ikm, salt:, info: <<\"app\":utf8>>, length: 32)\n"
" ```\n"
).
-spec hkdf(
kryptos@hash:hash_algorithm(),
bitstring(),
gleam@option:option(bitstring()),
bitstring(),
integer()
) -> {ok, bitstring()} | {error, nil}.
hkdf(Algorithm, Ikm, Salt, Info, Length) ->
Hash_len = kryptos@hash:byte_size(Algorithm),
Max_length = 255 * Hash_len,
Salt_bytes = gleam@option:lazy_unwrap(
Salt,
fun() -> _pipe = gleam@list:repeat(<<0>>, Hash_len),
gleam_stdlib:bit_array_concat(_pipe) end
),
case {kryptos@hmac:supported_hash(Algorithm),
Length > 0,
Length =< Max_length} of
{true, true, true} ->
kryptos@internal@hkdf:do_derive(
Algorithm,
Ikm,
Salt_bytes,
Info,
Length
);
{_, _, _} ->
{error, nil}
end.
-file("src/kryptos/crypto.gleam", 188).
-spec concat_kdf_supported_hash(kryptos@hash:hash_algorithm()) -> boolean().
concat_kdf_supported_hash(Algorithm) ->
case Algorithm of
sha1 ->
true;
sha256 ->
true;
sha384 ->
true;
sha512 ->
true;
sha512x224 ->
true;
sha512x256 ->
true;
sha3x224 ->
true;
sha3x256 ->
true;
sha3x384 ->
true;
sha3x512 ->
true;
blake2b ->
false;
blake2s ->
false;
md5 ->
false;
{shake128, _} ->
false;
{shake256, _} ->
false
end.
-file("src/kryptos/crypto.gleam", 166).
?DOC(
" Derives key material using Concat KDF (NIST SP 800-56A). Also called the\n"
" single-step or one-step key derivation function.\n"
"\n"
" Concat KDF is a single-step key derivation function that uses a hash\n"
" function to derive key material from a shared secret and context-specific\n"
" information.\n"
"\n"
" ## Parameters\n"
" - `algorithm`: The hash algorithm to use (SHA-1, SHA-2, or SHA-3 family)\n"
" - `secret`: The shared secret from key agreement (e.g., ECDH)\n"
" - `info`: Context and application specific information\n"
" - `length`: Desired output length in bytes (max: 255 * hash_length)\n"
"\n"
" ## Returns\n"
" `Ok(BitArray)` containing the derived key material of the requested\n"
" length, or `Error(Nil)` if the algorithm is not supported or length\n"
" is invalid.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import kryptos/crypto\n"
" import kryptos/hash\n"
"\n"
" let secret = crypto.random_bytes(32)\n"
" let assert Ok(derived) =\n"
" crypto.concat_kdf(hash.Sha256, secret:, info: <<\"context\":utf8>>, length: 32)\n"
" ```\n"
).
-spec concat_kdf(
kryptos@hash:hash_algorithm(),
bitstring(),
bitstring(),
integer()
) -> {ok, bitstring()} | {error, nil}.
concat_kdf(Algorithm, Secret, Info, Length) ->
Max_length = 255 * kryptos@hash:byte_size(Algorithm),
case {concat_kdf_supported_hash(Algorithm),
Length > 0,
Length =< Max_length} of
{true, true, true} ->
kryptos@internal@concat_kdf:derive_loop(
Algorithm,
Secret,
Info,
Length,
1,
gleam@bytes_tree:new()
);
{_, _, _} ->
{error, nil}
end.
-file("src/kryptos/crypto.gleam", 247).
?DOC(
" Derives key material from a password using PBKDF2 (RFC 8018).\n"
"\n"
" PBKDF2 applies a pseudorandom function (HMAC) to derive keys from passwords.\n"
" It is designed to be computationally expensive to resist brute-force attacks.\n"
"\n"
" **Note:** For password hashing in production applications, consider using\n"
" [Argus](https://github.com/Pevensie/argus) which provides Argon2 an\n"
" algorithm specifically designed for password storage. PBKDF2 is primarily\n"
" useful for interoperability with systems that require it.\n"
"\n"
" ## Parameters\n"
" - `algorithm`: The hash algorithm to use for HMAC (must be HMAC-compatible).\n"
" SHA-256 or stronger is recommended; MD5 and SHA-1 are weak for password hashing.\n"
" - `password`: The password to derive the key from\n"
" - `salt`: A random salt value (should be unique per password)\n"
" - `iterations`: Number of iterations (higher = slower but more secure)\n"
" - `length`: Desired output length in bytes\n"
"\n"
" ## Returns\n"
" `Ok(BitArray)` containing the derived key material of the requested\n"
" length, or `Error(Nil)` if the algorithm is not supported,\n"
" iterations <= 0, or length <= 0.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import kryptos/crypto\n"
" import kryptos/hash\n"
"\n"
" let salt = crypto.random_bytes(16)\n"
" let assert Ok(derived) =\n"
" crypto.pbkdf2(\n"
" hash.Sha256,\n"
" password: <<\"hunter2\":utf8>>,\n"
" salt:,\n"
" iterations: 100_000,\n"
" length: 32,\n"
" )\n"
" ```\n"
).
-spec pbkdf2(
kryptos@hash:hash_algorithm(),
bitstring(),
bitstring(),
integer(),
integer()
) -> {ok, bitstring()} | {error, nil}.
pbkdf2(Algorithm, Password, Salt, Iterations, Length) ->
case {kryptos@hmac:supported_hash(Algorithm), Iterations > 0, Length > 0} of
{true, true, true} ->
kryptos_ffi:pbkdf2_derive(
Algorithm,
Password,
Salt,
Iterations,
Length
);
{_, _, _} ->
{error, nil}
end.
-file("src/kryptos/crypto.gleam", 272).
?DOC(
" Generates cryptographically secure random bytes using the platform's\n"
" cryptographically secure random number generator.\n"
"\n"
" ## Parameters\n"
" - `length`: The number of random bytes to generate. If negative, returns\n"
" an empty `BitArray`.\n"
"\n"
" ## Returns\n"
" A `BitArray` containing the generated random bytes.\n"
).
-spec random_bytes(integer()) -> bitstring().
random_bytes(Length) ->
kryptos_ffi:random_bytes(Length).
-file("src/kryptos/crypto.gleam", 279).
?DOC(
" Generates a cryptographically secure random UUID v4.\n"
"\n"
" ## Returns\n"
" A `String` containing a UUID v4.\n"
).
-spec random_uuid() -> binary().
random_uuid() ->
{A@1, B@1, C_raw@1, D_raw@1, E@1} = case kryptos_ffi:random_bytes(16) of
<<A:32, B:16, C_raw:16, D_raw:16, E:48>> -> {A, B, C_raw, D_raw, E};
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"kryptos/crypto"/utf8>>,
function => <<"random_uuid"/utf8>>,
line => 280,
value => _assert_fail,
start => 8454,
'end' => 8524,
pattern_start => 8465,
pattern_end => 8505})
end,
C = erlang:'bor'(erlang:'band'(C_raw@1, 16#0FFF), 16#4000),
D = erlang:'bor'(erlang:'band'(D_raw@1, 16#3FFF), 16#8000),
Uuid = <<<<<<<<<<<<<<<<(gleam@string:pad_start(
gleam@int:to_base16(A@1),
8,
<<"0"/utf8>>
))/binary,
"-"/utf8>>/binary,
(gleam@string:pad_start(
gleam@int:to_base16(B@1),
4,
<<"0"/utf8>>
))/binary>>/binary,
"-"/utf8>>/binary,
(gleam@string:pad_start(
gleam@int:to_base16(C),
4,
<<"0"/utf8>>
))/binary>>/binary,
"-"/utf8>>/binary,
(gleam@string:pad_start(gleam@int:to_base16(D), 4, <<"0"/utf8>>))/binary>>/binary,
"-"/utf8>>/binary,
(gleam@string:pad_start(gleam@int:to_base16(E@1), 12, <<"0"/utf8>>))/binary>>,
string:lowercase(Uuid).