From ced06bae84dddf5bcbc1c4bdd740ca74230eaddf Mon Sep 17 00:00:00 2001 From: GGP1 Date: Wed, 31 Mar 2021 01:21:34 -0300 Subject: [PATCH] Code simplification --- password.go | 135 +++++++++++++++++++---------------------------- password_test.go | 24 +++++++-- syllable_list.go | 1 - util.go | 6 --- util_test.go | 13 ----- word_list.go | 1 - 6 files changed, 75 insertions(+), 105 deletions(-) diff --git a/password.go b/password.go index 650da91..889df9e 100644 --- a/password.go +++ b/password.go @@ -90,35 +90,50 @@ func (p *Password) generate() (string, error) { p.generatePool() - if p.Exclude != "" { - // Remove excluded characters from the pool - exclChars := strings.Split(p.Exclude, "") - for _, c := range exclChars { - p.pool = strings.Replace(p.pool, c, "", 1) - } - } - if !p.Repeat && int(p.Length) > (len(p.pool)+len(p.Include)) { return "", errors.New("password length is higher than the pool and repetition is turned off") } - password := p.initPassword() - // Subtract the number of characters already added to the password from the total length - remaining := int(p.Length) - len(password) + password := p.buildPassword() + password = p.sanitize(password) - for i := 0; i < remaining; i++ { - char := p.pool[randInt(len(p.pool))] - password = randInsert(password, char) + return password, nil +} - if !p.Repeat { - // Remove element used - p.pool = strings.Replace(p.pool, string(char), "", 1) +// buildPassword creates the password. +func (p *Password) buildPassword() string { + var password string + // Add included characters + for _, c := range p.Include { + password = p.randInsert(password, byte(c)) + } + + // Add one character of each level only if we can guarantee it + if int(p.Length) > len(p.Levels) { + for _, lvl := range p.Levels { + repeat: + char := lvl[randInt(len(lvl))] + + // If the pool does not contain the character selected it's because + // it was either excluded or already used + if !strings.Contains(p.pool, string(char)) { + if lvl == Space { + continue + } + goto repeat + } + + password = p.randInsert(password, char) } } - password = p.sanitize(password) + // Subtract the number of characters already added to the password from the total length + remaining := int(p.Length) - len(password) + for i := 0; i < remaining; i++ { + password = p.randInsert(password, p.pool[randInt(len(p.pool))]) + } - return password, nil + return password } func (p *Password) generatePool() { @@ -127,7 +142,7 @@ func (p *Password) generatePool() { for _, lvl := range p.Levels { // Ensure that duplicated levels aren't added twice - if _, ok := unique[lvl]; !ok && len(lvl) > 0 { + if _, ok := unique[lvl]; !ok { unique[lvl] = struct{}{} sb.Grow(len(lvl)) sb.WriteString(string(lvl)) @@ -135,49 +150,25 @@ func (p *Password) generatePool() { } p.pool = sb.String() -} -// initPassword creates the password, adds any included word and makes sure that it contains -// at least 1 character of each level (only if p.Length is longer than levels). -func (p *Password) initPassword() string { - var ( - password string - char byte - ) - - // Add included characters - for _, c := range p.Include { - password = randInsert(password, byte(c)) - - if !p.Repeat { - // Remove character used - p.pool = strings.Replace(p.pool, string(c), "", 1) + if p.Exclude != "" { + // Remove excluded characters from the pool + exclChars := strings.Split(p.Exclude, "") + for _, c := range exclChars { + p.pool = strings.Replace(p.pool, c, "", 1) } } +} - if int(p.Length) < len(p.Levels) { - return password - } - - for _, lvl := range p.Levels { - repeat: - char = lvl[randInt(len(lvl))] - - // If the pool does not contain the character selected it's because - // it was either excluded or already used - if !strings.Contains(p.pool, string(char)) { - if lvl == Space { - continue - } - goto repeat - } - - password = randInsert(password, char) +// randInsert returns password with char inserted in a random position and removes char from pool in +// case p.Repeat is set to false. +func (p *Password) randInsert(password string, char byte) string { + i := randInt(len(password) + 1) + password = password[0:i] + string(char) + password[i:] - if !p.Repeat { - // Remove character used - p.pool = strings.Replace(p.pool, string(char), "", 1) - } + if !p.Repeat { + // Remove character used + p.pool = strings.Replace(p.pool, string(char), "", 1) } return password @@ -193,7 +184,7 @@ func (p *Password) sanitize(password string) string { for i := 0; i < offset; i++ { // Add remaining characters in random positions - password = randInsert(password, p.pool[randInt(len(p.pool))]) + password = p.randInsert(password, p.pool[randInt(len(p.pool))]) } } @@ -210,6 +201,9 @@ repeat: // validateLevels checks if Exclude contains all the characters of a level that is in Levels. func (p *Password) validateLevels() error { for _, lvl := range p.Levels { + if len(lvl) > len(p.Exclude) { + continue + } if len(lvl) < 1 { return errors.New("empty levels aren't allowed") } @@ -252,27 +246,8 @@ func (p *Password) validateLevels() error { // Entropy returns the password entropy in bits. func (p *Password) Entropy() float64 { + p.generatePool() poolLength := len(p.pool) - if p.pool == "" { - p.generatePool() - poolLength = len(p.pool) - - if !p.Repeat { - poolLength -= int(p.Length) - } - - // Do not count 2/3 byte characters as they aren't in the pool - for _, excl := range p.Exclude { - if len(string(excl)) != 1 { - poolLength -= strings.Count(p.Exclude, string(excl)) - } - } - } - - // Add the characters that were removed from the pool - if !p.Repeat { - poolLength += int(p.Length) - } - + poolLength += len(p.Include) return math.Log2(math.Pow(float64(poolLength), float64(p.Length))) } diff --git a/password_test.go b/password_test.go index 56843a4..46d987e 100644 --- a/password_test.go +++ b/password_test.go @@ -75,13 +75,13 @@ func TestPassword(t *testing.T) { for _, inc := range tc.p.Include { // Skip space as we cannot guarantee that it won't be at the start or end of the password - if !strings.ContainsAny(password, string(inc)) && inc != ' ' { + if !strings.Contains(password, string(inc)) && inc != ' ' { t.Errorf("Character %q is not included", inc) } } for _, exc := range tc.p.Exclude { - if strings.ContainsAny(password, string(exc)) { + if strings.Contains(password, string(exc)) { t.Errorf("Found undesired character: %q", exc) } } @@ -237,6 +237,23 @@ func TestGeneratePool(t *testing.T) { } } +func TestRandInsert(t *testing.T) { + p := &Password{Length: 13, Repeat: false} + char1 := 'a' + char2 := 'b' + + password := "" + password = p.randInsert(password, byte(char1)) + password = p.randInsert(password, byte(char2)) + + if password != "ab" && password != "ba" { + t.Errorf("Expected \"ab\"/\"ba\" and got %q", password) + } + if strings.ContainsAny(p.pool, "ab") { + t.Errorf("Failed removing characters from the pool") + } +} + func TestSanitize(t *testing.T) { cases := []string{" trimSpacesX ", "admin123login"} @@ -273,8 +290,7 @@ func TestPasswordEntropy(t *testing.T) { Levels: []Level{Lowercase, Uppercase, Digit, Space, Special}, Exclude: "a1r/รถ", } - - expected := 136.65780028329485 + expected := 130.15589280397393 got := p.Entropy() if got != expected { diff --git a/syllable_list.go b/syllable_list.go index edf7fff..f31c752 100644 --- a/syllable_list.go +++ b/syllable_list.go @@ -1,6 +1,5 @@ package atoll -// 1Password syllable list var atollSyllables = []string{ "ab", "ac", diff --git a/util.go b/util.go index 87b5ecc..b9f550c 100644 --- a/util.go +++ b/util.go @@ -28,12 +28,6 @@ func randInt(max int) int { return int(randN.Int64()) } -// randInsert inserts the given char in a random position. -func randInsert(secret string, char byte) string { - i := randInt(len(secret) + 1) - return secret[0:i] + string(char) + secret[i:] -} - // shuffle changes randomly the order of the password elements. func shuffle(key []rune) string { for i := range key { diff --git a/util_test.go b/util_test.go index 3bc7356..f1267ef 100644 --- a/util_test.go +++ b/util_test.go @@ -21,19 +21,6 @@ func TestGetFuncName(t *testing.T) { } } -func TestRandInsert(t *testing.T) { - secret := "" - char1 := 'a' - char2 := 'b' - - got1 := randInsert(secret, byte(char1)) - got2 := randInsert(got1, byte(char2)) - - if got2 != "ab" && got2 != "ba" { - t.Errorf("Failed inserting a random character") - } -} - func TestShuffle(t *testing.T) { var p string = "%A$Ks#a0t14|&23" password := []rune(p) diff --git a/word_list.go b/word_list.go index 8cae8b6..ef6a0ef 100644 --- a/word_list.go +++ b/word_list.go @@ -1,6 +1,5 @@ package atoll -// 1Password word list var atollWords = []string{ "aardvark", "abaci",