defmodule SimpleCrypto do
@moduledoc """
Simple crypto helpers for Elixir.
"""
@doc """
Encrypts `str` using `key` as the encryption key.
## Example
iex> SimpleCrypto.encrypt("Hi there", "secret key")
"qCgs4rfReY5nTX39uHwjww=="
"""
@spec encrypt(iodata, iodata) :: binary
def encrypt(str, key) do
encrypt_fun(key, str)
|> Base.encode64()
end
@doc """
Decrypts `str` using `key` as the decryption key.
## Example
iex> SimpleCrypto.decrypt("qCgs4rfReY5nTX39uHwjww==", "secret key")
"Hi there"
"""
@spec decrypt(iodata, iodata) :: binary
def decrypt(str, key) do
decrypted = decrypt_fun(key, str)
case Regex.run(~r/(\d+)\|/, decrypted) do
nil ->
decrypted
[match, len] ->
Kernel.binary_part(decrypted, byte_size(match), String.to_integer(len))
end
end
@doc """
Hashes `str` using the SHA256 algorithm.
## Example
iex> SimpleCrypto.sha256("Turn me into SHA256")
"87A3AABED406EFBCD4956E2E32E75948DB88E7ED35CACD4D8B66669EA849C102"
"""
@spec sha256(iodata) :: binary
def sha256(str) do
:crypto.hash(:sha256, str) |> Base.encode16()
end
@doc """
Hashes `str` using the SHA256 algorithm, then base64-encodes it.
## Example
iex> SimpleCrypto.sha256_base64("Turn me into base64-encoded SHA256")
"/UWKWh0NJFgCf3mWSIJiDuJA9HCY94T2l/XJ+CyreAM="
"""
@spec sha256_base64(iodata) :: binary
def sha256_base64(str) do
:crypto.hash(:sha256, str) |> Base.encode64()
end
@doc """
Use `key` to generate a hash value of `str` using the HMAC method.
## Example
iex> SimpleCrypto.hmac("HMAC me now!", "secret key")
"E7235176D81E29EC202B117324C7B3A2A6180F2A2A163D79E5A6BB58E7A61A7B"
"""
@spec hmac(iodata, iodata) :: binary
def hmac(str, key) do
hmac_fun(key, str)
|> Base.encode16()
end
@doc """
Use `key` to generate a base64-encoded hash value of `str` using the HMAC method
## Example
iex> SimpleCrypto.hmac_base64("HMAC and base64 me now!", "secret key")
"Xqsja2bp+jfleCkl4bRFZoyljM2RL0DC4PNBkTtKXrk="
"""
@spec hmac_base64(iodata, iodata) :: binary
def hmac_base64(str, key) do
hmac_fun(key, str)
|> Base.encode64()
end
@doc """
Generate a random string of `length` length.
## Example
iex> SimpleCrypto.rand_str(32)
"rvbAtDMdVPJu2J-QDyAxgOLAL0LQWL0w"
"""
@spec rand_str(pos_integer) :: binary
def rand_str(length) do
:crypto.strong_rand_bytes(length) |> Base.url_encode64() |> binary_part(0, length)
end
@doc """
Generate a random string of `length` length, consisting only of integers.
## Example
iex> SimpleCrypto.rand_int_str(6)
"811238"
"""
@spec rand_int_str(pos_integer) :: binary
def rand_int_str(num_of_digits) do
len = :math.pow(10, num_of_digits) |> round()
Enum.random(0..len)
|> Integer.to_string()
|> String.pad_leading(4, "0")
end
@doc """
Generate a random string of `length` length that is suitable to use as an OTP (One-time pad) code.
## Example
iex> SimpleCrypto.otp_rand_str(16)
"UXGMUXNUANHONKZR"
"""
@spec otp_rand_str(pos_integer) :: binary
def otp_rand_str(length \\ 16) do
String.replace(String.replace(String.upcase(rand_str(length)), ~r([-_]), "N"), ~r(\d), "U")
end
@doc """
Generate a random string of `length` length that is suitable to use as an
easily copy/pastable ID (no hyphens or other problematic characters).
## Example
iex> SimpleCrypto.id_rand_str(12)
"SWm6fDWvd4id"
"""
@spec id_rand_str(pos_integer) :: binary
def id_rand_str(length) do
String.replace(rand_str(length), ~r([-_]), "w")
end
@doc """
Pad the end of `str` using `padding`, so that the total length becomes a multiple of `width`.
## Example
iex> SimpleCrypto.pad_by_width("The length of this string is 76 before padding, 4 less than a multiple of 16", 16, ".")
"The length of this string is 76 before padding, 4 less than a multiple of 16...."
"""
@spec pad_by_width(iodata, pos_integer, iodata) :: binary
def pad_by_width(str, width \\ 16, padding \\ " ") do
str_len = byte_size(str)
case rem(str_len, width) do
0 -> str
remainder -> String.pad_trailing(str, str_len - remainder + width, padding)
end
end
@doc """
DEPRECATED: Use pad_by_width/3 instead.
"""
@spec pad(iodata, pos_integer, iodata) :: binary
def pad(str, width \\ 16, padding \\ " ") do
pad_by_width(str, width, padding)
end
if Code.ensure_loaded?(:crypto) and function_exported?(:crypto, :mac, 4) do
defp hmac_fun(key, str) do
:crypto.mac(:hmac, :sha256, key, str)
end
defp encrypt_fun(key, str) do
:crypto.crypto_one_time(
:aes_ecb,
String.slice(pad_by_width(key), 0, 32),
pad_by_width(Integer.to_string(byte_size(str)) <> "|" <> str),
true
)
end
defp decrypt_fun(key, str) do
:crypto.crypto_one_time(
:aes_ecb,
String.slice(pad_by_width(key), 0, 32),
pad_by_width(elem(Base.decode64(str), 1)),
false
)
end
else
defp hmac_fun(key, str) do
:crypto.hmac(:sha256, key, str)
end
defp encrypt_fun(key, str) do
:crypto.block_encrypt(
:aes_ecb,
String.slice(pad(key), 0, 32),
pad(Integer.to_string(byte_size(str)) <> "|" <> str)
)
end
defp decrypt_fun(key, str) do
:crypto.block_decrypt(
:aes_ecb,
String.slice(pad(key), 0, 32),
pad(elem(Base.decode64(str), 1))
)
end
end
end