-module(munch). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/munch.gleam"). -export([init_md5/0, init_sha1/0, init_sha256/0, init_sha224/0, init_sha512/0, init_sha384/0, digest/1, update/2, new_hash/1, hash/2, digest_bits/1, update_bits/2, hash_bits/2, new_hmac/2, hmac/3, hmac_bits/3, secure_compare/2, sign_message/3, verify_signed_message/3, strong_random_bytes/1]). -export_type([hash_algorithm/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( " Cryptographic hash functions and related utilities.\n" "\n" " \n" " \n" "\n" " #### Hash Algorithms\n" " [md5](#md5 \"The MD5 hash algorithm\"),\n" " [sha1](#sha1 \"The SHA-1 hash algorithm\"),\n" " [sha224](#sha224 \"The SHA-224 hash algorithm\"),\n" " [sha256](#sha256 \"The SHA-256 hash algorithm\"),\n" " [sha384](#sha384 \"The SHA-384 hash algorithm\"),\n" " [sha512](#sha512 \"The SHA-512 hash algorithm\")\n" "\n" " #### Block API\n" " [hash](#hash \"Hash a UTF-8 encoded string\"),\n" " [hash_bits](#hash_bits \"Hash a bit string\"),\n" " [hmac](#hmac \"Authenticate a UTF-8 encoded string\"),\n" " [hmac_bits](#hmac_bits \"Authenticate a bit string\")\n" "\n" " #### Stream API\n" " [new_hash](#new_hash \"Start a streaming hash\"),\n" " [new_hmac](#new_hmac \"Start a streaming HMAC\"),\n" " [update](#update \"Add a UTF-8 encoded string\"),\n" " [update_bits](#update_bits \"Add binary data\"),\n" " [digest](#digest \"Return the base16 encoded digest\"),\n" " [digest_bits](#digest_bits \"Return the binary digest\")\n" "\n" " #### Utils\n" " [secure_compare](#secure_compare \"Compare two binaries in constant time\"),\n" " [sign_message](#sign_message \"Sign a message\"),\n" " [verify_signed_message](#verify_signed_message \"Verify a signed message\"),\n" " [strong_random_bytes](#strong_random_bytes \"Generate cryptographically secure random bytes\")\n" ). -opaque hash_algorithm() :: {hash_algorithm, fun(() -> munch@internal@hash:hash()), integer(), binary()}. -file("src/munch.gleam", 133). -spec init_md5() -> munch@internal@hash:hash(). init_md5() -> gleam@result:lazy_unwrap( munch@internal@native:md5(), fun munch@internal@md5:new/0 ). -file("src/munch.gleam", 149). -spec init_sha1() -> munch@internal@hash:hash(). init_sha1() -> gleam@result:lazy_unwrap( munch@internal@native:sha1(), fun munch@internal@sha1:new/0 ). -file("src/munch.gleam", 173). -spec init_sha256() -> munch@internal@hash:hash(). init_sha256() -> gleam@result:lazy_unwrap( munch@internal@native:sha256(), fun munch@internal@sha256:new/0 ). -file("src/munch.gleam", 161). -spec init_sha224() -> munch@internal@hash:hash(). init_sha224() -> gleam@result:lazy_unwrap( munch@internal@native:sha224(), fun munch@internal@sha256:new224/0 ). -file("src/munch.gleam", 197). -spec init_sha512() -> munch@internal@hash:hash(). init_sha512() -> gleam@result:lazy_unwrap( munch@internal@native:sha512(), fun munch@internal@sha512:new/0 ). -file("src/munch.gleam", 185). -spec init_sha384() -> munch@internal@hash:hash(). init_sha384() -> gleam@result:lazy_unwrap( munch@internal@native:sha384(), fun munch@internal@sha512:new384/0 ). -file("src/munch.gleam", 368). ?DOC( " Finalizes a streaming hash or HMAC calculation and returns its base16\n" " encoded digest.\n" "\n" " See `new_hash` and `new_hmac` for more information and examples.\n" ). -spec digest(munch@internal@hash:hash()) -> binary(). digest(Hash) -> gleam_stdlib:base16_encode(munch@internal@hash:finish(Hash)). -file("src/munch.gleam", 350). ?DOC( " Adds a UTF-8 encoded string to a streaming hash or HMAC calculation.\n" "\n" " See `new_hash` and `new_hmac` for more information and examples.\n" ). -spec update(munch@internal@hash:hash(), binary()) -> munch@internal@hash:hash(). update(Hash, Str) -> munch@internal@hash:update(Hash, <>). -file("src/munch.gleam", 321). ?DOC( " Initializes the state for a streaming hash digest calculation.\n" "\n" " Add data using `update_bits` or `update`, then retrieve the digest using\n" " `digest_bits` or `digest`.\n" "\n" " This is useful for hashing streams or large amounts of data without loading\n" " all of it into memory at once.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " import munch\n" "\n" " let digest =\n" " munch.new_hash(munch.sha512)\n" " |> munch.update(\"data to hash\")\n" " |> munch.update(\"more data\")\n" " |> munch.digest\n" " ```\n" ). -spec new_hash(hash_algorithm()) -> munch@internal@hash:hash(). new_hash(Algorithm) -> (erlang:element(2, Algorithm))(). -file("src/munch.gleam", 216). ?DOC( " Computes the base16 encoded digest of a UTF-8 encoded string.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " import munch\n" "\n" " assert munch.hash(munch.sha256, \"a\")\n" " == \"CA978112CA1BBDCAFAC231B39A23DC4DA786EFF8147C4E72B9807785AFEE48BB\"\n" " ```\n" "\n" " To hash content in multiple chunks, see the `new_hash` function.\n" ). -spec hash(hash_algorithm(), binary()) -> binary(). hash(Algorithm, String) -> _pipe = Algorithm, _pipe@1 = new_hash(_pipe), _pipe@2 = update(_pipe@1, String), digest(_pipe@2). -file("src/munch.gleam", 359). ?DOC( " Finalizes a streaming hash or HMAC calculation and returns its binary\n" " digest.\n" "\n" " See `new_hash` and `new_hmac` for more information and examples.\n" ). -spec digest_bits(munch@internal@hash:hash()) -> bitstring(). digest_bits(Hash) -> munch@internal@hash:finish(Hash). -file("src/munch.gleam", 342). ?DOC( " Adds binary data to a streaming hash or HMAC calculation.\n" "\n" " See `new_hash` and `new_hmac` for more information and examples.\n" ). -spec update_bits(munch@internal@hash:hash(), bitstring()) -> munch@internal@hash:hash(). update_bits(Hash, Bits) -> munch@internal@hash:update(Hash, Bits). -file("src/munch.gleam", 242). ?DOC( " Computes the binary digest of a bit string.\n" "\n" " ## Returns\n" "\n" " The digest as a `BitArray`.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " import gleam/bit_array\n" " import munch\n" "\n" " assert munch.hash_bits(munch.sha256, <<\"a\":utf8>>)\n" " |> bit_array.base16_encode\n" " == \"CA978112CA1BBDCAFAC231B39A23DC4DA786EFF8147C4E72B9807785AFEE48BB\"\n" " ```\n" "\n" " To hash content in multiple chunks, see the `new_hash` function.\n" ). -spec hash_bits(hash_algorithm(), bitstring()) -> bitstring(). hash_bits(Algorithm, Bits) -> _pipe = Algorithm, _pipe@1 = new_hash(_pipe), _pipe@2 = update_bits(_pipe@1, Bits), digest_bits(_pipe@2). -file("src/munch.gleam", 333). ?DOC( " Initializes a streaming HMAC (hash-based message authentication code).\n" "\n" " Add data using `update_bits` or `update`, then retrieve the authentication\n" " code using `digest_bits` or `digest`.\n" "\n" " If the key does not contain a whole number of bytes, its final byte is\n" " padded with trailing zero bits before use.\n" ). -spec new_hmac(hash_algorithm(), bitstring()) -> munch@internal@hash:hash(). new_hmac(Algorithm, Key) -> {hash_algorithm, Init, Block_size, _} = Algorithm, munch@internal@hmac:new(Init, Block_size, Key). -file("src/munch.gleam", 263). ?DOC( " Computes the base16 encoded HMAC of UTF-8 encoded data using a UTF-8\n" " encoded key.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " import munch\n" "\n" " assert munch.hmac(\"message\", munch.sha256, \"key\")\n" " == \"6E9EF29B75FFFC5B7ABAE527D58FDADB2FE42E7219011976917343065F58ED4A\"\n" " ```\n" "\n" " To authenticate content in multiple chunks, see the `new_hmac` function.\n" ). -spec hmac(binary(), hash_algorithm(), binary()) -> binary(). hmac(Data, Algorithm, Key) -> _pipe = Algorithm, _pipe@1 = new_hmac(_pipe, <>), _pipe@2 = update(_pipe@1, Data), digest(_pipe@2). -file("src/munch.gleam", 288). ?DOC( " Computes the binary HMAC of a bit string.\n" "\n" " If the key does not contain a whole number of bytes, its final byte is\n" " padded with trailing zero bits before use.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " import gleam/bit_array\n" " import munch\n" "\n" " assert munch.hmac_bits(<<\"message\":utf8>>, munch.sha256, <<\"key\":utf8>>)\n" " |> bit_array.base16_encode\n" " == \"6E9EF29B75FFFC5B7ABAE527D58FDADB2FE42E7219011976917343065F58ED4A\"\n" " ```\n" "\n" " To authenticate content in multiple chunks, see the `new_hmac` function.\n" ). -spec hmac_bits(bitstring(), hash_algorithm(), bitstring()) -> bitstring(). hmac_bits(Data, Algorithm, Key) -> _pipe = Algorithm, _pipe@1 = new_hmac(_pipe, Key), _pipe@2 = update_bits(_pipe@1, Data), digest_bits(_pipe@2). -file("src/munch.gleam", 385). -spec do_secure_compare(bitstring(), bitstring(), integer()) -> boolean(). do_secure_compare(Left, Right, Accumulator) -> case {Left, Right} of {<>, <>} -> Accumulator@1 = munch@internal@bitwise:'bor'( Accumulator, munch@internal@bitwise:'xor'(X, Y) ), do_secure_compare(Left@1, Right@1, Accumulator@1); {_, _} -> (Left =:= Right) andalso (Accumulator =:= 0) end. -file("src/munch.gleam", 378). ?DOC( " Compares two binaries in constant-time to avoid timing attacks.\n" "\n" " For more details see: http://codahale.com/a-lesson-in-timing-attacks/\n" ). -spec secure_compare(bitstring(), bitstring()) -> boolean(). secure_compare(Left, Right) -> case erlang:byte_size(Left) =:= erlang:byte_size(Right) of true -> do_secure_compare(Left, Right, 0); false -> false end. -file("src/munch.gleam", 455). -spec signing_input(hash_algorithm(), bitstring()) -> binary(). signing_input(Algorithm, Message) -> {hash_algorithm, _, _, Protected} = Algorithm, erlang:list_to_binary( [gleam@bit_array:base64_url_encode(<>, false), <<"."/utf8>>, gleam@bit_array:base64_url_encode(Message, false)] ). -file("src/munch.gleam", 408). ?DOC( " Signs a message which can later be verified using the\n" " `verify_signed_message` function to detect if the message has been tampered\n" " with.\n" "\n" " A web application could use this verifier to sign HTTP cookies. The data can\n" " be read by the user, but cannot be tampered with.\n" ). -spec sign_message(bitstring(), bitstring(), hash_algorithm()) -> binary(). sign_message(Message, Secret, Algorithm) -> Input = signing_input(Algorithm, Message), Signature = hmac_bits(<>, Algorithm, Secret), erlang:list_to_binary( [Input, <<"."/utf8>>, gleam@bit_array:base64_url_encode(Signature, false)] ). -file("src/munch.gleam", 425). ?DOC( " Verifies a message created by the `sign_message` function.\n" "\n" " The hash algorithm must be the same one that was used to sign the message.\n" ). -spec verify_signed_message(binary(), bitstring(), hash_algorithm()) -> {ok, bitstring()} | {error, nil}. verify_signed_message(Message, Secret, Algorithm) -> gleam@result:'try'(case gleam@string:split(Message, <<"."/utf8>>) of [Protected, Payload, Signature] -> {ok, {Protected, Payload, Signature}}; _ -> {error, nil} end, fun(_use0) -> {Protected@1, Payload@1, Signature@1} = _use0, Text = erlang:list_to_binary([Protected@1, <<"."/utf8>>, Payload@1]), gleam@result:'try'( gleam@bit_array:base64_url_decode(Payload@1), fun(Payload@2) -> gleam@result:'try'( gleam@bit_array:base64_url_decode(Signature@1), fun(Signature@2) -> gleam@result:'try'( gleam@bit_array:base64_url_decode(Protected@1), fun(Protected@2) -> {hash_algorithm, _, _, Signing_name} = Algorithm, case secure_compare( Protected@2, <> ) of true -> Challenge = hmac_bits( <>, Algorithm, Secret ), case secure_compare( Challenge, Signature@2 ) of true -> {ok, Payload@2}; false -> {error, nil} end; false -> {error, nil} end end ) end ) end ) end). -file("src/munch.gleam", 478). ?DOC( " Generates a specified number of bytes randomly uniform 0..255, and returns\n" " the result in a binary.\n" "\n" " On Erlang this uses a cryptographically secure PRNG seeded and periodically\n" " mixed with operating system provided entropy. By default this is the\n" " `RAND_bytes` method from OpenSSL.\n" " \n" "\n" " On JavaScript the WebCrypto API is used.\n" ). -spec strong_random_bytes(integer()) -> bitstring(). strong_random_bytes(Length) -> crypto:strong_rand_bytes(Length).