Packages

A replacement for gleam_crypto that works on all targets.

Current section

Files

Jump to
munch src munch.erl
Raw

src/munch.erl

-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"
" <style>\n"
" small {\n"
" font-size: 0.7em;\n"
" opacity: 0.75;\n"
" }\n"
" .goto-top {\n"
" display: block;\n"
" font-size: 0.85em;\n"
" text-align: right;\n"
" }\n"
" h4 {\n"
" margin-bottom: 0;\n"
" + p {\n"
" margin-top: 0;\n"
" }\n"
" }\n"
" </style>\n"
" <script>\n"
" (callback => document.readyState !== 'loading' ? callback() : document.addEventListener('DOMContentLoaded', callback, { once: true }))(() => {\n"
" const list = document.querySelector('.sidebar > ul:last-of-type')\n"
" const sortedLists = document.createDocumentFragment()\n"
" const sortedMembers = document.createDocumentFragment()\n"
"\n"
" for (const header of document.querySelectorAll('main > h4')) {\n"
" sortedLists.append((() => {\n"
" const node = document.createElement('h3')\n"
" node.append(header.textContent)\n"
" return node\n"
" })())\n"
" sortedMembers.append((() => {\n"
" const node = document.createElement('h2')\n"
" node.append(header.textContent)\n"
" return node\n"
" })())\n"
"\n"
" const sortedList = document.createElement('ul')\n"
" sortedLists.append(sortedList)\n"
"\n"
" for (const anchor of header.nextElementSibling.querySelectorAll('a')) {\n"
" const href = anchor.getAttribute('href')\n"
" const member = document.querySelector(`.member:has(h2 > a[href=\"${href}\"])`)\n"
" const sidebar = list.querySelector(`li:has(a[href=\"${href}\"])`)\n"
" sortedList.append(sidebar)\n"
" sortedMembers.append(member)\n"
" }\n"
" }\n"
"\n"
" document.querySelector('.sidebar').insertBefore(sortedLists, list)\n"
" document.querySelector('.module-members:has(#module-values)').insertBefore(sortedMembers, document.querySelector('#module-values').nextSibling)\n"
"\n"
" const goToTop = document.createElement('a')\n"
" goToTop.classList.add('goto-top')\n"
" goToTop.setAttribute('href', '#')\n"
" goToTop.textContent = 'Back to top ↑'\n"
" for (const member of document.querySelectorAll('.member')) {\n"
" member.insertBefore(goToTop.cloneNode(true), null)\n"
" }\n"
" })\n"
" </script>\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, <<Str/binary>>).
-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, <<Key/binary>>),
_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
{<<X, Left@1/binary>>, <<Y, Right@1/binary>>} ->
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(<<Protected/binary>>, 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(<<Input/binary>>, 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,
<<Signing_name/binary>>
) of
true ->
Challenge = hmac_bits(
<<Text/binary>>,
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"
" <https://erlang.org/doc/man/crypto.html#strong_rand_bytes-1>\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).