Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generated passwords look very similar #62

Closed
SebastianMpl opened this issue Jan 30, 2023 · 1 comment
Closed

Generated passwords look very similar #62

SebastianMpl opened this issue Jan 30, 2023 · 1 comment

Comments

@SebastianMpl
Copy link

Guys,
Just downloaded Linux x64 binary from the release page and while playing with he app I've noticed strange thing:

seba@sebapc Pobrane]$ ./gokey-v0.1.2-linux-amd64 -p example-p@ss -r github.com -l 10
sPYL`0_9rj
[seba@sebapc Pobrane]$ ./gokey-v0.1.2-linux-amd64 -p example-p@ss -r github.com -l 11
D5`0Nm*nuY;
[seba@sebapc Pobrane]$ ./gokey-v0.1.2-linux-amd64 -p example-p@ss -r github.com -l 12
z/Beul!Q1,.V
[seba@sebapc Pobrane]$ ./gokey-v0.1.2-linux-amd64 -p example-p@ss -r github.com -l 13
/Beul!Q1,.V>]
[seba@sebapc Pobrane]$ ./gokey-v0.1.2-linux-amd64 -p example-p@ss -r github.com -l 14
Beul!Q1,.V>]<@
[seba@sebapc Pobrane]$ ./gokey-v0.1.2-linux-amd64 -p example-p@ss -r github.com -l 15
w+m6j-vJH{"bz/B
[seba@sebapc Pobrane]$ ./gokey-v0.1.2-linux-amd64 -p example-p@ss -r github.com -l 16
w+m6j-vJH{"bz/Be
[seba@sebapc Pobrane]$ ./gokey-v0.1.2-linux-amd64 -p example-p@ss -r github.com -l 17
w+m6j-vJH{"bz/Beu
[seba@sebapc Pobrane]$ ./gokey-v0.1.2-linux-amd64 -p example-p@ss -r github.com -l 18
w+m6j-vJH{"bz/Beul
[seba@sebapc Pobrane]$ ./gokey-v0.1.2-linux-amd64 -p example-p@ss -r github.com -l 19
w+m6j-vJH{"bz/Beul!
[seba@sebapc Pobrane]$ ./gokey-v0.1.2-linux-amd64 -p example-p@ss -r github.com -l 20
w+m6j-vJH{"bz/Beul!Q
[seba@sebapc Pobrane]$ ./gokey-v0.1.2-linux-amd64 -p example-p@ss -r github.com -l 50
w+m6j-vJH{"bz/Beul!Q1,.V>]<@0psPYL`0_9rjP[b,d~^`:%
seba@sebapc Pobrane]$ ./gokey-v0.1.2-linux-amd64 -p example-p@ss -r github.com -l 51
w+m6j-vJH{"bz/Beul!Q1,.V>]<@0psPYL`0_9rjP[b,d~^`:%3

As you may see the result password with additional character is very similar to the previous password.
The same is for different Master Password and different websites.

Is it the expected behaviour or not?
Thanks,

@ignatk
Copy link
Contributor

ignatk commented Jan 30, 2023

Hi,

It should be expected: the combination of the master password + realm string should create a unique deterministic pseudo-random generator. Notice, that the requested password length is not contributing here yet.

Later, a filtering algorithm just takes the input from this DPRNG and tries to convert it to a string, which passes the requested password spec (including length). The result is than in most cases the string would be similar, just truncated to the requested length unless the bigger string stops conforming to the password spec (and in which case it would be just discarded and the next string from the DPRNG taken until it conforms).

see the relevant code here:

gokey/keygen.go

Lines 67 to 156 in 2d48d3b

func (spec *PasswordSpec) Compliant(password string) bool {
var upper, lower, digits, special int
for _, c := range password {
if unicode.IsUpper(c) {
upper++
}
if unicode.IsLower(c) {
lower++
}
if unicode.IsDigit(c) {
digits++
}
if unicode.IsSymbol(c) || unicode.IsPunct(c) {
if spec.AllowedSpecial == "" {
special++
} else {
if strings.ContainsRune(spec.AllowedSpecial, c) {
special++
} else {
return false
}
}
}
}
if !allowed(upper, spec.Upper) || !allowed(lower, spec.Lower) || !allowed(digits, spec.Digits) || !allowed(special, spec.Special) {
return false
}
return true
}
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?"
func randRange(rng io.Reader, max byte) (byte, error) {
var base [1]byte
for {
_, err := io.ReadFull(rng, base[:])
if err != nil {
return 0, err
}
if 255 == base[0] {
continue
}
rem := 255 % max
buck := 255 / max
if base[0] < 255-rem {
return base[0] / buck, nil
}
}
}
func (keygen *KeyGen) genRandStr(length int) (string, error) {
bytes := make([]byte, length)
for i := 0; i < length; i++ {
pos, err := randRange(keygen.rng, byte(len(chars)))
if err != nil {
return "", err
}
bytes[i] = chars[pos]
}
return string(bytes), nil
}
func (keygen *KeyGen) GeneratePassword(spec *PasswordSpec) (string, error) {
if !spec.Valid() {
return "", errors.New("invalid password specification")
}
for {
password, err := keygen.genRandStr(spec.Length)
if err != nil {
return "", err
}
if spec.Compliant(password) {
return password, nil
}
}
}

@ignatk ignatk closed this as completed Jan 30, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants