mirror of
https://github.com/wneessen/apg-go.git
synced 2024-12-23 03:30:39 +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
|
||||
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
|
||||
|
||||
### Docker
|
||||
|
@ -327,6 +335,24 @@ 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
|
||||
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
|
||||
|
@ -376,6 +402,9 @@ _apg-go_ replicates most of the parameters of the original c-apg. Some parameter
|
|||
- `0`: Pronouncable password generation (Koremutake syllables)
|
||||
- `1`: Random password generation according to password modes/flags
|
||||
- `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)
|
||||
- `-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)
|
||||
|
|
5
algo.go
5
algo.go
|
@ -18,6 +18,9 @@ const (
|
|||
// AlgoCoinFlip represents a very simple coinflip algorithm returning "heads"
|
||||
// or "tails"
|
||||
AlgoCoinFlip
|
||||
// AlgoBinary represents a full binary randomness mode with up to 256 bits
|
||||
// of randomness
|
||||
AlgoBinary
|
||||
// AlgoUnsupported represents an unsupported algorithm
|
||||
AlgoUnsupported
|
||||
)
|
||||
|
@ -32,6 +35,8 @@ func IntToAlgo(a int) Algorithm {
|
|||
return AlgoRandom
|
||||
case 2:
|
||||
return AlgoCoinFlip
|
||||
case 3:
|
||||
return AlgoBinary
|
||||
default:
|
||||
return AlgoUnsupported
|
||||
}
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
|
||||
package apg
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIntToAlgo(t *testing.T) {
|
||||
tt := []struct {
|
||||
|
@ -15,7 +17,8 @@ func TestIntToAlgo(t *testing.T) {
|
|||
{"AlgoPronounceable", 0, AlgoPronounceable},
|
||||
{"AlgoRandom", 1, AlgoRandom},
|
||||
{"AlgoCoinflip", 2, AlgoCoinFlip},
|
||||
{"AlgoUnsupported", 3, AlgoUnsupported},
|
||||
{"AlgoBinary", 3, AlgoBinary},
|
||||
{"AlgoUnsupported", 4, AlgoUnsupported},
|
||||
}
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
|
2
apg.go
2
apg.go
|
@ -5,7 +5,7 @@
|
|||
package apg
|
||||
|
||||
// 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
|
||||
type Generator struct {
|
||||
|
|
|
@ -31,6 +31,8 @@ func main() {
|
|||
var modeString string
|
||||
var complexPass, humanReadable, lowerCase, numeric, special, showVer, upperCase bool
|
||||
flag.IntVar(&algorithm, "a", 1, "")
|
||||
flag.BoolVar(&config.BinaryHexMode, "bh", false, "")
|
||||
flag.BoolVar(&config.BinaryNewline, "bn", false, "")
|
||||
flag.BoolVar(&complexPass, "C", false, "")
|
||||
flag.StringVar(&config.ExcludeChars, "E", "", "")
|
||||
flag.Int64Var(&config.FixedLength, "f", 0, "")
|
||||
|
@ -144,6 +146,23 @@ func configOldStyle(config *apg.Config, humanReadable, lowerCase, upperCase,
|
|||
|
||||
func generate(config *apg.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++ {
|
||||
password, err := generator.Generate()
|
||||
if err != nil {
|
||||
|
@ -197,12 +216,17 @@ Flags:
|
|||
- 0: pronounceable password generation (koremutake syllables)
|
||||
- 1: random password generation according to password modes/flags
|
||||
- 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)
|
||||
-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)
|
||||
- Note: Does not apply to binary mode (Algo: 3)
|
||||
-E CHARS List of characters to be excluded in the generated password
|
||||
-M [LUNSHClunshc] New style password 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
|
||||
// DefaultMaxLength reflects the default maximum length of a generated password
|
||||
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
|
||||
// lower- and upper-case characters as well as numbers
|
||||
DefaultMode ModeMask = ModeLowerCase | ModeNumeric | ModeUpperCase
|
||||
|
@ -21,6 +23,11 @@ const (
|
|||
type Config struct {
|
||||
// Algorithm sets the Algorithm used for the password generation
|
||||
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
|
||||
// against the HIBP pwned password database
|
||||
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
|
||||
// passwords
|
||||
func WithExcludeChars(chars string) Option {
|
||||
|
|
|
@ -46,7 +46,8 @@ func TestWithAlgorithm(t *testing.T) {
|
|||
{"Pronounceable passwords", AlgoPronounceable, 0},
|
||||
{"Random passwords", AlgoRandom, 1},
|
||||
{"Coinflip", AlgoCoinFlip, 2},
|
||||
{"Unsupported", AlgoUnsupported, 3},
|
||||
{"Binary", AlgoBinary, 3},
|
||||
{"Unsupported", AlgoUnsupported, 4},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
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) {
|
||||
e := "abcdefg"
|
||||
c := NewConfig(WithExcludeChars(e))
|
||||
|
|
|
@ -21,7 +21,8 @@ func TestHasBeenPwned(t *testing.T) {
|
|||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := HasBeenPwned(tt.password)
|
||||
if err != nil {
|
||||
t.Errorf("HasBeenPwned() failed: %s", err)
|
||||
t.Logf("HasBeenPwned() failed: %s", err)
|
||||
return
|
||||
}
|
||||
if 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()
|
||||
case AlgoRandom:
|
||||
return g.generateRandom()
|
||||
case AlgoBinary:
|
||||
return g.generateBinary()
|
||||
case AlgoUnsupported:
|
||||
return "", fmt.Errorf("unsupported algorithm")
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported algorithm")
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// GetCharRangeFromConfig checks the Mode from the Config and returns a
|
||||
|
@ -315,8 +318,26 @@ func (g *Generator) generatePronounceable() (string, error) {
|
|||
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
|
||||
// to AlgoRandmom
|
||||
// to AlgoRandom
|
||||
func (g *Generator) generateRandom() (string, error) {
|
||||
length, err := g.GetPasswordLength()
|
||||
if err != nil {
|
||||
|
|
|
@ -471,6 +471,10 @@ func TestGenerate(t *testing.T) {
|
|||
name: "Random",
|
||||
algorithm: AlgoRandom,
|
||||
},
|
||||
{
|
||||
name: "Binary",
|
||||
algorithm: AlgoBinary,
|
||||
},
|
||||
{
|
||||
name: "Unsupported",
|
||||
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",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Pronounce_Mixed",
|
||||
syllables: []string{"mu", "1"},
|
||||
want: "mu-ONE",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Pronounce_NonKoremutakeSyllable",
|
||||
syllables: []string{"ä"},
|
||||
|
|
Loading…
Reference in a new issue