diff --git a/actions.go b/actions.go index 73a61bb..e6db91c 100644 --- a/actions.go +++ b/actions.go @@ -239,7 +239,7 @@ func addNewEntry() error { if len(passwd) == 0 { fmt.Printf("\nGenerating password ...") - err, passwd = generateRandomPassword(16) + err, passwd = generateStrongPassword() fmt.Printf("done") } // fmt.Printf("Password => %s\n", passwd) @@ -315,7 +315,7 @@ func editCurrentEntry(idString string) error { if strings.ToLower(passwd) == "y" { fmt.Printf("\nGenerating new password ...") - err, passwd = generateRandomPassword(16) + err, passwd = generateStrongPassword() } // fmt.Printf("Password => %s\n", passwd) diff --git a/crypto.go b/crypto.go index 924031f..2c9ac69 100644 --- a/crypto.go +++ b/crypto.go @@ -13,7 +13,9 @@ import ( "golang.org/x/crypto/pbkdf2" "io" "math/big" + "math/rand" "os" + "time" "unsafe" crand "crypto/rand" @@ -435,10 +437,10 @@ func decryptFileXChachaPoly(encDbPath string, password string) error { } // Generate a random password - for adding listings -func generateRandomPassword(length int) (error, string) { +func generatePassword(length int) (error, string) { var data []byte - const source = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789?)(/%#!?)=" + const source = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789=+_()$#@!~:/%" data = make([]byte, length) @@ -453,3 +455,97 @@ func generateRandomPassword(length int) (error, string) { return nil, string(data) } + +// Generate a "strong" password +// A strong password is defined as, +// A mix of upper and lower case alphabets +// at least one number [0-9] +// at least one upper case alphabet [A-Z] +// at least one punctuation character +// at least length 12 +func generateStrongPassword() (error, string) { + + var data []byte + var length int + + const sourceAlpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + const sourceLargeAlpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + const sourceNum = "0123456789" + const sourcePunct = "=+_()$#@!~:/%" + const source = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789=+_()$#@!~:/%" + + // Generate in range 12 - 16 + rand.Seed(time.Now().UnixNano()) + + length = rand.Intn(4) + 12 + + data = make([]byte, length) + + var lengthAlpha int + var i, j, k, l int + + // Alpha chars is at least length 3-5 + lengthAlpha = rand.Intn(2) + 3 + + for i = 0; i < lengthAlpha; i++ { + num, err := crand.Int(crand.Reader, big.NewInt(int64(len(sourceAlpha)))) + if err != nil { + return err, "" + } + + data[i] = sourceAlpha[num.Int64()] + } + + // Add in numbers 1 or 2 + var lengthNum int + + lengthNum = rand.Intn(2) + 1 + + for j = i; j < i+lengthNum; j++ { + num, err := crand.Int(crand.Reader, big.NewInt(int64(len(sourceNum)))) + if err != nil { + return err, "" + } + + data[j] = sourceNum[num.Int64()] + } + + // Add in punctuations 1 or 2 + var lengthPunc int + + lengthPunc = rand.Intn(2) + 1 + + for k = j; k < j+lengthPunc; k++ { + num, err := crand.Int(crand.Reader, big.NewInt(int64(len(sourcePunct)))) + if err != nil { + return err, "" + } + + data[k] = sourcePunct[num.Int64()] + } + + // Fill in the rest + var lengthRem int + + lengthRem = length - k + + if lengthRem > 0 { + for l = k; l < k+lengthRem; l++ { + num, err := crand.Int(crand.Reader, big.NewInt(int64(len(source)))) + if err != nil { + return err, "" + } + + data[l] = source[num.Int64()] + } + } + + // Shuffle a few times + for i = 0; i < 5; i++ { + rand.Shuffle(len(data), func(i, j int) { + data[i], data[j] = data[j], data[i] + }) + } + + return nil, string(data) +} diff --git a/main.go b/main.go index 8396fd7..343073d 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,6 @@ import ( "fmt" "github.com/pythonhacker/argparse" "os" - "strconv" ) const VERSION = 0.3 @@ -20,6 +19,7 @@ AUTHORS type actionFunc func(string) error type actionFunc2 func(string) (error, string) type voidFunc func() error +type voidFunc2 func() (error, string) // Structure to keep the options data type CmdOption struct { @@ -47,13 +47,11 @@ func printVersionInfo() error { } // Command-line wrapper to generateRandomPassword -func genPass(length string) (error, string) { - var iLength int +func genPass() (error, string) { var err error var passwd string - iLength, _ = strconv.Atoi(length) - err, passwd = generatePassword(iLength) + err, passwd = generateStrongPassword() if err != nil { fmt.Printf("Error generating password - \"%s\"\n", err.Error()) @@ -97,6 +95,9 @@ func performAction(optMap map[string]interface{}) { stringActions2Map := map[string]actionFunc2{ "decrypt": decryptDatabase, + } + + flagsActions2Map := map[string]voidFunc2{ "genpass": genPass, } @@ -112,6 +113,15 @@ func performAction(optMap map[string]interface{}) { } } + // Flag 2 actions + for key, mappedFunc := range flagsActions2Map { + if *optMap[key].(*bool) { + mappedFunc() + flag = true + break + } + } + // One of bool or string actions for key, mappedFunc := range boolActionsMap { if *optMap[key].(*bool) { @@ -164,7 +174,6 @@ func initializeCmdLine(parser *argparse.Parser) map[string]interface{} { {"E", "edit", "Edit entry by ", "", ""}, {"l", "list-entry", "List entry by ", "", ""}, {"x", "export", "Export all entries to ", "", ""}, - {"g", "genpass", "Generate password of given ", "", ""}, } for _, opt := range stringOptions { @@ -176,6 +185,7 @@ func initializeCmdLine(parser *argparse.Parser) map[string]interface{} { {"A", "add", "Add a new entry", "", ""}, {"p", "path", "Show current database path", "", ""}, {"a", "list-all", "List all entries in current database", "", ""}, + {"g", "genpass", "Generate a strong password of length from 8 - 12", "", ""}, {"s", "show", "Show passwords when listing entries", "", ""}, {"c", "copy", "Copy password to clipboard", "", ""}, {"v", "version", "Show version information and exit", "", ""},