Skip to content

Latest commit

 

History

History
187 lines (135 loc) · 7.78 KB

pg-password.org

File metadata and controls

187 lines (135 loc) · 7.78 KB

pg-password: password generation

This file contains code to generate and securely store passwords based on Diceware-style word lists. By theselves, Diceware passwords of six words or more are very difficult to guess; you can, of course, augment them with other forms of entropy for additional assurance.

This code never stores the passwords themselves, but instead stores the encrypted numeric reference IDs in the wordlist. Thus, an attacker (or user) needs the wordlist and the encryption key to successfully recover the password.

Caveats

The encryption used here is XOR; for it to be at all secure, the size (in bits) of the key has to be larger than the size of the plaintext to encrypt. (If the key is larger than the plaintext, it’s essentially one-time-pad.) So pick keys that are larger than your passwords.

Even though I’m trying to be careful and KISS with the encryption, I still only regard it as a complementary security measure. It is preferable to store the encrypted passphrase in another secure vault, such as a keychain or password vault.

Finally, Emacs memory is not secure from attack (beyond what the OS provides to assure the confidentiality of a process’s memory space), and Emacs itself is much better designed to keep data around than it is to scrub it. Needless to say, if you copy/paste the password it will be in your clipboard. Emacs also has an extensive undo function that essentially works as a keylogger for everything you do in a buffer. (Not for password prompts, though.) It is prudent to assume that a password generated with this code has leaked all through the Emacs process and can’t be removed. (And if you’ve copy/pasted, it’s probably also on the OS clipboard.)

Therefore, I recommend avoiding the use of copy/paste. If you’re paranoid, generate and decrypt passwords in an Emacs process you’ve started for that purpose, and destroy it immediately after you’ve done your password operation.

The value of having an on-demand passphrase generator and the incentive it gives me to have strong one-off passwords everywhere offsets the vulnerability of the password in memory for me.

Additional precautions

You should also consider applying additional transformations to your passphrase that only you know, so the passphrase on disk serves as a hint rather than a ready-to-use passphrase. Some possibilities:

  • Use a word order other than the one supplied
  • Insert delimiters of your choice between words
  • Add capitalization that isn’t captured on disk
  • Insert extraneous punctuation, numbers, letters, etc.
  • Combine with another password generated with another algorithm

Basically, practice defense in depth.

Package Header

;;; pg-password.el --- Password utility functions

;; Copyright (C) 2017 Phil Groce

;; Author: Phil Groce <pgroce@gmail.com>
;; Version: 0.2
;; Keywords: gui

Requires

(require 's)

Code

Poor man’s encryption/decryption

Basic XOR encryption. Remember: Again, always use a longer key than your input, or your “encryption” is trivially breakable. Note that each number is encrypted individually, so the key has to be longer than any of the elements, not all of them together.

(defun pg/encode-number (number key)
  (format "%X" (logxor key number)))

(defun pg/encode-numbers (numbers key)
  (mapcar (lambda (i) (pg/encode-number i key)) numbers))

(defun pg/decode-number (number key)
  (logxor key (string-to-number number 16)))

(defun pg/decode-numbers (numbers key)
  (mapcar (lambda (i) (pg/decode-number i key)) numbers))

Diceware wordlist handling

pg/password-wordlist-file is a file in the Diceware wordlist format from which words will be drawn. By default it is nil and should be supplied by the user.

(defcustom pg/password-wordlist-file nil
  "Location of file containing Diceware wordlist.")

Here’s a quick macro to make it easier to use the wordlist file consistently.

(defmacro pg/--with-wordlist (var &rest body)
  "Do BODY with the wordlist, which shall be bound to the name in
VAR."
  (declare (indent defun))
  `(if (file-exists-p pg/password-wordlist-file)
       (let ((,var pg/password-wordlist-file))
         ,@body)
     (message
      "Could not locate '%s'. Is pg/password-wordlist-file set correctly?"
      pg/password-wordlist-file)))

Diceware wordlists contain a unique number and a corresponding word. The next few functions parse individual lines in the wordlist and generate either a (number word) pair, just the word, or just the number

(defun pg/list-from-diceware-entry (entry-string)
  (let ((recs (s-split "[[:space:]]+" (s-trim entry-string))))
    (list (string-to-int (car recs)) (cadr recs))))

(defun pg/word-from-diceware-entry (entry-string)
  (cadr (pg/list-from-diceware-entry entry-string)))

(defun pg/number-from-diceware-entry (entry-string)
  (car (pg/list-from-diceware-entry entry-string)))

Password generation

pg-password-gen pulls everything together to generate a new password.

;;;###autoload
(defun pg-password-gen (key numwords)
  "Generate a random diceware password. Inserts two lists into
the buffer. The first is a set of numbers corresponding to the
words in the wordlist, XORed with the key; if desired, this may
be used as a password hint. The second is the list of words.

Diceware passphrases of sufficient length have been shown to be
strong passwords, but it is only a little harder (and far
stronger) to use the passphrase as a seed in a personal password
generation algorithm with additional steps, and to change your
passwords often."
  (interactive "nKey: \nnNumber of words: ")
  (pg/--with-wordlist wl
    (let* ((items
            (mapcar #'pg/list-from-diceware-entry
                    (s-lines
                     (s-trim
                      (shell-command-to-string
                       (format
                        "gshuf %s | head -%d" wl numwords))))))
           (numbers-encoded (pg/encode-numbers (mapcar #'car items) key))
           (words (mapcar #'cadr items)))
      (insert (prin1-to-string numbers-encoded))
      (insert "\n")
      (insert (prin1-to-string words)))))

pg-password-decrypt-sexp converts an encrypted list of diceware IDs to their corresponding words. It’s somewhat the inverse operation of pg-password-gen, only it just operates on the encrypted ID list.

(defun pg/number-to-word (key num)
  "Lookup the diceware word corresponding to NUM, which is a
  number encrypted with KEY."
  ;; Yeah, I know I could do this with elisp, but this is consistent
  ;; with my use of shuf in gen-password, and doesn't require me
  ;; keeping an extra buffer hanging around. Maybe I'll change it
  ;; someday.
  (pg/--with-wordlist wl
    (let ((decoded (pg/decode-number num key)))
      (pg/word-from-diceware-entry
       (shell-command-to-string
        (format
         "grep %d %s" decoded wl))))))

;;;###autoload
(defun pg-password-decrypt-sexp (key)
  "Lookup the list (sexp) of encrypted numbers at point in a
wordlist file, returning the words. KEY is used to decrypt the
numbers."
  (interactive "nKey: ")
  (let ((words (sexp-at-point)))
    (with-output-to-temp-buffer "secretsauce"
      (print
       (if (listp words)
           (mapcar (lambda (w) (pg/number-to-word key w)) words)
         (pg/number-to-word key words))))))

Provide

(provide 'pg-password)
;;; pg-password.el ends here