Google HOTP/TOTP Two-factor Authentication for Clojure

Two Factor Authentication is an approach to authentication, by using two of the three valid authentication factors, something the user knows, something the user has, and something the user is. In this article we rely on something user knows (a password) and something user has (a phone).

Google Authenticator implements two types of passwords,

  • HOTP - HMAC-based One-Time Password, password changes with each call. Defined in RFC 4226.
  • TOTP - Time-based One-Time Password, password changes every 30 seconds. Defined in RFC 6238.

Process is actually pretty straightforward. The server and the user agree on a secret key to use as the seed value for the hashing function. You can either make the user type this into Google Authenticator or generate a QR code to automatically set it up.

Even though the passwords generated by the Google Authenticator may be random to each other, the sequence itself can be determined using the secret.

So once we have our device and the server in sync either by using system clock for TOTP or an index number for the next password in the sequence, for HOTP then the random numbers that the device creates, will be the same, random, numbers the server expects.

(defn secret-key []
  (let [buff (make-array Byte/TYPE 10)]
    (-> (java.security.SecureRandom.)
        (.nextBytes buff))

    (-> (org.apache.commons.codec.binary.Base32.)
        (.encode buff)
        (String.))))

(defn qr-code [user host secret]
  (format (str "https://www.google.com/chart?chs=200x200&chld=M%%7C0&cht=qr"
               "&chl=otpauth://totp/%s@%s%%3Fsecret%%3D%s")
          user host secret))

Shared secret is generated by taking 10 random bytes and converting it to a base32 encoded string. This results in a string of 16 characters which is what Google Authenticator expects the user to enter it.

(def usr-secret-key (secret-key))
(clojure.java.browse/browse-url (qr-code "nurullah" "nakkaya.com" usr-secret-key))

If you scan the QR code generated from the above snippet. It will setup an account for [email protected] using TOTP.

(defn hotp-token [secret idx]
  (let [secret (-> (org.apache.commons.codec.binary.Base32.)
                   (.decode secret))
        idx (-> (java.nio.ByteBuffer/allocate 8)
                (.putLong idx)
                (.array))
        key-spec (javax.crypto.spec.SecretKeySpec. secret "HmacSHA1")
        mac (doto (javax.crypto.Mac/getInstance "HmacSHA1")
              (.init key-spec))
        hash (->> (.doFinal mac idx)
                  (into []))]

    (let [offset (bit-and (hash 19) 0xf)
          bin-code (bit-or (bit-shift-left (bit-and (hash offset) 0x7f) 24)
                           (bit-shift-left (bit-and (hash (+ offset 1)) 0xff) 16)
                           (bit-shift-left (bit-and (hash (+ offset 2)) 0xff) 8)
                           (bit-and (hash (+ offset 3)) 0xff))]
      (format "%06d" (mod bin-code 1000000)))))

First we decode the secret back to a byte array and convert the counter to a byte array so we can hash it. Using HMAC-SHA-1 we compute the hash for the current value of the counter. Finally we extract a six digit password from the hash. Actual digit calculation is straight from the RFC quoted below,

5.4.  Example of HOTP Computation for Digit = 6

   The following code example describes the extraction of a dynamic
   binary code given that hmac_result is a byte array with the HMAC-
   SHA-1 result:

        int offset   =  hmac_result[19] & 0xf ;
        int bin_code = (hmac_result[offset]  & 0x7f) << 24
           | (hmac_result[offset+1] & 0xff) << 16
           | (hmac_result[offset+2] & 0xff) <<  8
           | (hmac_result[offset+3] & 0xff) ;

   SHA-1 HMAC Bytes (Example)

   -------------------------------------------------------------
   | Byte Number                                               |
   -------------------------------------------------------------
   |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|
   -------------------------------------------------------------
   | Byte Value                                                |
   -------------------------------------------------------------
   |1f|86|98|69|0e|02|ca|16|61|85|50|ef|7f|19|da|8e|94|5b|55|5a|
   -------------------------------***********----------------++|

   * The last byte (byte 19) has the hex value 0x5a.
   * The value of the lower 4 bits is 0xa (the offset value).
   * The offset value is byte 10 (0xa).
   * The value of the 4 bytes starting at byte 10 is 0x50ef7f19,
     which is the dynamic binary code DBC1.
   * The MSB of DBC1 is 0x50 so DBC2 = DBC1 = 0x50ef7f19 .
   * HOTP = DBC2 modulo 10^6 = 872921.

   We treat the dynamic binary code as a 31-bit, unsigned, big-endian
   integer; the first byte is masked with a 0x7f.

   We then take this number modulo 1,000,000 (10^6) to generate the 6-
   digit HOTP value 872921 decimal.
(defn totp-token [secret]
  (hotp-token secret (/ (System/currentTimeMillis) 1000 30)))

Time based one time password generation uses the counter based approach above. But instead of a counter value it uses Unix epoch. Google Authenticator uses the number of 30 seconds passed since the Unix epoch. One caveat with this approach is that client and server clocks needs to be in sync if not tokens generated by the server and the client won't match. You can combat this by checking +/- couple minutes of the current server time.

(totp-token usr-secret-key)