Previous | Up | Next

Encrypted hledger with Emacs and GnuPG

A reasonably seamless way to work with encrypted ledger files

Last month, I went all-in with organizing my life in plain text. I started logging work and study notes in org-mode, and adopted hledger for keeping track of finances.

Both personal notes and financial documents warrant extra security measures. Emacs’ EasyPG integrates seamlessly with my day-to-day emacsing, so I decided to leverage the fact. Below are some short notes on how to get things working with the least amount of hassle.

This blog entry assumes the reader has GnuPG set up on their machine.

Encrypting your text files

EasyPG has a neat way of letting you specify whose public key you’d like to encrypt a given file with. You can set elisp variables in a ‘special’ comment on the first line of the file:

  ;; -*- epa-file-encrypt-to: ("your.publickey@email.com");  -*-

When emacs reads this file from disk, it will set the buffer-local variable epa-file-encrypt-to to your specified email address. This means that, upon saving, EasyPG will use that public key to encrypt the file.

However, until you actually save the file to disk, and then re-read it, EasyPG will ask you to specify whose key you’d like to encrpyt the file with… Didn’t we just do that via the ‘magic comment’!?

There is a way around this behavior. We’ll force Emacs to interpret the magic comment by switching to normal-mode and back.

(defun refresh-buffer ()
  "Reload file-local variables"
  (interactive)
  (let ((v major-mode))
    (normal-mode)
    (funcall v)))

(global-set-key (kbd "<f5>") 'refresh-buffer)

Now we need to just hit <F5> once after writing the magic comment. After that, the file will get auto-encrypted whenever we save with C-x C-s.

If you have a whole directory of files you’d like to encrypt (like a personal diary), it’s best to leverage emacs’ ‘directory variables’ feature. Just pop a file named .dir-locals.el in the directory, and inside it, put:

(
 (nil . ((epa-file-encrypt-to . ("your.publickey@email.com"))))
)

This will tell EasyPG to encrypt all files you save in this directory with “your.publickey@email.com”. (The nil in the car of the first pair stands for “any major mode”).

Decrypting your text files

This is easiest if you save all your encrypted files as XYZ.gpg, or XYZ.gpg.asc, and then set the following variable:

  (setq epa-file-name-regexp "\\.\\(gpg\\|\\asc\\)\\(~\\|\\.~[0-9]+~\\)?\\'")
  (epa-file-name-regexp-update)

Upon opening an encrypted file, Emacs (or your window manager) will pop up a modal window asking for your private key passphrase. This only has to be done once in a while, as the passphrase is cached in memory by gpg-agent for some time.

And now, the legder

This is a sample journal file from hledger’s documentation:

; A sample journal file. This is a comment.

2008/01/01 income               
    assets:bank:checking  $1    
    income:salary        $-1    

2008/06/01 gift
    assets:bank:checking  $1    
    income:gifts         $-1    

As noted above, we’ll just need to furnish it with EasyPG’s magic variable:

; -*- epa-file-encrypt-to: ("your.publickey@email.com");  -*-
; A sample journal file. This is a comment.

2008/01/01 income               
    assets:bank:checking  $1    
    income:salary        $-1    

Then, hit <F5>, and then save the file as ~/hledger.journal.gpg.asc. Close it, and then verify that it’s encrypted via:

$ cat ~/hledger.journal.gpg.asc
-----BEGIN PGP MESSAGE-----
Version: GnuPG v2

hBENO538+KYKbPGzAQf9HlZ7eFwE/V/kCwCRCzA2B1Zvut3MJONtIZ8O0bcAyRLS
xfZt9wlg4v5yZtGji6SH73yzlxdz2VZRjkTb7neVIUz/ySJlrzoS+R1SPEBvBHy+
W5j/+bitbx/gqWMwCC3cn2geSY86mnKmAFdtbFeD56Zyb7sgv0KAghrKUhDUU+lc
Lfl920jsryYu+VjDohJDJyuLGv9j4o62i47D4tQIwSGhFYZArLmqs6et/wKKZWIr
(...)

Now, we’ll tell hledger to always use this file for generating reports. I keep a separate script named hl, containing the following:

#!/bin/sh
gpg2 --decrypt $HOME/hledger.journal.gpg.asc 2>/dev/null | hledger -f- "$@"

You can now use hl reg expenses to feel bad about your wastefulness.

Happy organizing!

Previous | Up | Next