Implement pronouncable password generation and refactor code

The new function "generatePronouncable" generates pronounceable passwords using the Koremutake syllabic representation. It is executed when 'Generate()' method is called with Algorithm set to 'AlgoPronouncable'. Additionally, significant changes were made to enhance the readability and performance of the 'GetCharRangeFromConfig' and 'checkMinimumRequirements' methods. In 'mode_test.go', the error message format has been updated for clear and concise display.
This commit is contained in:
Winni Neessen 2024-03-08 17:07:55 +01:00
parent bd654d40b8
commit 72576961e6
Signed by: wneessen
GPG key ID: 385AC9889632126E
2 changed files with 108 additions and 39 deletions

22
koremutake.go Normal file
View file

@ -0,0 +1,22 @@
package apg
// KoremutakeSyllables is a slightly modified Koremutake syllables list based on
// the mechanism described on https://shorl.com/koremutake.php
var KoremutakeSyllables = []string{"ba", "be", "bi", "bo", "bu", "by", "da", "de", "di",
"do", "du", "dy", "fe", "fi", "fo", "fu", "fy", "ga", "ge", "gi", "go", "gu",
"gy", "ha", "he", "hi", "ho", "hu", "hy", "ja", "je", "ji", "jo", "ju", "jy",
"ka", "ke", "ko", "ku", "ky", "la", "le", "li", "lo", "lu", "ly", "ma",
"me", "mi", "mo", "mu", "my", "na", "ne", "ni", "no", "nu", "ny", "pa", "pe",
"pi", "po", "pu", "py", "ra", "re", "ri", "ro", "ru", "ry", "sa", "se",
"si", "so", "su", "sy", "ta", "te", "ti", "to", "tu", "ty", "va", "ve", "vi",
"vo", "vu", "vy", "bra", "bre", "bri", "bro", "bru", "bry", "dra", "dre",
"dri", "dro", "dru", "dry", "fra", "fre", "fri", "fro", "fru", "fry", "gra",
"gre", "gri", "gro", "gru", "gry", "pra", "pre", "pri", "pro", "pru",
"pry", "sta", "ste", "sti", "sto", "stu", "sty", "tra", "tre", "er", "ed",
"in", "ex", "al", "en", "an", "ad", "or", "at", "ca", "ap", "el", "ci", "an",
"et", "it", "ob", "of", "af", "au", "cy", "im", "op", "co", "up", "ing",
"con", "ter", "com", "per", "ble", "der", "cal", "man", "est", "for", "mer",
"col", "ful", "get", "low", "son", "tle", "day", "pen", "pre", "ten",
"tor", "ver", "ber", "can", "ple", "fer", "gen", "den", "mag", "sub", "sur",
"men", "min", "out", "tal", "but", "cit", "cle", "cov", "dif", "ern",
"eve", "hap", "ket", "nal", "sup", "ted", "tem", "tin", "tro", "tro"}

125
random.go
View file

