lib/simple_crypto.ex

defmodule SimpleCrypto do
  require Logger

  def encrypt(str, key) do
    Base.encode64(
      :crypto.crypto_one_time(
        :aes_ecb,
        String.slice(pad(key), 0, 32),
        pad(Integer.to_string(byte_size(str)) <> "|" <> str),
        true
      )
    )
  end

  def decrypt(str, key) do
    decrypted =
      :crypto.crypto_one_time(
        :aes_ecb,
        String.slice(pad(key), 0, 32),
        pad(elem(Base.decode64(str), 1)),
        false
      )

    case Regex.run(~r/(\d+)\|/, decrypted) do
      nil -> decrypted
      [match, len] -> Kernel.binary_part(decrypted, byte_size(match), String.to_integer(len))
    end
  end

  def sha256(str) do
    :crypto.hash(:sha256, str) |> Base.encode16()
  end

  def hmac(str, key) do
    :crypto.mac(:hmac, :sha256, key, str) |> Base.encode16()
  end

  def rand_str(length) do
    :crypto.strong_rand_bytes(length) |> Base.url_encode64() |> binary_part(0, length)
  end

  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

  def otp_rand_str(length \\ 16) do
    String.replace(String.replace(String.upcase(rand_str(length)), ~r([-_]), "N"), ~r(\d), "U")
  end

  def id_rand_str(length) do
    String.replace(rand_str(length), ~r([-_]), "w")
  end

  def pad(binary, width \\ 16, char \\ " ") do
    # TODO: Use String.pad_trailing/2 instead?
    case rem(width - rem(byte_size(binary), width), width) do
      0 -> binary
      n -> binary <> String.duplicate(char, n)
    end
  end
end