diff --git a/apg.go b/apg.go index 31ff529..fd34d6d 100644 --- a/apg.go +++ b/apg.go @@ -22,6 +22,7 @@ type Config struct { useNumber bool useSpecial bool humanReadable bool + checkHibp bool excludeChars string newStyleModes string spellPassword bool @@ -50,6 +51,8 @@ Options: -H Avoid ambiguous characters in passwords (i. e.: 1, l, I, O, 0) (Default: off) -C Enable complex password mode (implies -L -U -N -S and disables -H) (Default: off) -l Spell generated passwords in phonetic alphabet (Default: off) + -p Check the HIBP database if the generated passwords was found in a leak before (Default: off) + '--> this feature requires internet connectivity -h Show this help text -v Show version string` @@ -96,5 +99,15 @@ func main() { break } } + + if config.checkHibp { + isPwned, err := checkHibp(pwString) + if err != nil { + log.Printf("unable to check HIBP database: %v", err) + } + if isPwned { + fmt.Print("^-- !!WARNING: The previously generated password was found in HIPB database. Do not use it!!\n") + } + } } } diff --git a/config.go b/config.go index ea575c0..e362cb6 100644 --- a/config.go +++ b/config.go @@ -33,6 +33,7 @@ func parseFlags() Config { flag.BoolVar(&switchConf.useComplex, "C", false, "Generate complex passwords (implies -L -U -N -S, disables -H)") flag.BoolVar(&switchConf.humanReadable, "H", false, "Generate human-readable passwords") flag.BoolVar(&config.spellPassword, "l", false, "Spell generated password") + flag.BoolVar(&config.checkHibp, "p", false, "Check the HIBP database if the generated password was leaked before") flag.BoolVar(&config.showVersion, "v", false, "Show version") flag.IntVar(&config.minPassLen, "m", DefaultMinLenght, "Minimum password length") flag.IntVar(&config.maxPassLen, "x", DefaultMaxLenght, "Maxiumum password length") diff --git a/hibp.go b/hibp.go new file mode 100644 index 0000000..47d7e66 --- /dev/null +++ b/hibp.go @@ -0,0 +1,44 @@ +package main + +import ( + "bufio" + "crypto/sha1" + "fmt" + "log" + "net/http" + "strings" + "time" +) + +func checkHibp(p string) (bool, error) { + shaSum := fmt.Sprintf("%x", sha1.Sum([]byte(p))) + firstPart := shaSum[0:5] + secondPart := shaSum[5:] + isPwned := false + + httpClient := &http.Client{Timeout: time.Second * 2} + httpRes, err := httpClient.Get("https://api.pwnedpasswords.com/range/" + firstPart) + if err != nil { + return false, err + } + defer func() { + err := httpRes.Body.Close() + if err != nil { + log.Printf("error while closing HTTP response body: %v", err) + } + }() + + scanObj := bufio.NewScanner(httpRes.Body) + for scanObj.Scan() { + scanLine := strings.SplitN(scanObj.Text(), ":", 2) + if strings.ToLower(scanLine[0]) == secondPart { + isPwned = true + break + } + } + if err := scanObj.Err(); err != nil { + return isPwned, err + } + + return isPwned, nil +}