mirror of
https://github.com/wneessen/apg-go.git
synced 2024-11-22 13:50:49 +01:00
Merge pull request #60 from wneessen/binary_random
Add binary mode for secret generation
This commit is contained in:
commit
9f035c5834
11 changed files with 162 additions and 7 deletions
29
README.md
29
README.md
|
@ -49,6 +49,14 @@ 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
|
||||||
use in e. g. a phone verification process.
|
use in e. g. a phone verification process.
|
||||||
|
|
||||||
|
### Cryptographic key for encryption
|
||||||
|
```shell
|
||||||
|
$ apg -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
|
||||||
|
hexadecimal format.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Docker
|
### Docker
|
||||||
|
@ -327,6 +335,24 @@ Heads
|
||||||
Heads
|
Heads
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Binary mode
|
||||||
|
Since v1.0.1 apg-go has a new algorithm for binary secrets. This is a very basic mode that will ignore
|
||||||
|
most of the available options, as it will only generate binary secrets with full 256 bits of randomness.
|
||||||
|
The only available options for this mode are: `-f` to set the length of the returned secret in bytes,
|
||||||
|
`-bh` to tell apg-go to output the generated secret in hexadecial representation and `-bn` to instruct
|
||||||
|
apg-go to return a newline after the generated secret. Any other option available in the other modes
|
||||||
|
will be ignored.
|
||||||
|
|
||||||
|
This mode can be useful for example if you need to generate a AES-256 encryption key. Since 32 bytes is
|
||||||
|
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
|
||||||
|
a1cdab8db365af3d70828b1fe43b7896190c157ad3f1ae2a0a1d52ec1628c6b5
|
||||||
|
```
|
||||||
|
*For ease for readability we used the `-bh` flag, to instruct apg-go to output the secret in its
|
||||||
|
hexadecimal representation*
|
||||||
|
|
||||||
### 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
|
||||||
|
@ -376,6 +402,9 @@ _apg-go_ replicates most of the parameters of the original c-apg. Some parameter
|
||||||
- `0`: Pronouncable password generation (Koremutake syllables)
|
- `0`: Pronouncable password generation (Koremutake syllables)
|
||||||
- `1`: Random password generation according to password modes/flags
|
- `1`: Random password generation according to password modes/flags
|
||||||
- `2`: Coinflip (returns heads or tails)
|
- `2`: Coinflip (returns heads or tails)
|
||||||
|
- `3`: Binary mode (returns a secret with 256 bits of randomness)
|
||||||
|
- `-bh`: When set, will print the generated secret in its hex representation (Default: off)
|
||||||
|
- `-bn`: When set, will return a new line character after the generated secret (Default: off)
|
||||||
- `-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)
|
||||||
|
|
5
algo.go
5
algo.go
|
@ -18,6 +18,9 @@ const (
|
||||||
// AlgoCoinFlip represents a very simple coinflip algorithm returning "heads"
|
// AlgoCoinFlip represents a very simple coinflip algorithm returning "heads"
|
||||||
// or "tails"
|
// or "tails"
|
||||||
AlgoCoinFlip
|
AlgoCoinFlip
|
||||||
|
// AlgoBinary represents a full binary randomness mode with up to 256 bits
|
||||||
|
// of randomness
|
||||||
|
AlgoBinary
|
||||||
// AlgoUnsupported represents an unsupported algorithm
|
// AlgoUnsupported represents an unsupported algorithm
|
||||||
AlgoUnsupported
|
AlgoUnsupported
|
||||||
)
|
)
|
||||||
|
@ -32,6 +35,8 @@ func IntToAlgo(a int) Algorithm {
|
||||||
return AlgoRandom
|
return AlgoRandom
|
||||||
case 2:
|
case 2:
|
||||||
return AlgoCoinFlip
|
return AlgoCoinFlip
|
||||||
|
case 3:
|
||||||
|
return AlgoBinary
|
||||||
default:
|
default:
|
||||||
return AlgoUnsupported
|
return AlgoUnsupported
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,9 @@
|
||||||
|
|
||||||
package apg
|
package apg
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func TestIntToAlgo(t *testing.T) {
|
func TestIntToAlgo(t *testing.T) {
|
||||||
tt := []struct {
|
tt := []struct {
|
||||||
|
@ -15,7 +17,8 @@ func TestIntToAlgo(t *testing.T) {
|
||||||
{"AlgoPronounceable", 0, AlgoPronounceable},
|
{"AlgoPronounceable", 0, AlgoPronounceable},
|
||||||
{"AlgoRandom", 1, AlgoRandom},
|
{"AlgoRandom", 1, AlgoRandom},
|
||||||
{"AlgoCoinflip", 2, AlgoCoinFlip},
|
{"AlgoCoinflip", 2, AlgoCoinFlip},
|
||||||
{"AlgoUnsupported", 3, AlgoUnsupported},
|
{"AlgoBinary", 3, AlgoBinary},
|
||||||
|
{"AlgoUnsupported", 4, AlgoUnsupported},
|
||||||
}
|
}
|
||||||
for _, tc := range tt {
|
for _, tc := range tt {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
|
2
apg.go
2
apg.go
|
@ -5,7 +5,7 @@
|
||||||
package apg
|
package apg
|
||||||
|
|
||||||
// VERSION represents the version string
|
// VERSION represents the version string
|
||||||
const VERSION = "1.0.0"
|
const VERSION = "1.0.1"
|
||||||
|
|
||||||
// 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 {
|
||||||
|
|
|
@ -31,6 +31,8 @@ func main() {
|
||||||
var modeString string
|
var modeString string
|
||||||
var complexPass, humanReadable, lowerCase, numeric, special, showVer, upperCase bool
|
var complexPass, humanReadable, lowerCase, numeric, special, showVer, upperCase bool
|
||||||
flag.IntVar(&algorithm, "a", 1, "")
|
flag.IntVar(&algorithm, "a", 1, "")
|
||||||
|
flag.BoolVar(&config.BinaryHexMode, "bh", false, "")
|
||||||
|
flag.BoolVar(&config.BinaryNewline, "bn", false, "")
|
||||||
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, "")
|
||||||
|
@ -144,6 +146,23 @@ func configOldStyle(config *apg.Config, humanReadable, lowerCase, upperCase,
|
||||||
|
|
||||||
func generate(config *apg.Config) {
|
func generate(config *apg.Config) {
|
||||||
generator := apg.New(config)
|
generator := apg.New(config)
|
||||||
|
|
||||||
|
// In binary mode we only generate a single secret
|
||||||
|
if config.Algorithm == apg.AlgoBinary {
|
||||||
|
password, err := generator.Generate()
|
||||||
|
if err != nil {
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr, "failed to generate password: %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if config.BinaryNewline {
|
||||||
|
fmt.Println(password)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Print(password)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// For any other mode we cycle through the amount of passwords to be generated
|
||||||
for i := int64(0); i < config.NumberPass; i++ {
|
for i := int64(0); i < config.NumberPass; i++ {
|
||||||
password, err := generator.Generate()
|
password, err := generator.Generate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -197,12 +216,17 @@ Flags:
|
||||||
- 0: pronounceable password generation (koremutake syllables)
|
- 0: pronounceable password generation (koremutake syllables)
|
||||||
- 1: random password generation according to password modes/flags
|
- 1: random password generation according to password modes/flags
|
||||||
- 2: coinflip (returns heads or tails)
|
- 2: coinflip (returns heads or tails)
|
||||||
|
- 3: full binary mode (generates simple 256 bit randomness)
|
||||||
|
-bh When set, will print the generated secret in its hex representation (Default: off)
|
||||||
|
-bn When set, will return a new line character after the generated secret (Default: off)
|
||||||
|
- Note: The -bX options only apply to binary mode (Algo: 3)
|
||||||
-m LENGTH Minimum length of the password to be generated (Default: 12)
|
-m LENGTH Minimum length of the password to be generated (Default: 12)
|
||||||
-x LENGTH Maximum length of the password to be generated (Default: 20)
|
-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)
|
-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
|
||||||
-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)
|
||||||
-E CHARS List of characters to be excluded in the generated password
|
-E CHARS List of characters to be excluded in the generated password
|
||||||
-M [LUNSHClunshc] New style password flags
|
-M [LUNSHClunshc] New style password flags
|
||||||
- Note: new-style flags have higher priority than any of the old-style flags
|
- Note: new-style flags have higher priority than any of the old-style flags
|
||||||
|
|
14
config.go
14
config.go
|
@ -10,6 +10,8 @@ const (
|
||||||
DefaultMinLength int64 = 12
|
DefaultMinLength int64 = 12
|
||||||
// DefaultMaxLength reflects the default maximum length of a generated password
|
// DefaultMaxLength reflects the default maximum length of a generated password
|
||||||
DefaultMaxLength int64 = 20
|
DefaultMaxLength int64 = 20
|
||||||
|
// DefaultBinarySize is the default byte size for generating binary random bytes
|
||||||
|
DefaultBinarySize int64 = 32
|
||||||
// DefaultMode sets the default character set mode bitmask to a combination of
|
// DefaultMode sets the default character set mode bitmask to a combination of
|
||||||
// lower- and upper-case characters as well as numbers
|
// lower- and upper-case characters as well as numbers
|
||||||
DefaultMode ModeMask = ModeLowerCase | ModeNumeric | ModeUpperCase
|
DefaultMode ModeMask = ModeLowerCase | ModeNumeric | ModeUpperCase
|
||||||
|
@ -21,6 +23,11 @@ const (
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// Algorithm sets the Algorithm used for the password generation
|
// Algorithm sets the Algorithm used for the password generation
|
||||||
Algorithm Algorithm
|
Algorithm Algorithm
|
||||||
|
// BinaryHexMode if set will output the hex representation of the generated
|
||||||
|
// binary random string
|
||||||
|
BinaryHexMode bool
|
||||||
|
// BinaryNewline if set will print out a new line in AlgoBinary mode
|
||||||
|
BinaryNewline bool
|
||||||
// CheckHIBP sets a flag if the generated password has to be checked
|
// CheckHIBP sets a flag if the generated password has to be checked
|
||||||
// against the HIBP pwned password database
|
// against the HIBP pwned password database
|
||||||
CheckHIBP bool
|
CheckHIBP bool
|
||||||
|
@ -88,6 +95,13 @@ func WithAlgorithm(algo Algorithm) Option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithBinaryHexMode sets the hex mode for the AlgoBinary
|
||||||
|
func WithBinaryHexMode() Option {
|
||||||
|
return func(config *Config) {
|
||||||
|
config.BinaryHexMode = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WithExcludeChars sets a list of characters to be excluded in the generated
|
// WithExcludeChars sets a list of characters to be excluded in the generated
|
||||||
// passwords
|
// passwords
|
||||||
func WithExcludeChars(chars string) Option {
|
func WithExcludeChars(chars string) Option {
|
||||||
|
|
|
@ -46,7 +46,8 @@ func TestWithAlgorithm(t *testing.T) {
|
||||||
{"Pronounceable passwords", AlgoPronounceable, 0},
|
{"Pronounceable passwords", AlgoPronounceable, 0},
|
||||||
{"Random passwords", AlgoRandom, 1},
|
{"Random passwords", AlgoRandom, 1},
|
||||||
{"Coinflip", AlgoCoinFlip, 2},
|
{"Coinflip", AlgoCoinFlip, 2},
|
||||||
{"Unsupported", AlgoUnsupported, 3},
|
{"Binary", AlgoBinary, 3},
|
||||||
|
{"Unsupported", AlgoUnsupported, 4},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -67,6 +68,18 @@ func TestWithAlgorithm(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWithBinaryHexMode(t *testing.T) {
|
||||||
|
c := NewConfig(WithBinaryHexMode())
|
||||||
|
if c == nil {
|
||||||
|
t.Errorf("NewConfig(WithBinaryHexMode()) failed, expected config pointer but got nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !c.BinaryHexMode {
|
||||||
|
t.Errorf("NewConfig(WithBinaryHexMode()) failed, expected chars: %t, got: %t",
|
||||||
|
true, c.BinaryHexMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestWithExcludeChars(t *testing.T) {
|
func TestWithExcludeChars(t *testing.T) {
|
||||||
e := "abcdefg"
|
e := "abcdefg"
|
||||||
c := NewConfig(WithExcludeChars(e))
|
c := NewConfig(WithExcludeChars(e))
|
||||||
|
|
|
@ -21,7 +21,8 @@ func TestHasBeenPwned(t *testing.T) {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := HasBeenPwned(tt.password)
|
got, err := HasBeenPwned(tt.password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("HasBeenPwned() failed: %s", err)
|
t.Logf("HasBeenPwned() failed: %s", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if tt.want != got {
|
if tt.want != got {
|
||||||
t.Errorf("HasBeenPwned() failed, wanted: %t, got: %t", tt.want, got)
|
t.Errorf("HasBeenPwned() failed, wanted: %t, got: %t", tt.want, got)
|
||||||
|
|
25
random.go
25
random.go
|
@ -57,10 +57,13 @@ func (g *Generator) Generate() (string, error) {
|
||||||
return g.generateCoinFlip()
|
return g.generateCoinFlip()
|
||||||
case AlgoRandom:
|
case AlgoRandom:
|
||||||
return g.generateRandom()
|
return g.generateRandom()
|
||||||
|
case AlgoBinary:
|
||||||
|
return g.generateBinary()
|
||||||
case AlgoUnsupported:
|
case AlgoUnsupported:
|
||||||
return "", fmt.Errorf("unsupported algorithm")
|
return "", fmt.Errorf("unsupported algorithm")
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("unsupported algorithm")
|
||||||
}
|
}
|
||||||
return "", nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCharRangeFromConfig checks the Mode from the Config and returns a
|
// GetCharRangeFromConfig checks the Mode from the Config and returns a
|
||||||
|
@ -315,8 +318,26 @@ func (g *Generator) generatePronounceable() (string, error) {
|
||||||
return password, nil
|
return password, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generateBinary is executed when Generate() is called with Algorithm set
|
||||||
|
// to AlgoBinary
|
||||||
|
func (g *Generator) generateBinary() (string, error) {
|
||||||
|
length := DefaultBinarySize
|
||||||
|
if g.config.FixedLength > 0 {
|
||||||
|
length = g.config.FixedLength
|
||||||
|
}
|
||||||
|
randBytes := make([]byte, length)
|
||||||
|
_, err := rand.Read(randBytes)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to generate random bytes: %w", err)
|
||||||
|
}
|
||||||
|
if g.config.BinaryHexMode {
|
||||||
|
return fmt.Sprintf("%x", randBytes), nil
|
||||||
|
}
|
||||||
|
return string(randBytes), nil
|
||||||
|
}
|
||||||
|
|
||||||
// generateRandom is executed when Generate() is called with Algorithm set
|
// generateRandom is executed when Generate() is called with Algorithm set
|
||||||
// to AlgoRandmom
|
// to AlgoRandom
|
||||||
func (g *Generator) generateRandom() (string, error) {
|
func (g *Generator) generateRandom() (string, error) {
|
||||||
length, err := g.GetPasswordLength()
|
length, err := g.GetPasswordLength()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -471,6 +471,10 @@ func TestGenerate(t *testing.T) {
|
||||||
name: "Random",
|
name: "Random",
|
||||||
algorithm: AlgoRandom,
|
algorithm: AlgoRandom,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Binary",
|
||||||
|
algorithm: AlgoBinary,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Unsupported",
|
name: "Unsupported",
|
||||||
algorithm: AlgoUnsupported,
|
algorithm: AlgoUnsupported,
|
||||||
|
@ -530,3 +534,38 @@ func BenchmarkGenerator_RandomString(b *testing.B) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGenerator_generateBinary(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
config *Config
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Positive Case - Binary in Hex",
|
||||||
|
config: &Config{FixedLength: 10, BinaryHexMode: true},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Negative Case - Insufficient length",
|
||||||
|
config: &Config{FixedLength: -1, BinaryHexMode: false},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Positive Case - Binary without Hex",
|
||||||
|
config: &Config{FixedLength: 10, BinaryHexMode: false},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
g := &Generator{config: tt.config}
|
||||||
|
_, err := g.generateBinary()
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Generator.generateBinary() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -140,6 +140,12 @@ func TestPronounce(t *testing.T) {
|
||||||
want: "mu-sa",
|
want: "mu-sa",
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Pronounce_Mixed",
|
||||||
|
syllables: []string{"mu", "1"},
|
||||||
|
want: "mu-ONE",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Pronounce_NonKoremutakeSyllable",
|
name: "Pronounce_NonKoremutakeSyllable",
|
||||||
syllables: []string{"ä"},
|
syllables: []string{"ä"},
|
||||||
|
|
Loading…
Reference in a new issue