@ -46,6 +46,7 @@ func (g *Generator) CoinFlipBool() bool {
func (g *Generator) Generate() (string, error) {
switch g.config.Algorithm {
case AlgoPronouncable:
return g.generatePronouncable()
case AlgoCoinFlip:
return g.generateCoinFlip()
case AlgoRandom:
@ -56,6 +57,45 @@ func (g *Generator) Generate() (string, error) {
return "", nil
}
// GetCharRangeFromConfig checks the Mode from the Config and returns a
// list of all possible characters that are supported by these Mode
func (g *Generator) GetCharRangeFromConfig() string {
charRange := strings.Builder{}
if MaskHasMode(g.config.Mode, ModeLowerCase) {
switch MaskHasMode(g.config.Mode, ModeHumanReadable) {
case true:
charRange.WriteString(CharRangeAlphaLowerHuman)
default:
charRange.WriteString(CharRangeAlphaLower)
}
}
if MaskHasMode(g.config.Mode, ModeNumeric) {
switch MaskHasMode(g.config.Mode, ModeHumanReadable) {
case true:
charRange.WriteString(CharRangeNumericHuman)
default:
charRange.WriteString(CharRangeNumeric)
}
}
if MaskHasMode(g.config.Mode, ModeSpecial) {
switch MaskHasMode(g.config.Mode, ModeHumanReadable) {
case true:
charRange.WriteString(CharRangeSpecialHuman)
default:
charRange.WriteString(CharRangeSpecial)
}
}
if MaskHasMode(g.config.Mode, ModeUpperCase) {
switch MaskHasMode(g.config.Mode, ModeHumanReadable) {
case true:
charRange.WriteString(CharRangeAlphaUpperHuman)
default:
charRange.WriteString(CharRangeAlphaUpper)
}
}
return charRange.String()
}
// GetPasswordLength returns the password length based on the given config
// parameters
func (g *Generator) GetPasswordLength() (int64, error) {
@ -154,45 +194,14 @@ func (g *Generator) RandomStringFromCharRange(length int64, charRange string) (s
return randString.String(), nil
}
// GetCharRangeFromConfig checks the Mode from the Config and returns a
// list of all possible characters that are supported by these Mode
func (g *Generator) GetCharRangeFromConfig() string {
charRange := strings.Builder{}
if MaskHasMode(g.config.Mode, ModeLowerCase) {
switch MaskHasMode(g.config.Mode, ModeHumanReadable) {
case true:
charRange.WriteString(CharRangeAlphaLowerHuman)
default:
charRange.WriteString(CharRangeAlphaLower)
}
}
if MaskHasMode(g.config.Mode, ModeNumeric) {
switch MaskHasMode(g.config.Mode, ModeHumanReadable) {
case true:
charRange.WriteString(CharRangeNumericHuman)
default:
charRange.WriteString(CharRangeNumeric)
}
}
if MaskHasMode(g.config.Mode, ModeSpecial) {
switch MaskHasMode(g.config.Mode, ModeHumanReadable) {
case true:
charRange.WriteString(CharRangeSpecialHuman)
default:
charRange.WriteString(CharRangeSpecial)
}
}
if MaskHasMode(g.config.Mode, ModeUpperCase) {
switch MaskHasMode(g.config.Mode, ModeHumanReadable) {
case true:
charRange.WriteString(CharRangeAlphaUpperHuman)
default:
charRange.WriteString(CharRangeAlphaUpper)
}
}
return charRange.String()
}
// checkMinimumRequirements checks if a password meets the minimum requirements specified in the
// generator's configuration. It returns true if the password meets the requirements, otherwise it
// returns false.
//
// The minimum requirements for each character type (lowercase, numeric, special, uppercase) are
// checked independently. For each character type, the corresponding character range is determined
// based on the generator's configuration. The password is then checked for the presence of each
// character in the character range, and a count is maintained.
func (g *Generator) checkMinimumRequirements(password string) bool {
ok := true
if g.config.MinLowerCase > 0 {
@ -275,6 +284,44 @@ func (g *Generator) generateCoinFlip() (string, error) {
return "Tails", nil
}
// generatePronouncable is executed when Generate() is called with Algorithm set
// to AlgoPronouncable
func (g *Generator) generatePronouncable() (string, error) {
var password string
syllables := make([]string, 0)
length, err := g.GetPasswordLength()
if err != nil {
return "", fmt.Errorf("failed to calculate password length: %w", err)
}
characterSet := append(KoremutakeSyllables, strings.Split(CharRangeNumericHuman, "")...)
characterSet = append(characterSet, strings.Split(CharRangeSpecialHuman, "")...)
characterSetLength := len(characterSet)
for int64(len(password)) < length {
randNum, err := g.RandNum(int64(characterSetLength))
if err != nil {
return "", fmt.Errorf("failed to generate a random number for Koremutake syllable generation: %s",
err)
}
nextSyllable := characterSet[randNum]
if g.CoinFlipBool() {
syllableLength := len(nextSyllable)
characterPosition, err := g.RandNum(int64(syllableLength))
if err != nil {
return "", fmt.Errorf("failed to generate a random number for Koremutake syllable generation: %s",
err)
}
randomChar := string(nextSyllable[characterPosition])
nextSyllable = strings.ReplaceAll(nextSyllable, randomChar, strings.ToUpper(randomChar))
}
password += nextSyllable
syllables = append(syllables, nextSyllable)
}
return password, nil
}
// generateRandom is executed when Generate() is called with Algorithm set
// to AlgoRandmom
func (g *Generator) generateRandom() (string, error) {