Simple encryption in Elixir - So you can stop storing passwords in plain-text

October 24, 2015

Of course you don’t know anyone that actually stores user passwords in plaintext, or database passwords directly in a repository, so this is more for those theoretical developers to provide them with just a little bit more security; without adding much more complexity

Before you get started, you will need a working Elixir environment. Once that is ready, let’s play with safetybox.

mix new myproj
cd myproj
vi mix.exs

  # Add safetybox to your dependencies
  defp deps do
    [
      {:safetybox, "~> 0.1.2" }
    ]
  end

Now you can compile your code and a start an IEX session.

mix deps.get
mix compile
iex -S mix

Encrypting User Data (one-way)

For one way encryption, e.g. storying user passwords, simply encrypt the string.

# Encrypt a password and store it in pwd
iex> pwd = Safetybox.encrypt("helloworld")
"fc5e038d38a57032085441e7fe7010b0"

# Later on, you can validate the user provided password
# against the encrypted stored password
# Oopses, not the same
iex> Safetybox.is_decrypted("goodbyeworld", pwd)
false

# Ok, validated!
iex> Safetybox.is_decrypted("helloworld", pwd)
true

Encrypting Config Data (two-way)

You will also want to encrypt configuration passwords, like those for databases. For this, you will need a secret and salt.

iex> enc = Safetybox.encrypt("helloworld", "MYSECRET", "MYSALT")
"dWlwZnh5QmlwOFBmYm1US0hWeUtTWG9adGpPZ3pOald6TFE1V25ZVWl1WT0tLXpSU2lhQzFQWDR0blc5VVNqZGV1b3c9PQ==--7C53B199CE26A6B39081236823329A606DFF37DF"
iex> dec = Safetybox.decrypt(enc, "MYSECRET", "MYSALT")
"helloworld"
iex> dec = Safetybox.decrypt(enc, "YOURSECRET", "YOURSALT")
:error

You can also run a mix command to generate your secret keys.

$ SECRET=MYSECRET SALT=MYSALT mix safetybox.encrypt helloworld
N2MwMUczREVCYU5zNXFUR0NtVFNZSEJEaWNETCtTWjJkZzNkeVptbWdEST0tLUhxRHB2R1ZxVUpNcmswWFRqdW9oa3c9PQ==--EAD3CEE629EC527E7C67C9E5AE1385D630BDB24A

You can now (more) safely store these encrypted strings directly in your configurations files, and in use environment variables to store your SECRET and SALT.

vi ./config/prod.exs
  config :myapp,
    secret: System.get_env("SECRET") || "myappsecret",
    salt: System.get_env("SALT") || "myappsalt",
    db_password: "Z3NLcGVwdjQ1UWtHL2lsUC9UN0xHQT09LS1lRFF0eUpJdmhObzZ6b2lZNzVQRlVBPT0=--68B020579898BCE71B01B7558DB9C0D3D9305350",
    db_user: "myapp"

And, then in your code you can decrypt the data using the Application.get_env function.

def conf(_env) do
  raw_password = Application.get_env(:myapp, :db_password)
  password = S.decrypt(raw_password, :myapp)
  parse_url "ecto://myapp:#{password}@localhost/myapp"
end

The code above is really only as secure as your SECRET and SALT, but it is much, much better than storing passwords directly in your database (no matter how small your project may be). For those trying out Elixir, safetybox provides a simple enough mechanism to encrypt your own passwords, as well as the passwords of your users.

Behind the scenes

I did not (nor should I) write the underlying algorithms for providing the encyrption. I simply wrapped available functions from other libraries in a slightly more user friendly form at the cost of somewhat reduced security.

The code is open sourced, and one-way encryption uses MD5 hashing combined with low level Erlang functions, shown below

def encrypt(plaintext) do
  :crypto.hash(:md5, plaintext)
    |> :erlang.bitstring_to_list
    |> Enum.map(&(:io_lib.format("~2.16.0b", [&1])))
    |> List.flatten
    |> :erlang.list_to_bitstring
end

The two-way encryption is a wrapper to cryptex which itself is a wrapper to crypto. The cryptex example was too cumbersome for my needs, so I encapsulated the Encryptor and KeyGenerator so that the only additional inputs were a SECRET and a SALT.

def encrypt(plaintext, secret, salt) when is_binary(secret) do
  secret
  |> K.generate(salt)
  |> E.new(K.generate(secret, "signed #{salt}"))
  |> E.encrypt_and_sign(plaintext)
end

The decryption function is simiarly wrapped.

If you disagree with me, then I invite you to fork the project and provide your own approach.

Resources

v0.7.2