Cryptographic hash functions in Elixir

Generating hex digests

First off, the md5 and sha1 cryptographic hash functions should only be used when security is not a requirement or when compatibility with legacy applications is needed. md5 was physically broken a very long time ago, and sha1 was theoretically broken back in 2005. When you need a hash for secure purposes, use a hash from the sha2 set (at least until sha3 starts appearing in libraries).

Versioning

This post applies to:

  • Elixir: v1.0.3
  • Erlang: OTP 17

Dropping down into Erlang

Now that is out of the way, the title is rather incorrect! To get access to these functions in Elixir we need to drop down into Erlang - however, that is incredibly easy. Most Erlang modules can be accessed from Elixir by simply using a colon in front of their lowercase module name. In this instance, we want :crypto.

Where :sha256 is used below, it can be swapped out for the following hash algorithms:

  • :md5
  • :ripemd160
  • :sha (SHA-1)
  • :sha224 (SHA-2)
  • :sha256 (SHA-2)
  • :sha384 (SHA-2)
  • :sha512 (SHA-2)

For more information, please see the Erlang Crypto module documentation.

Non-streaming hashing

Erlang offers up the crypto.hash/2 function for hashing when you have all the values you want to hash ready to go. In the following examples, we are calculating the sha256 hash of the binary string "whatever".

To get the binary hash:

iex(1)> :crypto.hash(:sha, "whatever")

And to get the hex digest from that:

iex(1)> :crypto.hash(:sha256, "whatever") |> Base.encode16

If you want it in lowercase, carry on the piping to String.downcase.

And finally, to show an example of hashing multiple things in a list:

iex(1)> :crypto.hash(:sha256, [3, "things", "!"]) |> Base.encode16

Streaming hashing

Erlang also allows us to build up a hash from multiple items when perhaps all the items are not available to you yet using the functions: crypto.hash_init/1, crypto.hash_update/2 & crypto.hash_final/1

The following it how you use the functions together procedurally (and is for example only, this is not proper Elixir form by any means):

sha = :crypto.hash_init(:sha256)
sha = :crypto.hash_update(sha, 2)
sha = :crypto.hash_update(sha, "things")
sha_binary = :crypto.hash_final(sha)
sha_hex = sha_binary |> Base.encode16 |> String.downcase

Here, we create a hash "context" using crypto.hash_init/1, and then pass that context into subsequent crypto.hash_update/2 calls along with the extra value to add to the hash. When we have finished adding all the items we want hashed, we simply run crypto.hash_final/1 with our created hash context to receive the hash binary, which can then easily be converted into a hex digest as shown.

One final thing: please don't use these for storing passwords, hashing algorithms are too fast, what you want is a key derivation function as they are more suitable for preventing brute forcing attacks - use extensive rounds of bcrypt or PBKDF2 instead.

Enjoy the post? You can follow me on twitter for more.