Skip to content

Instantly share code, notes, and snippets.

@alex-hhh alex-hhh/pwgen.rkt
Created Mar 21, 2019

Embed
What would you like to do?
#lang racket/base
;; Copyright (c) 2019 Alex Harsanyi
;; Permission is hereby granted, free of charge, to any person obtaining a
;; copy of this software and associated documentation files (the "Software"),
;; to deal in the Software without restriction, including without limitation
;; the rights to use, copy, modify, merge, publish, distribute, sublicense,
;; and/or sell copies of the Software, and to permit persons to whom the
;; Software is furnished to do so, subject to the following conditions:
;; The above copyright notice and this permission notice shall be included in
;; all copies or substantial portions of the Software.
;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
;; DEALINGS IN THE SOFTWARE.
(require racket/random
racket/math
racket/format
racket/port)
;; Generate a cryptographically secure random number of at least BITS bits
;; (BITS is rounded up to the next multiple of 8). Returns the random number
;; as an integer -- note that Racket supports big numbers, so it can represent
;; integers of arbitrary size, this is convenient for us.
(define (random-bignum bits)
(define num-bytes (exact-ceiling (/ bits 8)))
(for/fold ([result 0])
([byte (in-bytes (crypto-random-bytes num-bytes))])
(+ byte (* result 256))))
;; Encode KEY, a random integer produced by `random-bignum`, using ALPHABET,
;; which is a list of symbols to use for the encoding (see alphabet
;; definitions below). If GROUP is greater than 0, than SEPARATOR will be
;; inserted every GROUP symbols, this is intended to produce passwords that
;; are easier to read out and type by hand.
;;
;; NOTE: the choice of alphabet does not affect how strong a password is, the
;; strength of the password is determined by the random number generated by
;; `random-bignum`. However, alphabets with more symbols will produce shorter
;; passwords for the same strength.
(define (encode-bignum bignum alphabet group separator)
(define base (string-length alphabet))
(let loop ([position 0]
[result '()]
[bignum bignum])
(if (> bignum 0)
(let-values (([q r] (quotient/remainder bignum base)))
(let ((symbol (string-ref alphabet r)))
;; NOTE: if `group` is 0, there is no grouping
(if (and (> group 0) (> position 0) (= (remainder position group) 0))
(loop (add1 position) (cons symbol (cons separator result)) q)
(loop (add1 position) (cons symbol result) q))))
(list->string (reverse result)))))
;; A list of symbols suitable for passing as an alphabet to `encode-bignum` --
;; it contains all small case and capital case letters plus the numbers.
(define normal-alphabet
(string-append "abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789"))
;; Like `normal-alphabet` but add in some other symbols from punctuation
;; marks. This will produce shorter passwords, but the resulting passwords
;; might be difficult to use from scripts or to manually type in at the
;; keyboard.
(define full-alphabet
(string-append
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789"
"~!@#$%^&*_+-=|{}[]<>?,./"))
;; Like `normal-alphabet`, but only with lower case letters, this will
;; generate passwords that are easy to type on devices that have a touch
;; screen.
(define lower-case-alphabet
(string-append "abcdefghijklmnopqrstuvwxyz" "0123456789"))
;; Like `normal-alphabet`, but only contains symbols which are visually
;; distinct (i.e. the password will never contain 1, l, o or 0, as they can be
;; easily confused). These passwords will be longer for the same strength,
;; but might be useful if you need to write them down on paper.
(define simplified-alphabet "acefghkrstwxyz23456789")
(module+ main
(require racket/cmdline)
;; Program parameters
(define bits 128)
(define alphabet simplified-alphabet)
(define grouping 0)
(define (set-bits arg)
(let ((value (string->number arg)))
(if (and (number? value) (integer? value) (> value 64))
(set! bits value)
(error "invalid number of bits for the password, must be at least 64"))))
(define (set-alphabet arg)
(set! alphabet
(cond ((string-ci=? arg "full") full-alphabet)
((string-ci=? arg "normal") normal-alphabet)
((string-ci=? arg "lower-case") lower-case-alphabet)
((string-ci=? arg "simplified") simplified-alphabet)
(#t (error "unknown alphabet type")))))
(define (set-grouping arg)
(let ((value (string->number arg)))
(if (and (number? value) (integer? value) (> value 0))
(set! grouping value)
(error "invalid grouping, must be a positive integer"))))
;; Read command line arguments and set parameters
(command-line
#:program "pwgen"
#:usage-help "" "pwgen is a program to generate random passwords" ""
#:once-each
(("-b" "--bits")
arg
"number of random bits in the password"
(set-bits arg))
(("-a" "--alphabet")
arg
("alphabet to use for encoding"
"must be one of: 'full', 'normal', 'lower-case' or 'simplified'")
(set-alphabet arg))
(("-g" "--group")
arg
"group characters of <arg> items, use 0 for no grouping"
(set-grouping arg)))
;; Generate the password with the supplied arguments
(define n (random-bignum bits))
(printf "~a~%" (encode-bignum n alphabet grouping #\-)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.