Refactor spelling of "Pronounceable" and introduce syllable spelling feature

The spelling of "Pronounceable" has been adjusted throughout the code. Moreover, a new functionality for producing pronounceable passwords spelled as correlating syllables has been integrated. This includes relevant changes to password character sets used for pronounceable passwords and enhancements to test this new feature.
This commit is contained in:
Winni Neessen 2024-03-12 18:28:01 +01:00
parent 90ff88de41
commit fefb2557fc
Signed by: wneessen
GPG key ID: 5F3AF39B820C119D
9 changed files with 67 additions and 20 deletions

View file

@ -5,9 +5,9 @@ package apg
type Algorithm int
const (
// AlgoPronouncable represents the algorithm for pronouncable passwords
// AlgoPronounceable represents the algorithm for pronounceable passwords
// (koremutake syllables)
AlgoPronouncable Algorithm = iota
AlgoPronounceable Algorithm = iota
// AlgoRandom represents the algorithm for purely random passwords according
// to the provided password modes/flags
AlgoRandom
@ -23,7 +23,7 @@ const (
func IntToAlgo(a int) Algorithm {
switch a {
case 0:
return AlgoPronouncable
return AlgoPronounceable
case 1:
return AlgoRandom
case 2:

View file

@ -8,7 +8,7 @@ func TestIntToAlgo(t *testing.T) {
a int
e Algorithm
}{
{"AlgoPronouncable", 0, AlgoPronouncable},
{"AlgoPronounceable", 0, AlgoPronounceable},
{"AlgoRandom", 1, AlgoRandom},
{"AlgoCoinflip", 2, AlgoCoinFlip},
{"AlgoUnsupported", 3, AlgoUnsupported},

3
apg.go
View file

@ -7,6 +7,9 @@ const VERSION = "2.0.0"
type Generator struct {
// config is a pointer to the apg config instance
config *Config
// syllables holds the single syllables of the lasst generated
// pronounceable password
syllables []string
}
// New returns a new password Generator type

View file

@ -42,6 +42,7 @@ func main() {
flag.StringVar(&modeString, "M", "", "")
flag.Int64Var(&config.NumberPass, "n", config.NumberPass, "")
flag.BoolVar(&config.SpellPassword, "l", false, "")
flag.BoolVar(&config.SpellPronounceable, "t", false, "")
flag.Usage = usage
flag.Parse()
@ -114,7 +115,7 @@ func main() {
_, _ = fmt.Fprintf(os.Stderr, "failed to generate password: %s\n", err)
os.Exit(1)
}
if config.SpellPassword {
if config.Algorithm == apg.AlgoRandom && config.SpellPassword {
spellPass, err := apg.Spell(password)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "failed to spell password: %s\n", err)
@ -122,6 +123,14 @@ func main() {
fmt.Printf("%s (%s)\n", password, spellPass)
continue
}
if config.Algorithm == apg.AlgoPronounceable && config.SpellPronounceable {
pronouncePass, err := generator.Pronounce()
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "failed to pronounce password: %s\n", err)
}
fmt.Printf("%s (%s)\n", password, pronouncePass)
continue
}
fmt.Println(password)
}
}
@ -145,6 +154,8 @@ Flags:
-m LENGTH Minimum length of the password to be generated (Default: 12)
-x LENGTH Maximum length of the password to be generated (Default: 20)
-f LENGTH Fixed length of the password to be generated (Ignores -m and -x)
- Note: Due to the way the pronounceable password algorithm works,
this setting might not always apply
-n NUMBER Amount of password to be generated (Default: 6)
-E CHARS List of characters to be excluded in the generated password
-M [LUNSHClunshc] New style password flags
@ -155,6 +166,8 @@ Flags:
-mU NUMBER Minimum amount of upper-case characters (implies -U)
- Note: any of the "Minimum amount of" modes may result in
extraordinarily long calculation times
- Note 2: The "minimum amount of" modes do not apply in
pronounceable mode (-a 0)
-C Enable complex password mode (implies -L -U -N -S and disables -H)
-H Avoid ambiguous characters in passwords (i. e.: 1, l, I, O, 0) (Default: off)
-L Toggle lower-case characters in passwords (Default: on)
@ -163,6 +176,8 @@ Flags:
-U Toggle upper-case characters in passwords (Default: on)
- Note: this flag has higher priority than the other old-style flags
-l Spell generated passwords in phonetic alphabet (Default: off)
-t Spell generated pronounceable passwords with the corresponding
syllables (Default: off)
-p Check the HIBP database if the generated passwords was found in a leak before (Default: off)
- Note: this feature requires internet connectivity
-h Show this help text

View file

@ -43,6 +43,9 @@ type Config struct {
NumberPass int64
// SpellPassword if set will spell the generated passwords in the phonetic alphabet
SpellPassword bool
// SpellPronounceable if set will spell the generated pronounceable passwords in
// as its corresponding syllables
SpellPronounceable bool
}
// Option is a function that can override default Config settings

View file

@ -35,7 +35,7 @@ func TestWithAlgorithm(t *testing.T) {
algo Algorithm
want int
}{
{"Pronouncble passwords", AlgoPronouncable, 0},
{"Pronouncble passwords", AlgoPronounceable, 0},
{"Random passwords", AlgoRandom, 1},
{"Coinflip", AlgoCoinFlip, 2},
{"Unsupported", AlgoUnsupported, 3},

View file

@ -45,8 +45,8 @@ func (g *Generator) CoinFlipBool() bool {
// it as string type. If the generation fails, an error will be thrown
func (g *Generator) Generate() (string, error) {
switch g.config.Algorithm {
case AlgoPronouncable:
return g.generatePronouncable()
case AlgoPronounceable:
return g.generatePronounceable()
case AlgoCoinFlip:
return g.generateCoinFlip()
case AlgoRandom:
@ -284,18 +284,19 @@ 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) {
// generatePronounceable is executed when Generate() is called with Algorithm set
// to AlgoPronounceable
func (g *Generator) generatePronounceable() (string, error) {
var password string
syllables := make([]string, 0)
g.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 := KoremutakeSyllables
characterSet = append(characterSet, strings.Split(CharRangeNumericHuman, "")...)
characterSet = append(characterSet, strings.Split(CharRangeSpecialHuman, "")...)
characterSetLength := len(characterSet)
for int64(len(password)) < length {
@ -316,7 +317,7 @@ func (g *Generator) generatePronouncable() (string, error) {
nextSyllable = strings.ReplaceAll(nextSyllable, randomChar, strings.ToUpper(randomChar))
}
password += nextSyllable
syllables = append(syllables, nextSyllable)
g.syllables = append(g.syllables, nextSyllable)
}
return password, nil

View file

@ -299,14 +299,14 @@ func TestGenerateCoinFlip(t *testing.T) {
}
}
func TestGeneratePronouncable(t *testing.T) {
func TestGeneratePronounceable(t *testing.T) {
config := NewConfig()
generator := New(config)
foundSylables := 0
for range 100 {
res, err := generator.generatePronouncable()
res, err := generator.generatePronounceable()
if err != nil {
t.Errorf("generatePronouncable() failed: %s", err)
t.Errorf("generatePronounceable() failed: %s", err)
return
}
for _, syl := range KoremutakeSyllables {
@ -316,7 +316,7 @@ func TestGeneratePronouncable(t *testing.T) {
}
}
if foundSylables < 100 {
t.Errorf("generatePronouncable() failed, expected at least 1 sylable, got none")
t.Errorf("generatePronounceable() failed, expected at least 1 sylable, got none")
}
}
@ -440,8 +440,8 @@ func TestGenerate(t *testing.T) {
expectedErr error
}{
{
name: "Pronouncable",
algorithm: AlgoPronouncable,
name: "Pronounceable",
algorithm: AlgoPronounceable,
},
{
name: "CoinFlip",

View file

@ -93,6 +93,31 @@ func Spell(input string) (string, error) {
return strings.Join(returnString, "/"), nil
}
// Pronounce returns last generated pronounceable password as spelled syllables string
func (g *Generator) Pronounce() (string, error) {
var returnString []string
for _, syllable := range g.syllables {
isKoremutake := false
for _, x := range KoremutakeSyllables {
if x == strings.ToLower(syllable) {
isKoremutake = true
}
}
if isKoremutake {
returnString = append(returnString, syllable)
continue
}
curSpellString, err := ConvertByteToWord(syllable[0])
if err != nil {
return "", err
}
returnString = append(returnString, curSpellString)
}
return strings.Join(returnString, "-"), nil
}
// ConvertByteToWord converts a given ASCII byte into the corresponding spelled version
// of the english phonetic alphabet
func ConvertByteToWord(charByte byte) (string, error) {