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
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
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.
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.