Index

Performing the Google Authenticator computation in Elixir

Sept 15, 2025

Google Authenticator and many compatible alternatives implement time-based one time passwords (TOTP) with a specific set of parameters for two-factor authentication (2FA). How difficult would it be to perform the calculation behind Google Authenticator in Elixir ?

A long time ago I wrote a similar article, The code behind Google Authenticator, which used Pharo Smalltalk as implementation language. It still is a good introduction so I won’t repeat the basics here.

Elixir is a functional programming language with some unique support for dealing with bits and bytes. We will work with the exact same example as in the original article.

To get started, the server offering the 2FA and the client authenticator need to exchange a secret. This is a 80-bit key generated by the server and shared by both parties.

Once set up, the authenticator generates codes that are checked by the server. As long as they use the same secret they originally shared and their clocks are in sync, their codes will match and authentication will succeed.

To exchange the 80-bit key, it is encoded in Base32, resulting in a 32 character string. The QR code that you see while using an Authenticator application includes this key.

The example key is HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ.

$ iex
Erlang/OTP 27 [erts-15.2.7.2] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] [dtrace]
Interactive Elixir (1.18.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> secret_key = Base.decode32!("HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ")
<<61, 198, 202, 164, 130, 74, 109, 40, 135, 103, 178, 51, 30, 32, 180, 49, 102, 203, 133, 217>>

The Base module contains functions to handle various encodings, including Base32. Elixir functions ending with an exclamation mark return a single value and use exception semantics. The other style is returning a tuple holding multiple values, { :ok , value } or { :error, error }.

The result is a binary, a BitString. Incidentally, in Elixir, Strings are also binaries. Sometimes this is weird, sometimes this is handy, most of the time, this is no problem at all.

Next, we’ll use a fixed time, to make the calculations repeatable. In the real implementation we need to use the clock time.

iex(2)> unix_time = 1478167454
1478167454
iex(3)> period = 30
30
iex(4)> moving_factor = div(unix_time, period)
49272248

The moving_factor is the number that we start with, which will remain the same in each period.

Next we convert this number to a 8-byte, 64-bit binary and perform a HMAC-SHA1 on it using our secret key.

iex(5)> bytes = <<moving_factor::integer-size(64)>>
<<0, 0, 0, 0, 2, 239, 213, 184>>
iex(6)> hmac = :crypto.mac(:hmac, :sha, secret_key, bytes)
<<127, 67, 212, 219, 236, 88, 54, 168, 158, 177, 36, 112, 177, 244, 112, 88, 224, 169, 130, 215>>

The special ::integer-size(64) syntax specifies that we want to convert an integer to a specific size of binary. Elixir is built on top of Erlang, the :crypto.mac function is part of the Erlang standard library, and we can call it directly.

Now we have a 20-byte hash, hmac, which will be reduced to a smaller number using a specific algorithm. The very last nibble (half byte) will be used as an offset to make a 4 byte selection from the hash. In this 4 byte selection, the top bit is ignored, so only 31 bits are to be used and converted to an integer.

iex(7)> <<_::binary-size(19), _::4, offset::4>> = hmac
<<127, 67, 212, 219, 236, 88, 54, 168, 158, 177, 36, 112, 177, 244, 112, 88, 224, 169, 130, 215>>
iex(8)> offset
7

This is weird, right ? Elixir’s equal sign is actually not just an assignment operator, but a match operator as well. Here the left hand side should be read as a template that should match the right hand side. The template consists of a binary with 19 bytes that we are not interested in (hence the underscore), then 4 bits that we are not interested in, and finally the last nibble that we are interested in as offset.

iex(9)> <<_::binary-size(offset), _::1, selection::integer-size(31), _::binary>> = hmac
<<127, 67, 212, 219, 236, 88, 54, 168, 158, 177, 36, 112, 177, 244, 112, 88, 224, 169, 130, 215>>
iex(10)> selection
681488676

We do something similar to apply the offset to make a sub selection as described earlier. Here the template is a binary with offset bytes that we not interested in, a bit we are not interested in, a 31-bit integer that becomes our selection, and finally an ignored tail of undetermined length.

iex(11)> digits = 6
6
iex(12)> rem(selection, 10 ** digits)
488676

The final step is to reduce the selection even further down to the required number of digits. And yes, 488676 is the correct result!

This is the full code as an Elixir script (GoogleAuthenticator.exs).

defmodule GoogleAuthenticator do
@moduledoc """
An implementation of the GoogleAuthenticator computation.
This is a time-based one time password (TOTP),
with a 80-bit secret key encoded in a Base32 string of 32 characters,
using HMAC-SHA1, a period of 30 and 6 digits.
https://en.wikipedia.org/wiki/Google_Authenticator
"""
@doc """
Generate a new GoogleAuthenticator code give a Base32 encoded password.
"""
def generate_code(secret_key_base32, opts \\ []) do
secret_key = Base.decode32!(secret_key_base32)
algorithm = Keyword.get(opts, :algorithm, :sha)
unix_time = Keyword.get(opts, :unix_time, DateTime.utc_now() |> DateTime.to_unix())
period = Keyword.get(opts, :period, 30)
digits = Keyword.get(opts, :digits, 6)
moving_factor = div(unix_time, period)
bytes = <<moving_factor::integer-size(64)>>
hmac = :crypto.mac(:hmac, algorithm, secret_key, bytes)
<<_::binary-size(19), _::4, offset::4>> = hmac
<<_::binary-size(offset), _::1, selection::integer-size(31), _::binary>> = hmac
rem(selection, 10 ** digits)
end
end

The generate_code function takes one required argument and an optional opts keyword list. If you are unfamiliar with Elixir, it is worth noting the following expression.

DateTime.utc_now() |> DateTime.to_unix()

The pipe operator, |>, takes the result of the first expression and sends it as first argument to the next expression. So basically it is the same as the nesting both expressions.

DateTime.to_unix(DateTime.utc_now())

In this case the difference is small, but it is a very common idiom in Elixir, supporting the functional programming style of applying functions.

We can use our module as follows.

$ iex GoogleAuthenticator.exs
Erlang/OTP 27 [erts-15.2.7.2] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] [dtrace]
Interactive Elixir (1.18.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> GoogleAuthenticator.generate_code("HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ", unix_time: 1478167454)
488676
iex(2)> GoogleAuthenticator.generate_code("HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ")
30293

We also need to add some padding to always end up with 6 digits.

iex(3)> 30293 |> Integer.to_string() |> String.pad_leading(6, "0")
"030293"