Skip to content

Commit

Permalink
Code simplification
Browse files Browse the repository at this point in the history
  • Loading branch information
GGP1 committed Mar 31, 2021
1 parent 006fe5b commit ced06ba
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 105 deletions.
135 changes: 55 additions & 80 deletions password.go
Expand Up @@ -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() {
Expand All @@ -127,57 +142,33 @@ 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))
}
}

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
Expand All @@ -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))])
}
}

Expand All @@ -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")
}
Expand Down Expand Up @@ -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)))
}
24 changes: 20 additions & 4 deletions password_test.go
Expand Up @@ -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)
}
}
Expand Down Expand Up @@ -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"}

Expand Down Expand Up @@ -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 {
Expand Down
1 change: 0 additions & 1 deletion syllable_list.go
@@ -1,6 +1,5 @@
package atoll

// 1Password syllable list
var atollSyllables = []string{
"ab",
"ac",
Expand Down
6 changes: 0 additions & 6 deletions util.go
Expand Up @@ -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 {
Expand Down
13 changes: 0 additions & 13 deletions util_test.go
Expand Up @@ -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)
Expand Down
1 change: 0 additions & 1 deletion word_list.go
@@ -1,6 +1,5 @@
package atoll

// 1Password word list
var atollWords = []string{
"aardvark",
"abaci",
Expand Down

0 comments on commit ced06ba

Please sign in to comment.