mirror of
https://github.com/wneessen/apg-go.git
synced 2024-11-14 01:42:55 +01:00
Merge pull request #1 from wneessen/dev
v0.2.3: More chars, cleanup and better test coverage
This commit is contained in:
commit
01782c228e
2 changed files with 273 additions and 48 deletions
67
apg.go
67
apg.go
|
@ -4,7 +4,6 @@ import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
@ -12,13 +11,13 @@ import (
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
const DefaultPwLenght int = 20
|
const DefaultPwLenght int = 20
|
||||||
const VersionString string = "0.2.2"
|
const VersionString string = "0.2.3"
|
||||||
const PwLowerCharsHuman string = "abcdefghjkmnpqrstuvwxyz"
|
const PwLowerCharsHuman string = "abcdefghjkmnpqrstuvwxyz"
|
||||||
const PwUpperCharsHuman string = "ABCDEFGHJKMNPQRSTUVWXYZ"
|
const PwUpperCharsHuman string = "ABCDEFGHJKMNPQRSTUVWXYZ"
|
||||||
const PwLowerChars string = "abcdefghijklmnopqrstuvwxyz"
|
const PwLowerChars string = "abcdefghijklmnopqrstuvwxyz"
|
||||||
const PwUpperChars string = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
const PwUpperChars string = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
const PwSpecialCharsHuman string = "\"#/\\$%&+-*"
|
const PwSpecialCharsHuman string = "\"#%*+-/:;=\\_|~"
|
||||||
const PwSpecialChars string = "\"#/!\\$%&+-*.,?=()[]{}:;~^|"
|
const PwSpecialChars string = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
|
||||||
const PwNumbersHuman string = "23456789"
|
const PwNumbersHuman string = "23456789"
|
||||||
const PwNumbers string = "1234567890"
|
const PwNumbers string = "1234567890"
|
||||||
|
|
||||||
|
@ -60,11 +59,12 @@ func init() {
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if config.showVersion {
|
if config.showVersion {
|
||||||
_, _ = os.Stderr.WriteString("Advanced Password Generator v" + VersionString + "\n")
|
_, _ = os.Stderr.WriteString("Winni's Advanced Password Generator Clone (apg.go) v" + VersionString + "\n")
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Main function that generated the passwords and returns them
|
||||||
func main() {
|
func main() {
|
||||||
pwLength := config.minPassLen
|
pwLength := config.minPassLen
|
||||||
if pwLength < config.minPassLen {
|
if pwLength < config.minPassLen {
|
||||||
|
@ -81,6 +81,20 @@ func main() {
|
||||||
config.humanReadable = false
|
config.humanReadable = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
charRange := getCharRange()
|
||||||
|
|
||||||
|
for i := 1; i <= config.numOfPass; i++ {
|
||||||
|
pwString, err := getRandChar(&charRange, pwLength)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("getRandChar returned an error: %q\n", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Println(pwString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provide the range of available characters based on provided parameters
|
||||||
|
func getCharRange() string {
|
||||||
pwUpperChars := PwUpperChars
|
pwUpperChars := PwUpperChars
|
||||||
pwLowerChars := PwLowerChars
|
pwLowerChars := PwLowerChars
|
||||||
pwNumbers := PwNumbers
|
pwNumbers := PwNumbers
|
||||||
|
@ -110,29 +124,48 @@ func main() {
|
||||||
charRange = regExp.ReplaceAllLiteralString(charRange, "")
|
charRange = regExp.ReplaceAllLiteralString(charRange, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 1; i <= config.numOfPass; i++ {
|
return charRange
|
||||||
pwString := getRandChar(&charRange, pwLength)
|
|
||||||
fmt.Println(pwString)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRandChar(charRange *string, pwLength int) string {
|
// Generate random characters based on given character range
|
||||||
|
// and password length
|
||||||
|
func getRandChar(charRange *string, pwLength int) (string, error) {
|
||||||
|
if pwLength <= 0 {
|
||||||
|
err := fmt.Errorf("provided pwLength value is <= 0: %v", pwLength)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
availCharsLength := len(*charRange)
|
availCharsLength := len(*charRange)
|
||||||
charSlice := []byte(*charRange)
|
charSlice := []byte(*charRange)
|
||||||
returnString := []byte{}
|
returnString := make([]byte, pwLength)
|
||||||
for i := 0; i < pwLength; i++ {
|
for i := 0; i < pwLength; i++ {
|
||||||
randNum := getRandNum(availCharsLength)
|
randNum, err := getRandNum(availCharsLength)
|
||||||
returnString = append(returnString, charSlice[randNum])
|
if err != nil {
|
||||||
|
return "", err
|
||||||
}
|
}
|
||||||
return string(returnString)
|
returnString[i] = charSlice[randNum]
|
||||||
|
}
|
||||||
|
return string(returnString), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRandNum(maxNum int) int {
|
// Generate a random number with given maximum value
|
||||||
|
func getRandNum(maxNum int) (int, error) {
|
||||||
|
if maxNum <= 0 {
|
||||||
|
err := fmt.Errorf("provided maxNum is <= 0: %v", maxNum)
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
maxNumBigInt := big.NewInt(int64(maxNum))
|
maxNumBigInt := big.NewInt(int64(maxNum))
|
||||||
|
if !maxNumBigInt.IsUint64() {
|
||||||
|
err := fmt.Errorf("big.NewInt() generation returned negative value: %v", maxNumBigInt)
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
randNum64, err := rand.Int(rand.Reader, maxNumBigInt)
|
randNum64, err := rand.Int(rand.Reader, maxNumBigInt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("An error occured generating random number: %v", err)
|
return 0, err
|
||||||
}
|
}
|
||||||
randNum := int(randNum64.Int64())
|
randNum := int(randNum64.Int64())
|
||||||
return randNum
|
if randNum < 0 {
|
||||||
|
err := fmt.Errorf("generated random number does not fit as int64: %v", randNum64)
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return randNum, nil
|
||||||
}
|
}
|
||||||
|
|
226
apg_test.go
226
apg_test.go
|
@ -1,6 +1,9 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
// Make sure the flags are initalized
|
// Make sure the flags are initalized
|
||||||
var _ = func() bool {
|
var _ = func() bool {
|
||||||
|
@ -9,45 +12,224 @@ var _ = func() bool {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Test getRandNum with max 1000
|
// Test getRandNum with max 1000
|
||||||
func TestGetRandNumMax1000(t *testing.T) {
|
func TestGetRandNum(t *testing.T) {
|
||||||
randNum := getRandNum(1000)
|
t.Run("maxNum_is_1000", func(t *testing.T) {
|
||||||
|
randNum, err := getRandNum(1000)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Random number generation failed: %v", err.Error())
|
||||||
|
}
|
||||||
if randNum > 1000 {
|
if randNum > 1000 {
|
||||||
t.Errorf("Generated random number between 0 and 1000 is too big: %d", randNum)
|
t.Errorf("Generated random number between 0 and 1000 is too big: %v", randNum)
|
||||||
}
|
}
|
||||||
if randNum < 0 {
|
if randNum < 0 {
|
||||||
t.Errorf("Generated random number between 0 and 1000 is too small: %d", randNum)
|
t.Errorf("Generated random number between 0 and 1000 is too small: %v", randNum)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
t.Run("maxNum_is_1", func(t *testing.T) {
|
||||||
|
randNum, err := getRandNum(1)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Random number generation failed: %v", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test getRandNum with max 1
|
|
||||||
func TestGetRandNumMax1(t *testing.T) {
|
|
||||||
randNum := getRandNum(1)
|
|
||||||
if randNum > 1 {
|
if randNum > 1 {
|
||||||
t.Errorf("Generated random number between 0 and 1 is too big: %d", randNum)
|
t.Errorf("Generated random number between 0 and 1000 is too big: %v", randNum)
|
||||||
}
|
}
|
||||||
if randNum < 0 {
|
if randNum < 0 {
|
||||||
t.Errorf("Generated random number between 0 and 1 is too small: %d", randNum)
|
t.Errorf("Generated random number between 0 and 1000 is too small: %v", randNum)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
t.Run("maxNum_is_0", func(t *testing.T) {
|
||||||
|
randNum, err := getRandNum(0)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Random number expected to fail, but provided a value instead: %v", randNum)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test getRandChar
|
// Test getRandChar
|
||||||
func TestGetRandChar(t *testing.T) {
|
func TestGetRandChar(t *testing.T) {
|
||||||
|
t.Run("return_value_is_A_B_or_C", func(t *testing.T) {
|
||||||
charRange := "ABC"
|
charRange := "ABC"
|
||||||
randChar := getRandChar(&charRange, 1)
|
randChar, err := getRandChar(&charRange, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Random character generation failed => %v", err.Error())
|
||||||
|
}
|
||||||
if randChar != "A" && randChar != "B" && randChar != "C" {
|
if randChar != "A" && randChar != "B" && randChar != "C" {
|
||||||
t.Errorf("Random character generation failed. Expected A, B or C but got: %v", randChar)
|
t.Errorf("Random character generation failed. Expected A, B or C but got: %v", randChar)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
randChar = getRandChar(&charRange, 1000)
|
t.Run("return_value_has_specific_length", func(t *testing.T) {
|
||||||
if len(randChar) != 1000 {
|
charRange := "ABC"
|
||||||
t.Errorf("Generated random characters with 1000 chars returned wrong amount of chars: %v", len(randChar))
|
randChar, err := getRandChar(&charRange, 1000)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Random character generation failed => %v", err.Error())
|
||||||
}
|
}
|
||||||
|
if len(randChar) != 1000 {
|
||||||
|
t.Errorf("Generated random characters with 1000 chars returned wrong amount of chars: %v",
|
||||||
|
len(randChar))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("fail", func(t *testing.T) {
|
||||||
|
charRange := "ABC"
|
||||||
|
randChar, err := getRandChar(&charRange, -2000)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Generated random characters expected to fail, but returned a value => %v",
|
||||||
|
randChar)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test getCharRange() with different config settings
|
||||||
|
func TestGetCharRange(t *testing.T) {
|
||||||
|
|
||||||
|
t.Run("lower_case_only", func(t *testing.T) {
|
||||||
|
// Lower case only
|
||||||
|
allowedBytes := []int{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
|
||||||
|
's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}
|
||||||
|
config.useLowerCase = true
|
||||||
|
charRange := getCharRange()
|
||||||
|
for _, curChar := range charRange {
|
||||||
|
searchAllowedBytes := containsByte(allowedBytes, int(curChar))
|
||||||
|
if !searchAllowedBytes {
|
||||||
|
t.Errorf("Character range for lower-case only returned invalid value: %v",
|
||||||
|
string(curChar))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Lower case only (human readable)
|
||||||
|
t.Run("lower_case_only_human_readable", func(t *testing.T) {
|
||||||
|
allowedBytes := []int{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r',
|
||||||
|
's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}
|
||||||
|
config.humanReadable = true
|
||||||
|
charRange := getCharRange()
|
||||||
|
for _, curChar := range charRange {
|
||||||
|
searchAllowedBytes := containsByte(allowedBytes, int(curChar))
|
||||||
|
if !searchAllowedBytes {
|
||||||
|
t.Errorf("Character range for lower-case only (human readable) returned invalid value: %v",
|
||||||
|
string(curChar))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Upper case only
|
||||||
|
t.Run("upper_case_only", func(t *testing.T) {
|
||||||
|
allowedBytes := []int{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
|
||||||
|
'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}
|
||||||
|
config.humanReadable = false
|
||||||
|
config.useLowerCase = false
|
||||||
|
config.useUpperCase = true
|
||||||
|
charRange := getCharRange()
|
||||||
|
for _, curChar := range charRange {
|
||||||
|
searchAllowedBytes := containsByte(allowedBytes, int(curChar))
|
||||||
|
if !searchAllowedBytes {
|
||||||
|
t.Errorf("Character range for upper-case only returned invalid value: %v",
|
||||||
|
string(curChar))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Upper case only (human readable)
|
||||||
|
t.Run("upper_case_only_human_readable", func(t *testing.T) {
|
||||||
|
allowedBytes := []int{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q',
|
||||||
|
'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}
|
||||||
|
config.humanReadable = true
|
||||||
|
charRange := getCharRange()
|
||||||
|
for _, curChar := range charRange {
|
||||||
|
searchAllowedBytes := containsByte(allowedBytes, int(curChar))
|
||||||
|
if !searchAllowedBytes {
|
||||||
|
t.Errorf("Character range for upper-case only (human readable) returned invalid value: %v",
|
||||||
|
string(curChar))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Numbers only
|
||||||
|
t.Run("numbers_only", func(t *testing.T) {
|
||||||
|
allowedBytes := []int{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}
|
||||||
|
config.humanReadable = false
|
||||||
|
config.useUpperCase = false
|
||||||
|
config.useNumber = true
|
||||||
|
charRange := getCharRange()
|
||||||
|
for _, curChar := range charRange {
|
||||||
|
searchAllowedBytes := containsByte(allowedBytes, int(curChar))
|
||||||
|
if !searchAllowedBytes {
|
||||||
|
t.Errorf("Character range for numbers only returned invalid value: %v",
|
||||||
|
string(curChar))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Numbers only (human readable)
|
||||||
|
t.Run("numbers_only_human_readable", func(t *testing.T) {
|
||||||
|
allowedBytes := []int{'2', '3', '4', '5', '6', '7', '8', '9'}
|
||||||
|
config.humanReadable = true
|
||||||
|
charRange := getCharRange()
|
||||||
|
for _, curChar := range charRange {
|
||||||
|
searchAllowedBytes := containsByte(allowedBytes, int(curChar))
|
||||||
|
if !searchAllowedBytes {
|
||||||
|
t.Errorf("Character range for numbers (human readable) only returned invalid value: %v",
|
||||||
|
string(curChar))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Special characters only
|
||||||
|
t.Run("special_chars_only", func(t *testing.T) {
|
||||||
|
allowedBytes := []int{'!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':',
|
||||||
|
';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~'}
|
||||||
|
config.humanReadable = false
|
||||||
|
config.useNumber = false
|
||||||
|
config.useSpecial = true
|
||||||
|
charRange := getCharRange()
|
||||||
|
for _, curChar := range charRange {
|
||||||
|
searchAllowedBytes := containsByte(allowedBytes, int(curChar))
|
||||||
|
if !searchAllowedBytes {
|
||||||
|
t.Errorf("Character range for special characters only returned invalid value: %v",
|
||||||
|
string(curChar))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Special characters only (human readable)
|
||||||
|
t.Run("special_chars_only_human_readable", func(t *testing.T) {
|
||||||
|
allowedBytes := []int{'"', '#', '%', '*', '+', '-', '/', ':', ';', '=', '\\', '_', '|', '~'}
|
||||||
|
config.humanReadable = true
|
||||||
|
charRange := getCharRange()
|
||||||
|
for _, curChar := range charRange {
|
||||||
|
searchAllowedBytes := containsByte(allowedBytes, int(curChar))
|
||||||
|
if !searchAllowedBytes {
|
||||||
|
t.Errorf("Character range for special characters only returned invalid value: %v",
|
||||||
|
string(curChar))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forced failures
|
||||||
|
func TestForceFailures(t *testing.T) {
|
||||||
|
t.Run("too_big_big.NewInt_value", func(t *testing.T) {
|
||||||
|
maxNum := 9223372036854775807
|
||||||
|
maxNumBigInt := big.NewInt(int64(maxNum) + 1)
|
||||||
|
if maxNumBigInt.IsUint64() {
|
||||||
|
t.Errorf("Calling big.NewInt() with too large number expected to fail: %v", maxNumBigInt)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("negative value for big.NewInt()", func(t *testing.T) {
|
||||||
|
randNum, err := getRandNum(-20000)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Calling getRandNum() with negative value is expected to fail, but returned value: %v",
|
||||||
|
randNum)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Benchmark: Random number generation
|
// Benchmark: Random number generation
|
||||||
func BenchmarkGetRandNum(b *testing.B) {
|
func BenchmarkGetRandNum(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
getRandNum(100000)
|
_, _ = getRandNum(100000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +237,16 @@ func BenchmarkGetRandNum(b *testing.B) {
|
||||||
func BenchmarkGetRandChar(b *testing.B) {
|
func BenchmarkGetRandChar(b *testing.B) {
|
||||||
charRange := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\"#/!\\$%&+-*.,?=()[]{}:;~^|"
|
charRange := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\"#/!\\$%&+-*.,?=()[]{}:;~^|"
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
getRandChar(&charRange, 20)
|
_, _ = getRandChar(&charRange, 20)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Contains function to search a given slice for values
|
||||||
|
func containsByte(allowedBytes []int, currentChar int) bool {
|
||||||
|
for _, charInt := range allowedBytes {
|
||||||
|
if charInt == currentChar {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue