diff --git a/README.md b/README.md index 6738cf4..49899fd 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ This section provides some examples on how to use apg-go for common password gen ### Login password for a website ```shell -$ apg -C -f 20 -n 1 +$ apg-go -C -f 20 -n 1 Zq#lIY?=?J@4_\X@\xtf ``` **Note:** Nowadays 20 random characters are still considered secure for passwords. You might want to adjust @@ -36,7 +36,7 @@ the `-f` parameter if you require a longer password. ### PIN generation ```shell -$ apg -M lusN -f 6 -n 1 +$ apg-go -M lusN -f 6 -n 1 952170 ``` **Note:** A code example on how to programatically build a PIN generator with apg-go, can be found @@ -44,7 +44,7 @@ here: [pin-generator](example-code/pin-generator). ### Phone verification phrase (pronounceable) ```shell -$ apg -a 0 -m 15 -x 15 -t -n 1 +$ apg-go -a 0 -m 15 -x 15 -t -n 1 vEbErlaFryaNgyex (vE-bEr-la-Fry-aN-gy-ex) ``` We generated a 15-character long pronounceable phrase with syllables output, for easy @@ -52,7 +52,7 @@ use in e. g. a phone verification process. ### Cryptographic key for encryption ```shell -$ apg -a 3 -f 32 -bh +$ apg-go -a 3 -f 32 -bh ``` We generated a 32 bytes/256 bits long fully binary secret that can be used i. e. as encryption key for a AES-256 symmetric encryption. The output is represented in @@ -200,7 +200,7 @@ By default apg-go will generate 6 passwords, with a minimum length of 12 charact maxiumum length of 20 characters. The generated password will use a character set constructed from lower case, upper case and numeric characters. ```shell -$ ./apg-go +$ apg-go R8rCC8bw5NvJmTUK2g cHB9qogTbfdzFgnH hoHfpWAHHSNa4Q @@ -216,7 +216,7 @@ by setting the `-L` parameter. In addition you would set the `-S` parameter to e characters. Finally the parameter `-n 1` is needed to keep apg-go from generating more than one password: ```shell -$ ./apg-go -n 1 -L -S +$ apg-go -n 1 -L -S XY7>}H@5U40&_A1*9I$ ``` @@ -226,7 +226,7 @@ parameters instead. The new style is all combined in the `-M` parameter. Using t version of a parameter argument enables a feature, while the lower case version disabled it. The previous example could be represented like this in new style: ```shell -$ ./apg-go -n 1 -M lUSN +$ apg-go -n 1 -M lUSN $B|~sudhtyDBu ``` Alternatively, since v1.0.0 apg-go has the new `-f` flag, which allows to request a fixed length password. Instead of using `-m` and `-x` you can just use `-f 32` to get a 32 character long password: ```shell -$ ./apg -n 1 -C -f 32 +$ apg-go -n 1 -C -f 32 O"Q\d0zT'@(1f~%_56O*!q[!9:z[~\A* ``` @@ -283,7 +283,7 @@ If you need to read out a password, it can be helpful to know the corresponding the phonetic alphabet. By setting the `-l` parameter, agp-go will provide you with the phonetic spelling (english language) of your newly created password: ```shell -$ ./apg-go -n 1 -M LUSN -H -E : -l +$ apg-go -n 1 -M LUSN -H -E : -l fUTDKeFsU+zn3r= (foxtrot/Uniform/Tango/Delta/Kilo/echo/Foxtrot/sierra/Uniform/PLUS_SIGN/zulu/november/THREE/romeo/EQUAL_SIGN) ``` @@ -312,10 +312,10 @@ randomly generated passwords. It might also be helpful to run the pronoucable pa "[HIBP](#have-i-been-pwned)" flag, so that each generated password is automatically checked against "Have I Been Pwned" database. ```shell -$ ./apg-go -a 0 -n 1 +$ apg-go -a 0 -n 1 KebrutinernMy -$ ./apg-go -a 0 -n 1 -m 15 -x 15 -t +$ apg-go -a 0 -n 1 -m 15 -x 15 -t pEnbocydrageT*En (pEn-bo-cy-dra-geT-ASTERISK-En) ``` @@ -323,7 +323,7 @@ pEnbocydrageT*En (pEn-bo-cy-dra-geT-ASTERISK-En) Sometimes you just want to quickly perform a simple, but random coinflip. Since v1.0.0 apg-go has a coinflip mode, which will return either "Heads" or "Tails". To use coinflip mode, use the `-a 2` argument: ```shell -$ ./apg -n 10 -a 2 +$ apg-go -n 10 -a 2 Tails Tails Heads @@ -348,12 +348,29 @@ This mode can be useful for example if you need to generate a AES-256 encryption the default length for the secret generation in this mode, you can simply generate a secret key with the following command: ```shell -$ apg -a 3 -bh +$ apg-go -a 3 -bh a1cdab8db365af3d70828b1fe43b7896190c157ad3f1ae2a0a1d52ec1628c6b5 ``` *For ease for readability we used the `-bh` flag, to instruct apg-go to output the secret in its hexadecimal representation* +### Mobile-friendly character grouping +Since v1.2.0 apg-go supports grouping of characters in a mobile-friendly manner. Entering a random string +of characters with a smartphone touch screen is tedious and error prone due to the need to toggle keypads +to gain access to different character tables. For this reason, this feature groups the characters of the +generated password in "keypad-order". It does so by groupoing the characters into character groups. The +following precedense is used: Upper-case characters, lower-case characters, numeric values, any other +character. + +Example: +```shell +$ apg-go -C -f 20 -n 1 -g +CETMPGGxuamj346!)>}) +``` + +**Please note that this feature makes the generated passwords much more predictable and lowers the +entropy of the generated password. Please use this feature with caution** + ### Minimum required characters Even though in apg-go you can select what kind of characters are used for the password generation, it is not guaranteed, that if you request a password with a numeric value, that the generated password will @@ -369,7 +386,7 @@ never being able to finish the job. Example: ```shell -$ ./apg -n 10 -a 1 -M NLUs -f 20 -mN 3 +$ apg-go -n 10 -a 1 -M NLUs -f 20 -mN 3 kqFG935E280LvTFUbJ4M RVBJAI5tJ6hy6oWrNfXG uy1IWBEoOQFyG66VrLqu @@ -409,6 +426,7 @@ _apg-go_ replicates most of the parameters of the original c-apg. Some parameter - `-m `: The minimum length of the password to be generated (Default: 12) - `-x `: The maximum length of the password to be generated (Default: 20) - `-f `: Fixed length of the password to be generated (Ignores -m and -x) +- `-g`: When set, mobile-friendly character grouping will be enabled in Algo: 1 (Default: off) - `-n `: The amount of passwords to be generated (Default: 6) - `-E `: Do not use the specified characters in generated passwords - `-M <[LUNSHClunshc]>`: New style password parameters (upper-case enables, lower-case disables) diff --git a/apg.go b/apg.go index b2cdd63..ea1dba0 100644 --- a/apg.go +++ b/apg.go @@ -5,7 +5,7 @@ package apg // VERSION represents the version string -const VERSION = "1.1.0" +const VERSION = "1.2.0" // Generator is the password generator type of the APG package type Generator struct { diff --git a/cmd/apg/apg.go b/cmd/apg/apg.go index 04d00ba..1858884 100644 --- a/cmd/apg/apg.go +++ b/cmd/apg/apg.go @@ -36,6 +36,7 @@ func main() { flag.BoolVar(&complexPass, "C", false, "") flag.StringVar(&config.ExcludeChars, "E", "", "") flag.Int64Var(&config.FixedLength, "f", 0, "") + flag.BoolVar(&config.MobileGrouping, "g", false, "") flag.BoolVar(&humanReadable, "H", false, "") flag.BoolVar(&config.SpellPassword, "l", false, "") flag.BoolVar(&lowerCase, "L", false, "") @@ -225,6 +226,9 @@ Flags: -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 + -g When set, mobile-friendly character grouping will be enabled in Algo: 1 + - Note: Grouping characters in random passwords makes them much + more predictable and lowers the entropy of the generated password. -n NUMBER Amount of password to be generated (Default: 6) - Note: Does not apply to binary mode (Algo: 3) -E CHARS List of characters to be excluded in the generated password diff --git a/config.go b/config.go index a64bb0b..42607d0 100644 --- a/config.go +++ b/config.go @@ -53,6 +53,9 @@ type Config struct { // MinUpperCase represents the minimum amount of upper-case characters that have // to be part of the generated password MinUpperCase int64 + // MobileGrouping indicates if the generated password should be grouped in a + // mobile-friendly manner + MobileGrouping bool // Mode holds the different character modes for the Random algorithm Mode ModeMask // NumberPass sets the number of passwords that are generated @@ -175,6 +178,13 @@ func WithMaxLength(length int64) Option { } } +// WithMobileGrouping enables the mobile-friendly character grouping for AlgoRandom +func WithMobileGrouping() Option { + return func(config *Config) { + config.MobileGrouping = true + } +} + // WithModeMask overrides the default mode mask for the random algorithm func WithModeMask(mask ModeMask) Option { return func(config *Config) { diff --git a/config_test.go b/config_test.go index 1f43cd2..038d463 100644 --- a/config_test.go +++ b/config_test.go @@ -184,6 +184,18 @@ func TestWithMinUppercase(t *testing.T) { } } +func TestWithMobileGrouping(t *testing.T) { + c := NewConfig(WithMobileGrouping()) + if c == nil { + t.Errorf("NewConfig(WithMobileGrouping()) failed, expected config pointer but got nil") + return + } + if c.MobileGrouping != true { + t.Errorf("NewConfig(WithMobileGrouping()) failed, expected: %t, got: %t", + true, c.MobileGrouping) + } +} + func TestWithModeMask(t *testing.T) { e := DefaultMode c := NewConfig(WithModeMask(e)) diff --git a/grouping.go b/grouping.go new file mode 100644 index 0000000..6338a74 --- /dev/null +++ b/grouping.go @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2021-2024 Winni Neessen +// +// SPDX-License-Identifier: MIT + +package apg + +import "unicode" + +// GroupCharsForMobile takes a given string of characters and groups them in a mobile-friendly +// manner. The grouping is based on the following precedense: uppercase, lowercase, numbers +// and special characters. The grouped string is then returned. +func GroupCharsForMobile(chars string) string { + var uppers, lowers, numbers, others []rune + for _, char := range chars { + switch { + case unicode.IsUpper(char): + uppers = append(uppers, char) + case unicode.IsLower(char): + lowers = append(lowers, char) + case unicode.IsNumber(char): + numbers = append(numbers, char) + default: + others = append(others, char) + } + } + return string(uppers) + string(lowers) + string(numbers) + string(others) +} diff --git a/grouping_test.go b/grouping_test.go new file mode 100644 index 0000000..e995b68 --- /dev/null +++ b/grouping_test.go @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2021-2024 Winni Neessen +// +// SPDX-License-Identifier: MIT + +package apg + +import "testing" + +func TestGroupCharacters(t *testing.T) { + tests := []struct { + name string + password string + want string + }{ + {`PW: A1c9.Ba`, `A1c9.Ba`, `ABca19.`}, + {`PW: PX4xDoiKrs,[egEAief{`, `PX4xDoiKrs,[egEAief{`, `PXDKEAxoirsegief4,[{`}, + {`PW: *Z%C9d+PZYkD7D+{~r'w`, `*Z%C9d+PZYkD7D+{~r'w`, `ZCPZYDDdkrw97*%++{~'`}, + {`PW: 4?r2YV:Abo&/z<3tJ*Z{`, `4?r2YV:Abo&/z<3tJ*Z{`, `YVAJZrbozt423?:&/<*{`}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + grouped := GroupCharsForMobile(tt.password) + if grouped != tt.want { + t.Errorf("GroupCharsForMobile() failed, expected: %s, got: %s", tt.want, grouped) + } + }) + } +} diff --git a/random.go b/random.go index 1195b2e..312dfa5 100644 --- a/random.go +++ b/random.go @@ -354,6 +354,9 @@ func (g *Generator) generateRandom() (string, error) { ok = g.checkMinimumRequirements(password) } + if g.config.MobileGrouping { + return GroupCharsForMobile(password), nil + } return password, nil } diff --git a/random_test.go b/random_test.go index 2b088bf..2d4c8b0 100644 --- a/random_test.go +++ b/random_test.go @@ -453,6 +453,27 @@ func TestGenerateRandom(t *testing.T) { } } +func TestGenerateRandom_withGrouping(t *testing.T) { + config := NewConfig(WithAlgorithm(AlgoRandom), WithMinLength(1), + WithMaxLength(1), WithMobileGrouping()) + config.MinNumeric = 1 + generator := New(config) + pw, err := generator.generateRandom() + if err != nil { + t.Errorf("generateRandom() failed: %s", err) + } + if len(pw) > 1 { + t.Errorf("expected password with length 1 but got: %d", len(pw)) + } + n, err := strconv.Atoi(pw) + if err != nil { + t.Errorf("expected password to be a number but got an error: %s", err) + } + if n < 0 || n > 9 { + t.Errorf("expected password to be a number between 0 and 9, got: %d", n) + } +} + func TestGenerate(t *testing.T) { tests := []struct { name string