Merge pull request #90 from wneessen/feature/70_group-password-characters-in-a-smart-phone-friendly-order

Add mobile-friendly character groupoing
This commit is contained in:
Winni Neessen 2024-03-25 11:53:42 +01:00 committed by GitHub
commit b7b4073483
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 142 additions and 19 deletions

View file

@ -28,7 +28,7 @@ This section provides some examples on how to use apg-go for common password gen
### Login password for a website ### Login password for a website
```shell ```shell
$ apg -C -f 20 -n 1 $ apg-go -C -f 20 -n 1
Zq#lIY?=?J@4_\X@\xtf Zq#lIY?=?J@4_\X@\xtf
``` ```
**Note:** Nowadays 20 random characters are still considered secure for passwords. You might want to adjust **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 ### PIN generation
```shell ```shell
$ apg -M lusN -f 6 -n 1 $ apg-go -M lusN -f 6 -n 1
952170 952170
``` ```
**Note:** A code example on how to programatically build a PIN generator with apg-go, can be found **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) ### Phone verification phrase (pronounceable)
```shell ```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) vEbErlaFryaNgyex (vE-bEr-la-Fry-aN-gy-ex)
``` ```
We generated a 15-character long pronounceable phrase with syllables output, for easy 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 ### Cryptographic key for encryption
```shell ```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 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 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 maxiumum length of 20 characters. The generated password will use a character set constructed
from lower case, upper case and numeric characters. from lower case, upper case and numeric characters.
```shell ```shell
$ ./apg-go $ apg-go
R8rCC8bw5NvJmTUK2g R8rCC8bw5NvJmTUK2g
cHB9qogTbfdzFgnH cHB9qogTbfdzFgnH
hoHfpWAHHSNa4Q 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 characters. Finally the parameter `-n 1` is needed to keep apg-go from generating more than one
password: password:
```shell ```shell
$ ./apg-go -n 1 -L -S $ apg-go -n 1 -L -S
XY7>}H@5U40&_A1*9I$ 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 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: previous example could be represented like this in new style:
```shell ```shell
$ ./apg-go -n 1 -M lUSN $ apg-go -n 1 -M lUSN
$</K?*|M)%8\U$5JA5~ $</K?*|M)%8\U$5JA5~
``` ```
@ -239,7 +239,7 @@ readability, you can set the `-H` parameter to toggle on the "human readable" fe
option is set, apg-go will avoid using any of the typical ambiguous characters in the generated option is set, apg-go will avoid using any of the typical ambiguous characters in the generated
passwords. passwords.
```shell ```shell
$ ./apg-go -n 1 -M LUSN -H $ apg-go -n 1 -M LUSN -H
YpranThY3b6b5%\6ARx YpranThY3b6b5%\6ARx
``` ```
@ -248,7 +248,7 @@ Let's assume, that for whatever reason, your generated password can never includ
this specific case, you can use the `-E` parameter to specify a list of characters that are to be excluded this specific case, you can use the `-E` parameter to specify a list of characters that are to be excluded
from the password generation character set: from the password generation character set:
```shell ```shell
$ ./apg-go -n 1 -M lUSN -H -E : $ apg-go -n 1 -M lUSN -H -E :
~B2\%E_|\VV|/5C7EF= ~B2\%E_|\VV|/5C7EF=
``` ```
@ -258,7 +258,7 @@ parameter, apg-go will automatically default to the most secure settings. The co
basically implies that the password will use all available characters (lower case, upper case, basically implies that the password will use all available characters (lower case, upper case,
numeric and special) and will make sure that human readability is disabled. numeric and special) and will make sure that human readability is disabled.
```shell ```shell
$ ./apg-go -n 1 -C $ apg-go -n 1 -C
{q6cvz9le5_fo"X7 {q6cvz9le5_fo"X7
``` ```
@ -268,13 +268,13 @@ want to be more specific, you can use the `-m` and `-x` parameters to override t
assume you want a single complex password with a length of exactly 32 characters you can do so by assume you want a single complex password with a length of exactly 32 characters you can do so by
running: running:
```shell ```shell
$ ./apg-go -n 1 -C -m 32 -x 32 $ apg-go -n 1 -C -m 32 -x 32
5lc&HBvx=!EUY*;'/t&>B|~sudhtyDBu 5lc&HBvx=!EUY*;'/t&>B|~sudhtyDBu
``` ```
Alternatively, since v1.0.0 apg-go has the new `-f` flag, which allows to request a fixed length 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: password. Instead of using `-m` and `-x` you can just use `-f 32` to get a 32 character long password:
```shell ```shell
$ ./apg -n 1 -C -f 32 $ apg-go -n 1 -C -f 32
O"Q\d0zT'@(1f~%_56O*!q[!9:z[~\A* 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 the phonetic alphabet. By setting the `-l` parameter, agp-go will provide you with the phonetic spelling
(english language) of your newly created password: (english language) of your newly created password:
```shell ```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) 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" "[HIBP](#have-i-been-pwned)" flag, so that each generated password is automatically checked against "Have I Been Pwned"
database. database.
```shell ```shell
$ ./apg-go -a 0 -n 1 $ apg-go -a 0 -n 1
KebrutinernMy 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) 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 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: coinflip mode, which will return either "Heads" or "Tails". To use coinflip mode, use the `-a 2` argument:
```shell ```shell
$ ./apg -n 10 -a 2 $ apg-go -n 10 -a 2
Tails Tails
Tails Tails
Heads 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 default length for the secret generation in this mode, you can simply generate a secret key with
the following command: the following command:
```shell ```shell
$ apg -a 3 -bh $ apg-go -a 3 -bh
a1cdab8db365af3d70828b1fe43b7896190c157ad3f1ae2a0a1d52ec1628c6b5 a1cdab8db365af3d70828b1fe43b7896190c157ad3f1ae2a0a1d52ec1628c6b5
``` ```
*For ease for readability we used the `-bh` flag, to instruct apg-go to output the secret in its *For ease for readability we used the `-bh` flag, to instruct apg-go to output the secret in its
hexadecimal representation* 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 ### Minimum required characters
Even though in apg-go you can select what kind of characters are used for the password generation, it is 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 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: Example:
```shell ```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 kqFG935E280LvTFUbJ4M
RVBJAI5tJ6hy6oWrNfXG RVBJAI5tJ6hy6oWrNfXG
uy1IWBEoOQFyG66VrLqu uy1IWBEoOQFyG66VrLqu
@ -409,6 +426,7 @@ _apg-go_ replicates most of the parameters of the original c-apg. Some parameter
- `-m <length>`: The minimum length of the password to be generated (Default: 12) - `-m <length>`: The minimum length of the password to be generated (Default: 12)
- `-x <length>`: The maximum length of the password to be generated (Default: 20) - `-x <length>`: The maximum length of the password to be generated (Default: 20)
- `-f <length>`: Fixed length of the password to be generated (Ignores -m and -x) - `-f <length>`: 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 <number of passwords>`: The amount of passwords to be generated (Default: 6) - `-n <number of passwords>`: The amount of passwords to be generated (Default: 6)
- `-E <list of characters>`: Do not use the specified characters in generated passwords - `-E <list of characters>`: Do not use the specified characters in generated passwords
- `-M <[LUNSHClunshc]>`: New style password parameters (upper-case enables, lower-case disables) - `-M <[LUNSHClunshc]>`: New style password parameters (upper-case enables, lower-case disables)

2
apg.go
View file

@ -5,7 +5,7 @@
package apg package apg
// VERSION represents the version string // 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 // Generator is the password generator type of the APG package
type Generator struct { type Generator struct {

View file

@ -36,6 +36,7 @@ func main() {
flag.BoolVar(&complexPass, "C", false, "") flag.BoolVar(&complexPass, "C", false, "")
flag.StringVar(&config.ExcludeChars, "E", "", "") flag.StringVar(&config.ExcludeChars, "E", "", "")
flag.Int64Var(&config.FixedLength, "f", 0, "") flag.Int64Var(&config.FixedLength, "f", 0, "")
flag.BoolVar(&config.MobileGrouping, "g", false, "")
flag.BoolVar(&humanReadable, "H", false, "") flag.BoolVar(&humanReadable, "H", false, "")
flag.BoolVar(&config.SpellPassword, "l", false, "") flag.BoolVar(&config.SpellPassword, "l", false, "")
flag.BoolVar(&lowerCase, "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) -f LENGTH Fixed length of the password to be generated (Ignores -m and -x)
- Note: Due to the way the pronounceable password algorithm works, - Note: Due to the way the pronounceable password algorithm works,
this setting might not always apply 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) -n NUMBER Amount of password to be generated (Default: 6)
- Note: Does not apply to binary mode (Algo: 3) - Note: Does not apply to binary mode (Algo: 3)
-E CHARS List of characters to be excluded in the generated password -E CHARS List of characters to be excluded in the generated password

View file

@ -53,6 +53,9 @@ type Config struct {
// MinUpperCase represents the minimum amount of upper-case characters that have // MinUpperCase represents the minimum amount of upper-case characters that have
// to be part of the generated password // to be part of the generated password
MinUpperCase int64 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 holds the different character modes for the Random algorithm
Mode ModeMask Mode ModeMask
// NumberPass sets the number of passwords that are generated // 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 // WithModeMask overrides the default mode mask for the random algorithm
func WithModeMask(mask ModeMask) Option { func WithModeMask(mask ModeMask) Option {
return func(config *Config) { return func(config *Config) {

View file

@ -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) { func TestWithModeMask(t *testing.T) {
e := DefaultMode e := DefaultMode
c := NewConfig(WithModeMask(e)) c := NewConfig(WithModeMask(e))

27
grouping.go Normal file
View file

@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: 2021-2024 Winni Neessen <wn@neessen.dev>
//
// 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)
}

28
grouping_test.go Normal file
View file

@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: 2021-2024 Winni Neessen <wn@neessen.dev>
//
// 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)
}
})
}
}

View file

@ -354,6 +354,9 @@ func (g *Generator) generateRandom() (string, error) {
ok = g.checkMinimumRequirements(password) ok = g.checkMinimumRequirements(password)
} }
if g.config.MobileGrouping {
return GroupCharsForMobile(password), nil
}
return password, nil return password, nil
} }

View file

@ -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) { func TestGenerate(t *testing.T) {
tests := []struct { tests := []struct {
name string name string