1
0
mirror of https://github.com/Luzifer/password.git synced 2024-09-19 18:32:57 +00:00

Add XKCD style password generator

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2017-10-31 12:01:53 +01:00
parent 2d5397c819
commit cb3fd042e1
Signed by: luzifer
GPG Key ID: DC2729FDD34BE99E
5 changed files with 4022 additions and 5 deletions

View File

@ -10,6 +10,7 @@ debug: compile_coffee
pack: compile_coffee
go-bindata frontend/...
bash generateXKCDWordList.sh
publish:
curl -sSLo golang.sh https://raw.githubusercontent.com/Luzifer/github-publish/master/golang.sh

22
generateXKCDWordList.sh Normal file
View File

@ -0,0 +1,22 @@
#!/bin/bash
set -eu
set -o pipefail
VERSION="8744120d"
SOURCE="https://cdn.rawgit.com/leonardr/olipy/${VERSION}/data/more-corpora/scribblenauts_words.txt"
WORDS=$(curl -sL "${SOURCE}" | awk '/^[a-z]{4}[a-z]*$/{ print "\""$1"\"," }')
cat -s <<EOF > lib/xkcd_words.go
package securepassword
// xkcdWordList contains a list of words derived from the scribblenauts
// word list inside the olipy library by leonardr
// https://github.com/leonardr/olipy
var xkcdWordList = []string{
${WORDS}
}
EOF
gofmt -s -w lib/xkcd_words.go

56
lib/xkcd.go Normal file
View File

@ -0,0 +1,56 @@
package securepassword
import (
"errors"
"math/rand"
"strings"
"time"
"github.com/Luzifer/go_helpers/str"
)
type XKCD struct{}
var (
// ErrTooFewWords represents an error thrown if the password will
// have fewer than four words and are not considered to be safe
ErrTooFewWords = errors.New("XKCD passwords with less than 4 words makes no sense")
// DefaultXKCD contains an default instance of the XKCD password
// generator
DefaultXKCD = NewXKCDGenerator()
)
// NewXKCDGenerator initializes a new XKCD password generator
// https://xkcd.com/936/
func NewXKCDGenerator() *XKCD { return &XKCD{} }
// GeneratePassword generates a password with the number of words
// given and optionally the current date prepended
func (x XKCD) GeneratePassword(length int, addDate bool) (string, error) {
if length < 4 {
return "", ErrTooFewWords
}
var (
password string
usedWords []string
)
if addDate {
password = time.Now().Format("20060102.")
}
rand.Seed(time.Now().UnixNano())
for len(usedWords) < length {
word := strings.Title(xkcdWordList[rand.Intn(len(xkcdWordList))])
if str.StringInSlice(word, usedWords) {
// Don't use a word twice
continue
}
password = password + word
usedWords = append(usedWords, word)
}
return password, nil
}

50
lib/xkcd_test.go Normal file
View File

@ -0,0 +1,50 @@
package securepassword
import (
"fmt"
"regexp"
"testing"
)
func TestXKCDWordList(t *testing.T) {
if w := len(xkcdWordList); w < 1000 {
t.Fatalf("Word list is expected to contain at least 1000 words, has %d", w)
}
}
func TestXKCDGeneratePassword(t *testing.T) {
for i := 4; i < 20; i++ {
pwd, err := DefaultXKCD.GeneratePassword(i, false)
if err != nil {
t.Fatalf("Generated had an error: %s", err)
}
if !regexp.MustCompile(fmt.Sprintf("^([A-Z][a-z]+){%d}$", i)).MatchString(pwd) {
t.Errorf("Password %q is expected to contain %d words, did not match expected RegEx", pwd, i)
}
}
}
func TestXKCDDatePrepend(t *testing.T) {
pwd, err := DefaultXKCD.GeneratePassword(4, true)
if err != nil {
t.Fatalf("Generated had an error: %s", err)
}
if !regexp.MustCompile(`^[0-9]{8}\.([A-Z][a-z]+){4}$`).MatchString(pwd) {
t.Errorf("Password %q did not match expected RegEx", pwd)
}
}
func BenchmarkGeneratePasswords4Words(b *testing.B) {
for i := 0; i < b.N; i++ {
DefaultXKCD.GeneratePassword(4, false)
}
}
func BenchmarkGeneratePasswords20Words(b *testing.B) {
for i := 0; i < b.N; i++ {
DefaultXKCD.GeneratePassword(20, false)
}
}

3888
lib/xkcd_words.go Normal file

File diff suppressed because it is too large Load Diff