Browse Source

Implement zxcvbn

Dennis Rodewyk 5 years ago
parent
commit
227c60bbc8
1 changed files with 106 additions and 87 deletions
  1. 106 87
      app.go

+ 106 - 87
app.go

@@ -3,10 +3,11 @@ package main
 import (
 	"flag"
 	"fmt"
+	"github.com/trustelem/zxcvbn"
 	"math"
 	"os"
-	"strings"
 	"passphrase-entropy/packages/haveibeenpwned"
+	"strings"
 )
 
 func contains(arr [3]string, str string) bool {
@@ -26,6 +27,33 @@ func myUsage() {
 	fmt.Println(" invented   Calculate entropy of an invented passphrase")
 }
 
+func secondsToHuman(guesses float64) (result string) {
+	minute := float64(60)
+	hour := minute * 60
+	day := hour * 24
+	month := day * 31
+	year := month * 12
+	century := year * 100
+	if guesses < 1 {
+		result = "< 1 second"
+	} else if guesses < minute {
+		result = fmt.Sprintf("%d seconds", int(math.Round(guesses)))
+	} else if guesses < hour {
+		result = fmt.Sprintf("%d minutes", int(math.Round(guesses / minute)))
+	} else if guesses < day {
+		result = fmt.Sprintf("%d hours", int(math.Round(guesses / hour)))
+	} else if guesses < month {
+		result = fmt.Sprintf("%d days", int(math.Round(guesses / day)))
+	} else if guesses < year {
+		result = fmt.Sprintf("%d months", int(math.Round(guesses / month)))
+	} else if guesses < century {
+		result = fmt.Sprintf("%d years", int(math.Round(guesses / year)))
+	} else {
+		result = fmt.Sprintf("Centuries")
+	}
+	return
+}
+
 func main() {
 	var charsets map[string]string
 	var usedCharsets[] string
@@ -33,6 +61,8 @@ func main() {
 
 	var haveIBeenPwned bool
 	var passphrase string
+	var pwnedFlag bool
+
 	var err error
 
 	charsets = make(map[string]string)
@@ -43,21 +73,6 @@ func main() {
 	charsets["Space"] = " "
 	charsets["Extended ASCII"] = "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"
 
-	dicewareCommand := flag.NewFlagSet("diceware", flag.ExitOnError)
-	dicewarePassphraseFlag := dicewareCommand.String("password", "", "the password")
-	wordsFlag := dicewareCommand.Int("words", 0, "number of words in passphrase")
-	dictSizeFlag := dicewareCommand.Int("dictSize", 0, "number of words in dictionary")
-	delimiterFlag := dicewareCommand.String("delimiter", "", "delimiter used")
-
-	randomCommand := flag.NewFlagSet("random", flag.ExitOnError)
-	randomPassphraseFlag := randomCommand.String("password", "", "the password")
-	randomPwnedFlag := randomCommand.Bool("pwned", false, "check if password has been seen before")
-
-	inventedCommand := flag.NewFlagSet("invented", flag.ExitOnError)
-	inventedPassphraseFlag := inventedCommand.String("password", "", "the password")
-	inventedWords := inventedCommand.Int("words", 0, "number of words in invented passphrase")
-	inventedPwnedFlag := inventedCommand.Bool("pwned", false, "check if password has been seen before")
-
 	if len(os.Args) == 1 {
 		myUsage()
 		return
@@ -65,93 +80,97 @@ func main() {
 
 	switch os.Args[1] {
 	case "diceware":
+		var words, dictSize int
+		dicewareCommand := flag.NewFlagSet("diceware", flag.ExitOnError)
+		dicewareCommand.IntVar(&words,"words", 0, "number of words in passphrase")
+		dicewareCommand.IntVar(&dictSize,"dictSize", 0, "number of words in dictionary")
 		dicewareCommand.Parse(os.Args[2:])
+		if dicewareCommand.Parsed() {
+			if words == 0 {
+				dicewareCommand.Usage()
+				return
+			}
+			if dictSize == 0 {
+				dicewareCommand.Usage()
+				return
+			}
+
+			entropy := math.Log2(math.Pow(float64(dictSize), float64(words)))
+			fmt.Println("\nPassphrase entropy:", entropy)
+		}
 	case "random":
+		var pwnedFlag bool
+		randomCommand := flag.NewFlagSet("random", flag.ExitOnError)
+		randomCommand.StringVar(&passphrase,"password", "", "the password")
+		randomCommand.BoolVar(&pwnedFlag, "pwned", false, "check if password has been seen before")
 		randomCommand.Parse(os.Args[2:])
-	case "invented":
-		inventedCommand.Parse(os.Args[2:])
-	default:
-		myUsage()
-		fmt.Printf("\n%q is not valid command.\n", os.Args[1])
-		os.Exit(2)
-	}
 
-	if *inventedPassphraseFlag != "" {
-		passphrase = *inventedPassphraseFlag
-	} else if *randomPassphraseFlag != "" {
-		passphrase = *randomPassphraseFlag
-	} else {
-		passphrase = *dicewarePassphraseFlag
-	}
+		if randomCommand.Parsed() {
+			if passphrase == "" {
+				randomCommand.Usage()
+				return
+			}
+			if pwnedFlag == true {
+				haveIBeenPwned, err = pwned.IsPasswordCompromised(passphrase)
+				if haveIBeenPwned == true {
+					fmt.Println("This password does not have any entropy, because it has been compromised.")
+					return
+				}
+			}
+			passphraseLength := len(passphrase)
 
-	if dicewareCommand.Parsed() {
-		if *wordsFlag == 0 {
-			dicewareCommand.Usage()
-			return
-		}
-		if *dictSizeFlag == 0 {
-			dicewareCommand.Usage()
-			return
-		}
-		if *delimiterFlag != "" {
 			for key, value := range charsets {
-				if strings.ContainsAny(*delimiterFlag, value) {
-					*dictSizeFlag += len(value)
+				if strings.ContainsAny(passphrase, value) {
+					possibleSymbols += len(value)
 					usedCharsets = append(usedCharsets, key)
 				}
 			}
-		}
-		entropy := math.Log2(math.Pow(float64(*dictSizeFlag), float64(*wordsFlag)))
-		fmt.Println("\nPassphrase entropy:", entropy)
-	}
-
-	if randomCommand.Parsed() {
-		if *randomPassphraseFlag == "" {
-			randomCommand.Usage()
-			return
-		}
 
-		if *randomPwnedFlag == true {
-			haveIBeenPwned, err = pwned.IsPasswordCompromised(passphrase)
-			if haveIBeenPwned == true {
-				fmt.Println("This password does not have any entropy, because it has been compromised.")
-				return
-			}
-		}
-		passphraseLength := len(*randomPassphraseFlag)
+			entropy := math.Log2(math.Pow(float64(possibleSymbols), float64(passphraseLength)))
 
-		for key, value := range charsets {
-			if strings.ContainsAny(*randomPassphraseFlag, value) {
-				possibleSymbols += len(value)
-				usedCharsets = append(usedCharsets, key)
-			}
+			fmt.Println("\nNumber of characters:", passphraseLength)
+			fmt.Println("\nUsed character sets:", strings.Join(usedCharsets, ", "))
+			fmt.Println("\nPassphrase entropy:", entropy)
 		}
 
-		entropy := math.Log2(math.Pow(float64(possibleSymbols), float64(passphraseLength)))
-
-		fmt.Println("\nNumber of characters:", passphraseLength)
-		fmt.Println("\nUsed character sets:", strings.Join(usedCharsets, ", "))
-		fmt.Println("\nPassphrase entropy:", entropy)
-	}
-
-	if inventedCommand.Parsed() {
+	case "invented":
+		var words string
+		inventedCommand := flag.NewFlagSet("invented", flag.ExitOnError)
+		inventedCommand.StringVar(&passphrase,"password", "", "the password")
+		inventedCommand.StringVar(&words, "words", "", "used words")
+		inventedCommand.BoolVar(&pwnedFlag, "pwned", false, "check if password has been seen before")
+		inventedCommand.Parse(os.Args[2:])
 
-		if *inventedWords != 0 {
-			inventedCommand.Usage()
-			return
-		}
+		if inventedCommand.Parsed() {
 
-		if *inventedPwnedFlag == true {
-			haveIBeenPwned, err = pwned.IsPasswordCompromised(passphrase)
-		}
+			if pwnedFlag == true {
+				haveIBeenPwned, err = pwned.IsPasswordCompromised(passphrase)
+			}
 
-		if err != nil {
-			fmt.Println("Something went wrong.")
-		} else if haveIBeenPwned == true {
-			fmt.Println("This password does not have any entropy, because it has been compromised.")
-			return
-		} else {
-			fmt.Println("Calc entropy here")
+			if err != nil {
+				fmt.Println("Something went wrong.")
+			} else if haveIBeenPwned == true {
+				fmt.Println("This password does not have any entropy, because it has been compromised.")
+				return
+			} else {
+				userInputs := strings.Fields(words)
+				strength := zxcvbn.PasswordStrength(passphrase, userInputs)
+				guesses := strength.Guesses
+				fmt.Printf("\nPassword:\t\t%s\n", passphrase)
+
+				fmt.Printf("Password strength:\t%d/4\n", strength.Score)
+
+				fmt.Printf("Guesses Log10:\t\t%f\n", math.Log10(float64(guesses)))
+				fmt.Println("\nGuess times")
+				fmt.Printf("100 / h:\t\t%s\t(throttled online attack)\n",secondsToHuman(guesses * (3600 / 100)))
+				fmt.Printf("10  / s:\t\t%s\t(unthrottled online attack)\n",secondsToHuman(guesses/10))
+				fmt.Printf("10k / s:\t\t%s\t(offline attack, slow hash, many cores)\n",secondsToHuman(guesses / 1e4))
+				fmt.Printf("10b / s:\t\t%s\t(offline attack, slow hash, many cores)\n",secondsToHuman(guesses / 1e10))
+			}
 		}
+	default:
+		myUsage()
+		fmt.Printf("\n%q is not valid command.\n", os.Args[1])
+		os.Exit(2)
 	}
 }