Emacs 29 Edition is out now!


Securely Generating TOTP tokens with Emacs

If you're tired of reaching for your smartphone authenticator or key fob to type in a TOTP, or Time-Based One-Time Password, then why not use Emacs to generate them for you instead? Combine it with auth sources and GPG, and you can securely encrypt the master keys at the same time.
Updated for Emacs 28
2 comments

I’ve previously written about using Emacs to encrypt and decrypt secrets. And by secrets I mean conceivably anything you like: ORG mode notes; sensitive elisp files; auth source or .netrc files; a text file with user names and passwords; or maybe your Remington Steele fan fiction… the sky is the limit.

Emacs leverages GnuPG to encrypt or decrypt files either symmetrically, using a fixed password; or asymmetrically, using keys or certificates.

So if you’re using Emacs as a chat or email client, then you can not only use Emacs’s auth sources to manage your credentials, but you can keep the file encrypted at the same time. The whole shebang also plugs into your operating system’s key agent (if you have one), making it an especially potent combination if you also use that.

Example workflow of TOTP generation inside Emacs
Invoking M-x totp-display and picking a valid TOTP candidate generates a valid code. Emacs automatically encrypts and decrypts the master key(s) as needed.

With that said, one thing Emacs cannot do out of the box is TOTP: Time-based One-Time Passwords. I’m sure you’re familiar with them by now: they’re the ever-changing 6-digit numbers generated by key fobs or authenticator apps on your phone.

But you can actually generate them yourself, anywhere and on any device, provided you have master code. The master code is usually given out by the identity provider when you set up 2-factor authentication. So if you’re in possession of the QR code or the text master code, you can generate these codes anywhere you like. The code is in human-readable base-32 format, and it’s usually in multiples of 8 characters.

And that, of course, means you can generate TOTP inside Emacs with a little bit of work: here’s a Python example, for instance.

Combine it with the aforementioned secure storage and you can securely generate codes from inside Emacs, safe in the knowledge that you have yet another method of accomplishing 2-factor authentication when you inevitably lose your phone or key fob.

Let me demonstrate…

Configuring TOTP

There’s a neat little package written by Jürgen Hötzel called totp.el. It’s also on MELPA. It’s definitely one of those “scratch your itch” type packages, and it’s not that well documented. It also makes a number of hardcoded assumptions that I don’t like.

Another thing to consider is whether you want a third-party package, that could change at any moment, to manage this; it’s rather important. So I’ve included the only required part for the totp generator below, so you don’t need the package if you don’t want to.

