Diceware in Lisp

I recently downloaded the Ironclad Cryptography Library with quicklisp and wanted to give it a spin. I thought a nice little project would be to implement the Diceware password generation algorithm in Lisp. This turned out to be really easy. I was expecting to use Ironclad primitives to get some random data, expand it with hashing, and use the result as an index to choose a word from the dictionary as I did with the C version. Instead, Ironclad provides the strong-random function that can provide the indicies with a single function call.

To implement the Diceware algorithm, I first downloaded the diceware8k.txt file that contains 8,192 words. Having the number of words be a power of 2 avoids a bias from the modulo operation—see Diceware Implementation (Part 2)—although strong-random avoids that at the expense of some extra work. The code itself is very simple:

(in-package #:cl-diceware)

(require 'ironclad)
(defparameter *words* nil)
(defparameter *prng* (ironclad:make-prng :fortuna))

(defun load-words (&optional (wd-list #P "~/Desktop/diceware8k.txt"))
  "Load *words* with diceware8k.txt."
  (with-open-file (s wd-list)
    (do ((wd (read-line s nil) (read-line s nil))
         ( i 0 (1+ i)))
        ((not wd))
      (setf (aref *words* i) wd))))

(defun choose-one ()
  "Randomly choose a single word from *words*."
  (aref *words* (ironclad:strong-random (length *words*) *prng*)))

(defun choose-password (n)
  "Generate n random words for a pass phrase. Initialize *words* if needed"
  (unless *words*
    (setf *words* (make-array 8192))
    (load-words))
  (let (words)
    (dotimes (i n)
      (push (choose-one) words))
    (reverse words)))

The choose-one function gets a (strong) random number between 0 and 8191 and uses it as an index into the *words* array. The choose-password function loads the dictionary into the *words* array if it hasn't already been loaded and then calls choose-one the requested number of times. The random words are accumulated in the words local variable and returned at the end.

Here's a sample call

CL-USER> (cl-diceware:choose-password 6)
("hath" "otto" "fly" "urea" "becalm" "seed")

Choosing 6 words gives 78 bits of entropy, more than enough for today's hardware and technology.

Update: 6 words gives 72 bits of entropy → 6 words gives 78 bits of entropy

This entry was posted in Programming and tagged . Bookmark the permalink.