-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, 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 => <>, 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).