1
\$\begingroup\$

I'm working on a Phoenix/Absinthe application and I thought to expose encrypted sequential IDs instead of UUIDs since these are a bit shorter. Encryption on Elixir/Erlang seems very hard, so I think I'll use UUIDs eventually.

Anyway I'd like to know how bad, from security perspective, is the solution I came up with:

defmodule MyAppWeb.GraphQL.Types.EncId do
  use Absinthe.Schema.Notation

  defp secret_key(len \\ 32) do
    Application.get_env(:my_app, MyAppWeb.Endpoint)
    |> Keyword.get(:secret_key_base, "")
    |> String.slice(0, len)
  end

  defp pad_bytes(binary, block \\ 16) do
    padding_bits =
      case rem(byte_size(binary), block) do
        0 -> 0
        r -> (block - r) * 8
      end

    <<0::size(padding_bits)>> <> binary
  end

  defp unpad_bytes(<<0, tail::bitstring>>), do: unpad_bytes(tail)
  defp unpad_bytes(binary), do: binary

  defp encrypt(raw_binary) do
    padded_binary = pad_bytes(raw_binary)

    :crypto.crypto_one_time(:aes_256_ecb, secret_key(), padded_binary, true)
  end

  defp decrypt(raw_enc) do
    :crypto.crypto_one_time(:aes_256_ecb, secret_key(), raw_enc, false)
    |> unpad_bytes()
    |> :erlang.binary_to_term()
  end

  def serialize(id) do
    id
    |> :erlang.term_to_binary()
    |> encrypt()
    |> Base.url_encode64(padding: false)
  end

  def parse(%{value: enc_id}) do
    try do
      {:ok, raw_enc} = Base.url_decode64(enc_id, padding: false)
      {:ok, decrypt(raw_enc)}
    rescue
      _ -> :error
    end
  end

  scalar :enc_id, name: "EncId" do
    serialize(&__MODULE__.serialize/1)

    parse(&__MODULE__.parse/1)
  end
end
\$\endgroup\$
2
\$\begingroup\$

I don't understand why encryption would be "hard" - the code above looks straightforward enough although I'm not sure you need the padding (never tried the new crypto APIs).

Do note that if you want to secure your UUIDs (why?), you probably want to add a random IV to your UUIDs otherwise you'll probably expose too much information - UUIDs have some bits very static. Generally speaking, rolling your own crypto protocol is fraught with error; I would use an existing library like Hashids (https://hexdocs.pm/hashids/Hashids.html) because that protocol has been analyzed and you're probably better off with the mild known weaknesses in such a library than some huge unknown weakness lurking in roll-your-own :).

But for all practical purposes, generating v4 UUIDs using a cryptographically strong RNG should be all you need.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.