;; Taken from Jürgen Hötzel's `totp.el':
;; https://github.com/juergenhoetzel/emacs-totp
(require 'bindat)
(require 'gnutls)
(require 'hexl)
(require 'auth-source)

(defun totp--hex-decode-string (string)
  "Hex-decode STRING and return the result as a unibyte string."
  (apply #'unibyte-string
         (seq-map (lambda (s) (hexl-htoi (aref s 0) (aref s 1)))
                  (seq-partition string 2))))

(defun totp (string &optional time digits)
  "Return a TOTP token using the secret hex STRING and current time.
TIME is used as counter value instead of current time, if non-nil.
DIGITS is the number of pin digits and defaults to 6."
  (let* ((key-bytes (totp--hex-decode-string (upcase string)))
         (counter (truncate (/ (or time (time-to-seconds)) 30)))
         (digits (or digits 6))
         (format-string (format "%%0%dd" digits))
         ;; we have to manually split the 64 bit number (u64 not supported in Emacs 27.2)
         (counter-bytes (bindat-pack  '((:high u32) (:low u32))
                                      `((:high . ,(ash counter -32)) (:low . ,(logand counter #xffffffff)))))
         (mac (gnutls-hash-mac 'SHA1 key-bytes counter-bytes))
         (offset (logand (bindat-get-field (bindat-unpack '((:offset u8)) mac 19) :offset) #xf)))
    (format format-string
            (mod
             (logand (bindat-get-field (bindat-unpack '((:totp-pin u32)) mac  offset) :totp-pin)
                     #x7fffffff)
             (expt 10 digits)))))

The one thing the package – and Emacs – does not do is convert to base-32. Jürgen proposes the following command line query to work around that:

$ base32 -d <<<CODEHERE | xxd -g0 -p
HEX-STRING-OUTPUT-HERE

But that assumes you have all of those tools available. I’ve instead written a minimal version of base-32 that does the job. It’s not 100% compliant, but it does not matter for this task:

(defconst base32-alphabet
  (let ((tbl (make-char-table nil)))
    (dolist (mapping '(("A" . 0) ("B" . 1) ("C" . 2) ("D" . 3) 
                       ("E" . 4) ("F" . 5) ("G" . 6)
                       ("H" . 7) ("I" . 8) ("J" . 9) ("K" . 10) 
                       ("L" . 11) ("M" . 12) ("N" . 13)
                       ("O" . 14) ("P" . 15) ("Q" . 16) ("R" . 17) 
                       ("S" . 18) ("T" . 19) ("U" . 20)
                       ("V" . 21) ("W" . 22) ("X" . 23) ("Y" . 24) 
                       ("Z" . 25) ("2" . 26) ("3" . 27)
                       ("4" . 28) ("5" . 29) ("6" . 30) ("7" . 31)))
      (aset tbl (string-to-char (car mapping)) (cdr mapping)))
    tbl)
  "Base-32 mapping table, as defined in RFC 4648.")

(defun base32-hex-decode (string)
  "The cheats' version of base-32 decode.

This is not a 100% faithful implementation of RFC 4648. The
concept of encoding partial quanta is not implemented fully.

No attempt is made to pad the output either as that is not
required for HMAC-TOTP."
  (unless (mod (length string) 8)
    (error "Padding is incorrect"))
  (setq string (upcase string))
  (let ((trimmed-array (append (string-trim-right string "=+") nil)))
    (format "%X" (seq-reduce
                  (lambda (acc char) (+ (ash acc 5) (aref base32-alphabet char)))
                  trimmed-array 0))))

Evaluate the elisp and then test that it works:

IELM> (totp (base32-hex-decode "ABCDEFGH"))
"680341"

Note that as it’s timed the number you’ll get back is likely different than mine. The string ABCDEFGH is the secret code you get when you sign up for TOTP authentication. On Github, for example, you can request the text code alongside the QR code used with phone authenticators.

So that’s technically that. But let’s make it a bit nicer still. For one thing, we need a safe way of storing the secret codes.

As I see it, there are three options:

  1. Use Emacs’s auth sources as I explain in my article on auth sources and GnuPG.

    This works reasonably well, though the format is basic and does not natively support TOTP. Jürgen’s package works around that by prefixing the machine name with TOTP: which is one way of doing it.

  2. The other way is to have a separate elisp file with the codes in a simple LISP data structure. The only problem is keeping it encrypted in memory.

    This is something that auth sources does do, to at least deter basic memory snooping.

  3. Use your operating system’s key agent to store the secret. Emacs is able to make use of it if you set it up right. (Try M-x secrets-show-secret to see what I mean.)

But for simplicity’s sake, I think sticking with auth sources is the best approach. You can follow the article I wrote to set it up before you get started here.

Generating a TOTP with a Simple UI

TOTP Generation inside Emacs
M-x totp-display shows a list of valid TOTP candidates. Selecting one generates a TOTP for that entry.

Open your .authinfo.gpg file and add an entry like this:

machine TOTP:example.com login costanza password ABCD3456

Here I’m declaring a TOTP entry – note, again, that this prefix is a made-up construct and not intrinsically understood by auth sources – for the user costanza with the secret code ABCD3456.

NOTE: If you’re using Jürgen’s package, then your login must match your current user-login-name as that is a hardcoded assumption his package makes!

Save the .authinfo.gpg (or a file name of your choosing) with the added credentials, and don’t forget to customize auth-sources so Emacs knows about the file. Finally, type M-x auth-source-forget-all-cached so Emacs clears its cache.

The master code is now safe: it’s automatically encrypted and decrypted as needed and, if you’ve done it right, Emacs is able to retrieve it effortlessly:

IELM> (auth-source-search :host "TOTP:example.com" :max 100)
((:host "TOTP:example.com"
  :user "costanza"
  :secret #f(compiled-function () #<bytecode -0x934f39f>)))

As you can see, Emacs’s auth source library found it. It’s a wildcard search, so you can also pass additional options like :user, :port and :max to it. Note that the secret is a compiled function — it’s there to obscure the secret, so the only way to reveal it is to call the function.

Now we can build a little UI. I’ll demonstrate how with Emacs’s minibuffer completion:

(defun totp-display (auth)
  "Select a TOTP AUTH from `auth-sources' and display its TOTP."
  (interactive
   (list
    (let ((candidates (mapcar
           (lambda (auth)
             (cons (format "User '%s' on %s"
                           (propertize (plist-get auth :user) 'face 'font-lock-keyword-face)
                           (propertize (plist-get auth :host) 'face 'font-lock-string-face))
                   auth))
           (seq-filter (lambda (auth) (string-prefix-p "TOTP:" (plist-get auth :host)))
                       (auth-source-search :max 10000)))))
      (cdr (assoc (completing-read "Pick a TOTP> " candidates) candidates)))))
  (let ((code (totp (base32-hex-decode (funcall (plist-get auth :secret))))))
    (message "Your TOTP for '%s' is: %s (sent to kill ring)"
             (propertize (plist-get auth :host) 'face font-lock-keyword-face)
             (propertize code 'face 'font-lock-string-face))
    (kill-new code)
    code))

When you run M-x totp-display Emacs will first filter all known auth sources for any prefixed with TOTP: in its machine (host) field. You’re then asked to pick one from a pretty-printed completion prompt.

NOTE: Jürgen’s package assumes you’re storing the base-32-decoded in your auth sources file, whereas I assume that you do not!

The auth source entry you select is passed to the TOTP token generator and then printed in the echo area and sent to the kill ring so you can paste it.

Enjoy your Emacs-powered 2FA interactions.

Further Reading

Have you read my Reading Guide yet? It's a curated guide to most of my articles, and I guarantee you'll learn something whether you're a beginner or an expert. And why not check out my book?

Subscribe to the Mastering Emacs newsletter

I write infrequently, so go on — sign up and receive an e-mail when I write new articles

Copyright 2010-24 Mickey Petersen. Terms & Privacy Policy (Last updated 2022-05-24.)