dicegen

0.2.3


Super simple library for generating very strong, memorable passphrases.

dependencies

org.clojure/clojure
1.10.1
me.gosimple/nbvcxz
1.4.3
org.clojure/tools.cli
0.4.2



(this space intentionally left almost blank)
 
(ns dicegen.core
  (:require [clojure.java.io :as io]
            [clojure.pprint :as pprint]
            [clojure.set :as set]
            [clojure.string :as str]
            [clojure.tools.cli :as cli])
  (:gen-class)
  (:import (me.gosimple.nbvcxz Nbvcxz)
           (me.gosimple.nbvcxz.resources ConfigurationBuilder)))

Dicegen

The library portion

(def wordlist-diceware "diceware")
(def default-wordlist wordlist-diceware)
(def valid-wordlists (sorted-set wordlist-diceware "eff"))

Convert a diceware line into a key-value pair

(defn- dice-line  [line]
  (let [[key val] (str/split line #"\t")] {key val}))
(defn- words [n]
  (-> (str n ".wordlist.txt")
      io/resource
      (io/make-input-stream {})
      slurp
      (str/split #"\n")
      ((partial map dice-line))
      ((partial apply merge))))

Roll 1dn

(defn rolldn  [n] (-> n rand-int inc))

Roll xdn

(defn rollxdn  [x n] (repeatedly x (partial rolldn n)))

Roll 5d6 and produce a string of the results

(defn random-word-address  []
  (str/join "" (rollxdn 5 6)))

An endless sequence of random words

(defn random-words 
  ([wordlist] (repeatedly (comp (words wordlist) random-word-address)))
  ([] (random-words default-wordlist)))

A passphrase of word length n

(defn passphrase 
  ([wordlist n] (->> wordlist random-words (take n) (str/join " ")))
  ([n] (passphrase default-wordlist n)))

An endless sequence of passphrases of word length n

(defn passphrases 
  ([wordlist n] (repeatedly (partial passphrase wordlist n)))
  ([n] (passphrases default-wordlist n)))

The app portion

(def allowed-wordlist-msg (format "one of: %s" (str/join ", " valid-wordlists)))
(defn usage [options-summary]
  ["Usage:"
   "------"
   "dicegen [options]"
   "Options:"
   options-summary])
(defn printlns [lines] (->> lines (str/join "\n") println))
(defn print-err-lns [lines]
  (binding [*out* *err*] (printlns lines)))
(defn print-usage [options-summary] (-> options-summary usage printlns))
(defn error-exit [errors options-summary]
  (print-err-lns errors)
  (print-usage options-summary)
  (System/exit 1))
(defmacro try-parse-num [t f n]
  `(try
     (~f ~n)
     (catch NumberFormatException _#
       (throw (ex-info (str "Must be a valid " ~t) {:given ~n})))))
(defn parse-int [n] (try-parse-num "integer" Integer/parseInt n))
(defn parse-double [n] (try-parse-num "double" Double/parseDouble n))
(def positive-integer-validation
  [pos? "Must be a positive integer"])
(def valid-wordlist-validation
  [valid-wordlists (str "Wordlist can be " allowed-wordlist-msg)])
(def cli-options
  [["-w" "--words WORDS" "Number of words per passphrase"
    :default 5
    :parse-fn parse-int
    :validate positive-integer-validation]
   ["-p" "--phrases PHRASES" "Number of passphrases to generate"
    :default 10
    :parse-fn parse-int
    :validate positive-integer-validation]
   ["-l" "--wordlist WORDLIST"
    (str "Which wordlist to use (" allowed-wordlist-msg ")")
    :default wordlist-diceware
    :parse-fn str/lower-case
    :validate valid-wordlist-validation]
   ["-e" "--enable-entropy" "Enable entropy calculation"]
   ["-m" "--minimum-entropy MINENTROPY" "Minimum entropy (for display purposes)"
    :default 40.0
    :parse-fn parse-double]
   ["-h" "--help"]])
(def entropy-table-fields [:passphrase :entropy :strong?])
(defn calculate-entropy [calculator phrase]
  (-> calculator
      (.estimate phrase)
      bean
      (set/rename-keys {:password :passphrase
                        :minimumEntropyMet :strong?})))
(defn entropy-calculator [minimum-entropy]
  (Nbvcxz. (-> (ConfigurationBuilder.)
               (.setMinimumEntropy minimum-entropy)
               .createConfiguration)))
(defn get-with-entropy [minimum-entropy result]
  (map (partial calculate-entropy (entropy-calculator minimum-entropy)) result))
(defn print-with-entropy [minimum-entropy result]
  (pprint/print-table entropy-table-fields
                      (get-with-entropy minimum-entropy result))
  (println "\nEntropy calculation by https://github.com/GoSimpleLLC/nbvcxz"))
(defn get-result [words wordlist phrases]
  (->> words (passphrases wordlist) (take phrases)))
(defn -main [& args]
  (let [{:keys [errors summary]
         {:keys [help
                 words
                 phrases
                 wordlist
                 enable-entropy
                 minimum-entropy]} :options}
        (cli/parse-opts args cli-options)]
    (cond
      help (print-usage summary)
      (seq errors) (error-exit errors summary)
      :otherwise
      (let [result (get-result words wordlist phrases)]
        (if enable-entropy
          (print-with-entropy minimum-entropy result)
          (printlns result))))
    (System/exit 0)))

Some examples

(comment

  (dice-line "12345\tthing")
  ;=> {"12345" "thing"}

  (rolldn 8)
  ;=> 7

  (rollxdn 3 12)
  ;=> (10 9 1)

  (take 5 (random-words))
  ;=> ("secant" "cream" "insult" "hear" "kodak")

  (take 5 (random-words "eff"))
  ;=> ("till" "sprig" "backstab" "unclothed" "uncooked")

  (passphrase 5)
  ;=> "chile jive dread crept !!"

  (passphrase "eff" 5)
  ;=> "abide query glitter whoopee landfill"

  (take 2 (passphrases 5))
  ;=> ("hive piece zs bogus bonus" "cure bask keel vivid fi")

  (take 2 (passphrases "eff" 5))
  ;=> ("army lividly grading primarily pagan" "purist send poet unbolted jab")

  )