passit
is a password generation
toolkit for Go. It features a variety of different password generators from charsets
to regular expressions, wordlists and emoji.
All generators implement the following interface:
// Generator is an interface for generating passwords.
type Generator interface {
// Password returns a randomly generated password using r as the source of
// randomness.
//
// The returned password may or may not be deterministic with respect to r.
// All generators in this package are deterministic unless otherwise noted.
//
// The output of r should be indistinguishable from a random string of the
// same length. This is a property of a good CSPRNG. Fundamentally the
// strength of the generated password is only as good as the provided source
// of randomness.
//
// r should implement the io.ByteReader interface for improved performance.
Password(r io.Reader) (string, error)
}
The package provides a number of generators that produce output from a fixed set:
Generator | Description | Examples |
---|---|---|
Digit |
[0-9] | "0" "7" |
LatinLower |
[a-z] | "a" "j" |
LatinLowerDigit |
[a-z0-9] | "a" "j" "0" "7" |
LatinUpper |
[A-Z] | "A" "J" |
LatinUpperDigit |
[A-Z0-9] | "A" "J" "0" "7" |
LatinMixed |
[a-zA-Z] | "a" "j" "A" "J" |
LatinMixedDigit |
[a-zA-Z0-9] | "a" "j" "A" "J" "0" "7" |
ASCIINoLettersNumbers |
!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ | "!" "=" "@" "`" "{" "~" |
ASCIINoLetters |
!"#$%&'()*+,-./0123456789:;<=>?@[\]^_`{|}~ | "!" "0" "7" "=" "@" "~" |
ASCIIGraphic |
[[:graph:]] | "!" "0" "7" "=" "A" "J" "a" "j" |
OrchardStreetMedium |
A word from Sam Schlinkert's Orchard Street Medium List | "abandon" "forest" |
OrchardStreetLong |
A word from Sam Schlinkert's Orchard Street Long List | "abandon" "mobility" |
OrchardStreetAlpha |
A word from Sam Schlinkert's Orchard Street Alpha List | "abbot" "points" |
OrchardStreetQWERTY |
A word from Sam Schlinkert's Orchard Street QWERTY List | "access" "peg" |
STS10Wordlist |
A word from Sam Schlinkert's '1Password Replacement List' | "aback" "loophole" |
EFFLargeWordlist |
A word from the EFF Large Wordlist for Passphrases | "abacus" "partition" |
EFFShortWordlist1 |
A word from the EFF Short Wordlist for Passphrases #1 | "acid" "match" |
EFFShortWordlist2 |
A word from the EFF Short Wordlist for Passphrases #2 | "aardvark" "jaywalker" |
Emoji13 |
A Unicode 13.0 fully-qualified emoji | "⌚" "🕸️" "🧎🏾♀️" |
Emoji15 |
A Unicode 15.0 fully-qualified emoji | "⌚" "🏎️" "🧏🏿♂️" |
HexLower |
Lowercase hexadecimal encoding | "66e94bd4ef8a2c3b" |
HexUpper |
Uppercase hexadecimal encoding | "66E94BD4EF8A2C3B" |
Base32 |
Base32 standard encoding | "M3UUXVHPRIWDW" |
Base32Hex |
Base32 hexadecimal encoding | "CRKKNL7FH8M3M" |
Base64 |
Base64 standard encoding | "ZulL1O+KLDs" |
Base64URL |
Base64 URL encoding | "ZulL1O-KLDs" |
Ascii85 |
Ascii85 encoding | "B'Dt<mtrYX" |
SpectreMaximum |
The Spectre maximum template | "i7,o%yC4&fmQ1r*qfcWq" |
SpectreLong |
The Spectre long template | "ZikzXuwuHeve1(" |
SpectreMedium |
The Spectre medium template | "Zik2~Puh" |
SpectreBasic |
The Spectre basic template | "izJ24tHJ" |
SpectreShort |
The Spectre short template | "His8" |
SpectrePIN |
The Spectre PIN template | "0778" |
SpectreName |
The Spectre name template | "hiskixuwu" |
SpectrePhrase |
The Spectre phrase template | "zi kixpu hoy vezamcu" |
Empty |
Empty string | "" |
Hyphen |
ASCII hyphen-minus | "-" |
Space |
ASCII space | " " |
The package also provides a number of generators that produce output based on user input:
Generator | Description |
---|---|
String |
A fixed string |
RegexpParser |
Password that matches a regular expression pattern |
FromCharset |
A rune from a charset string |
FromRangeTable |
A rune from a unicode.RangeTable |
FromSlice |
A string from a slice of strings |
There are also a number of 'helper' generators that interact with the output of other generators:
Generator | Description |
---|---|
Alternate |
Select a generator at random |
Join |
Concatenate the output of multiple generators |
Repeat |
Invoke a generator multiple times and concatenate the output with a fixed separator |
RepeatGen |
Invoke a generator multiple times and concatenate the output with a dynamic separator |
RandomRepeat |
Invoke a generator a random number of times and concatenate the output |
RejectionSample |
Continually invoke a generator until the output passes a test |
Transform |
Invoke a generator and convert the output according to a user supplied function |
LowerCase |
Invoke a generator and convert the output to lower case |
UpperCase |
Invoke a generator and convert the output to upper case |
TitleCase |
Invoke a generator and convert the output to language-specific title case |
Most generators only generate a single of something, be it a rune, ASCII character
or word. For generating longer passwords use Repeat
or RandomRepeat
, possibly
with Join
or Alternate
. In this way the various generators can be composed to
generator arbitrarily long and complex passwords, or short and simple passwords as
is needed.
The generators are designed to map from a random string / stream to a text password. This is not designed to be a reversible process and decoding the password to the original random string is not possible.
Two commands for easy CLI password generation are provided.
passphrase
is a tool that generates random passphrases using one of the embedded
wordlists supported by passit.
$ go install go.tmthrgd.dev/passit/cmd/passphrase@latest
$ passphrase -n 5 -s -
assumes-forth-humanities-exemption-paid
twoproblems
is a tool that generates random passwords based on a regular
expression template.
$ go install go.tmthrgd.dev/passit/cmd/twoproblems@latest
$ twoproblems '[[:alpha:]]{15}-[[:digit:]]{3}[[:punct:]]{2}'
KsMtvHnSOmqjIll-277&$
$ twoproblems '[[:alnum:]]{5}-(?P<word>count=5&sep=-)-[[:punct:]]{5}'
7Gfyv-degree-currency-revolt-coined-requested-*@`}[
Two special captures ((?P<name>)
) are supported:
(?P<word>)
: A word from any of the supported wordlists. This takes parameters similar to a URL query string (e.g.x=1&y=2&y=3
), including supporting percent escaping. The supported parameters are:case
transform the word to a given case ('lower' - default, 'upper' or 'title');count
generate N multiple words instead of just one;list
the name of a supported wordlist to use ('orchard:medium', 'orchard:long' - default, 'orchard:alpha, 'orchard:qwerty, 'eff:large' / 'eff', 'eff:short1' or 'eff:short2');sep
a separator to insert between words (defaults to a space).
(?P<emoji>)
: A Unicode 15.0 emoji returned fromEmoji15
. This can take a number to generate multiple emoji.
Note: Remember that wrapping the io.Reader
with bufio.NewReader
(if it
doesn't already implement io.ByteReader
) will greatly improve the performance of
the generators.
For generating random passwords, Password
should be called with
crypto/rand.Reader
. Avoid using
poor quality sources of randomness like math/rand.
import "crypto/rand"
func ExampleEFFLargeWordlist_WithCryptoRand() {
pass, _ := passit.Repeat(passit.EFFLargeWordlist, "-", 4).Password(rand.Reader)
fmt.Println(pass)
}
For generating deterministic passwords, Password
should be called with a
deterministic stream that should be indistinguishable from a random string of the
same length. Good examples of sources for this would be
HKDF,
ChaCha20 or AES-CTR with proper
key generation. Care must be taken when using deterministic password generation as
the generated password is only ever as good as the provided source of randomness.
func ExampleEFFLargeWordlist_WithHKDF() {
secret, salt, info := []byte("secret"), []byte("salt"), []byte("info")
r := hkdf.New(sha512.New, secret, salt, info)
pass, _ := passit.Repeat(passit.EFFLargeWordlist, "-", 4).Password(r)
fmt.Println(pass) // Output: king-unflawed-vagrancy-laxative
}
func ExampleEFFLargeWordlist_WithChaCha20() {
key := []byte("secret key for password generate")
c, _ := chacha20.NewUnauthenticatedCipher(key, []byte("IV for PWDGN"))
r := cipher.StreamReader{S: c, R: zeroReader{}}
pass, _ := passit.Repeat(passit.EFFLargeWordlist, "-", 4).Password(r)
fmt.Println(pass) // Output: penny-enclose-preoccupy-sappy
}
func ExampleEFFLargeWordlist_WithAESCTR() {
key := []byte("secret key for password generate")
block, _ := aes.NewCipher(key)
ctr := cipher.NewCTR(block, []byte("IV for PWDGN CTR"))
r := cipher.StreamReader{S: ctr, R: zeroReader{}}
pass, _ := passit.Repeat(passit.EFFLargeWordlist, "-", 4).Password(r)
fmt.Println(pass) // Output: juncture-net-unseen-pegboard
}
type zeroReader struct{}
func (zeroReader) Read(p []byte) (int, error) {
clear(p)
return len(p), nil
}
This library is Copyright (c) 2022, Tom Thorogood and is licensed under a BSD 3-Clause License.
Embedded wordlists are subject to their own copyright license referenced in the documentation.