From 32a22f986e28f574553af5a4540b5d74439f1492 Mon Sep 17 00:00:00 2001 From: Matthew Hartstonge Date: Tue, 17 Dec 2024 11:03:56 +1300 Subject: [PATCH 1/3] feat(cmd/argon2): adds argon2 cli. --- cmd/argon2/README.md | 27 +++++++ cmd/argon2/main.go | 164 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 cmd/argon2/README.md create mode 100644 cmd/argon2/main.go diff --git a/cmd/argon2/README.md b/cmd/argon2/README.md new file mode 100644 index 0000000..e7ce83b --- /dev/null +++ b/cmd/argon2/README.md @@ -0,0 +1,27 @@ +# argon2-cli + +## Usage + +```shell +Usage: + argon2 [options] p@ssw0rd + +Description: + Enables hashing passwords with argon via the CLI + +Options: + -hash-len uint + hash length specifies the length of the resulting hash in bytes. + -salt-len uint + salt length specifies the length of the resulting salt in bytes. + -m uint + memory cost specifies the amount of memory to use in kibibytes. + -p uint + parallelism cost + -t uint + time cost specifies the number of iterations of argon2. + -s + silent removes all cli output + -help + prints usage +``` diff --git a/cmd/argon2/main.go b/cmd/argon2/main.go new file mode 100644 index 0000000..f68f4f2 --- /dev/null +++ b/cmd/argon2/main.go @@ -0,0 +1,164 @@ +package main + +import ( + "errors" + "flag" + "fmt" + "math" + "os" + + "github.com/matthewhartstonge/argon2" +) + +type config struct { + silent bool + argon argon2.Config +} + +func main() { + cfg, err := parseFlags() + if err != nil { + cliPrintln(cfg, err) + os.Exit(1) + } + + pw, err := parsePassword() + if err != nil { + cliPrintln(cfg, err) + os.Exit(1) + } + + out, err := genHash(cfg, pw) + if err != nil { + cliPrintln(cfg, err) + os.Exit(1) + } + + fmt.Println(out) +} + +func parseFlags() (*config, error) { + t := flag.Uint("t", 0, "time cost specifies the number of iterations of argon2.") + m := flag.Uint("m", 0, "memory cost specifies the amount of memory to use in kibibytes.") + p := flag.Uint("p", 0, "parallelism cost") + hashLen := flag.Uint("hash-len", 0, "hash length specifies the length of the resulting hash in bytes.") + saltLen := flag.Uint("salt-len", 0, "salt length specifies the length of the resulting salt in bytes.") + s := flag.Bool("s", false, "silent removes all cli output") + + flag.Parse() + + cfg := &config{ + silent: *s, + } + + argon := argon2.RecommendedDefaults() + if *hashLen != 0 { + v, err := isUint32(*hashLen) + if err != nil { + return cfg, err + } + argon.HashLength = v + } + + if *saltLen != 0 { + v, err := isUint32(*saltLen) + if err != nil { + return cfg, err + } + argon.SaltLength = v + } + + if *t != 0 { + v, err := isUint32(*t) + if err != nil { + return cfg, err + } + argon.TimeCost = v + } + + if *m != 0 { + v, err := isUint32(*m) + if err != nil { + return cfg, err + } + argon.MemoryCost = v + } + + if *p != 0 { + v, err := isUint8(*p) + if err != nil { + return cfg, err + } + argon.Parallelism = v + } + + // inject argon config + cfg.argon = argon + + return cfg, nil +} + +// parsePassword extracts and returns the password to hash. +func parsePassword() (string, error) { + args := flag.Args() + if len(args) == 0 { + return "", errors.New("please provide a password to hash") + } + + return args[0], nil +} + +// genHash encodes and returns a stringified argon2 hash. +func genHash(cfg *config, password string) (string, error) { + cliPrintf(cfg, + "Generating argon2id hash with m=%d, t=%d, p=%d4...\n\n", + cfg.argon.MemoryCost, + cfg.argon.TimeCost, + cfg.argon.Parallelism, + ) + + enc, err := cfg.argon.HashEncoded([]byte(password)) + if err != nil { + return "", err + } + + return string(enc), nil +} + +// isUint8 performs bounds checking for uint8 +func isUint8(i uint) (uint8, error) { + if i < 0 || i > math.MaxUint8 { + return 0, fmt.Errorf("argon2: invalid uint8 value: %d", i) + } + + return uint8(i), nil +} + +// isUint32 performs bounds checking for uint32 +func isUint32(i uint) (uint32, error) { + if i < 0 || i > math.MaxUint32 { + return 0, fmt.Errorf("argon2: invalid uint32 value: %d", i) + } + + return uint32(i), nil +} + +// cliPrintf provides a fmt.Printf wrapper that doesn't print if silence is +// desired. +func cliPrintf(cfg *config, format string, a ...any) { + if cfg.silent { + return + } + + fmt.Printf(format, a...) +} + +// cliPrintln provides a fmt.Println wrapper that doesn't print if silence is +// desired. +func cliPrintln(cfg *config, a ...any) { + if cfg.silent { + return + } + + fmt.Println(a...) +} From 29e76358e66d407541a04aca724916903a97be1c Mon Sep 17 00:00:00 2001 From: Matthew Hartstonge Date: Fri, 20 Dec 2024 13:16:56 +1300 Subject: [PATCH 2/3] chore(cmd/argon2): adds custom usage signature. --- cmd/argon2/README.md | 34 ++++++++++++++++++------------- cmd/argon2/main.go | 48 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 57 insertions(+), 25 deletions(-) diff --git a/cmd/argon2/README.md b/cmd/argon2/README.md index e7ce83b..195fd3e 100644 --- a/cmd/argon2/README.md +++ b/cmd/argon2/README.md @@ -3,25 +3,31 @@ ## Usage ```shell -Usage: - argon2 [options] p@ssw0rd +NAME: + argon2 - An Argon2id CLI hash generator -Description: - Enables hashing passwords with argon via the CLI +USAGE: + argon2 [command options] p@ssw0rd -Options: - -hash-len uint - hash length specifies the length of the resulting hash in bytes. - -salt-len uint - salt length specifies the length of the resulting salt in bytes. +VERSION: + v0.1.3 + +AUTHOR: + Matthew Hartstonge - https://github.com/matthewhartstonge + +OPTIONS: -m uint memory cost specifies the amount of memory to use in kibibytes. -p uint - parallelism cost + parallelism cost specifies the number of parallel threads to spawn. -t uint time cost specifies the number of iterations of argon2. - -s - silent removes all cli output - -help - prints usage + -hash-len uint + hash length specifies the length of the resulting hash in bytes. + -salt-len uint + salt length specifies the length of the resulting salt in bytes. + +GLOBAL OPTIONS: + -h displays usage. + -s silent removes all cli output. ``` diff --git a/cmd/argon2/main.go b/cmd/argon2/main.go index f68f4f2..867cc3f 100644 --- a/cmd/argon2/main.go +++ b/cmd/argon2/main.go @@ -10,12 +10,31 @@ import ( "github.com/matthewhartstonge/argon2" ) +const ( + // AppName configures the binaries name. + AppName = "argon2" + // AppVersion outputs the binaries version. + AppVersion = "0.1.3" // x-release-please-version +) + +var ( + // configure flags + t = flag.Uint("t", 0, "time cost specifies the number of iterations of argon2.") + m = flag.Uint("m", 0, "memory cost specifies the amount of memory to use in kibibytes.") + p = flag.Uint("p", 0, "parallelism cost specifies the number of parallel threads to spawn.") + hashLen = flag.Uint("hash-len", 0, "hash length specifies the length of the resulting hash in bytes.") + saltLen = flag.Uint("salt-len", 0, "salt length specifies the length of the resulting salt in bytes.") + s = flag.Bool("s", false, "silent removes all cli output.") +) + type config struct { silent bool argon argon2.Config } func main() { + setupFlagUsage() + cfg, err := parseFlags() if err != nil { cliPrintln(cfg, err) @@ -28,7 +47,7 @@ func main() { os.Exit(1) } - out, err := genHash(cfg, pw) + out, err := hash(cfg, pw) if err != nil { cliPrintln(cfg, err) os.Exit(1) @@ -37,14 +56,21 @@ func main() { fmt.Println(out) } -func parseFlags() (*config, error) { - t := flag.Uint("t", 0, "time cost specifies the number of iterations of argon2.") - m := flag.Uint("m", 0, "memory cost specifies the amount of memory to use in kibibytes.") - p := flag.Uint("p", 0, "parallelism cost") - hashLen := flag.Uint("hash-len", 0, "hash length specifies the length of the resulting hash in bytes.") - saltLen := flag.Uint("salt-len", 0, "salt length specifies the length of the resulting salt in bytes.") - s := flag.Bool("s", false, "silent removes all cli output") +// setupFlagUsage configures the binaries custom usage signature. +func setupFlagUsage() { + flag.Usage = func() { + _, _ = fmt.Fprintf(flag.CommandLine.Output(), "NAME:\n\t%s - An Argon2id CLI hash generator\n\n", AppName) + _, _ = fmt.Fprintf(flag.CommandLine.Output(), "USAGE:\n\t%s [command options] p@ssw0rd\n\n", AppName) + _, _ = fmt.Fprintf(flag.CommandLine.Output(), "VERSION:\n\tv%s\n\n", AppVersion) + _, _ = fmt.Fprintf(flag.CommandLine.Output(), "AUTHOR:\n\tMatthew Hartstonge - https://github.com/matthewhartstonge\n\n") + _, _ = fmt.Fprintf(flag.CommandLine.Output(), "OPTIONS:\n") + flag.PrintDefaults() + } +} +// parseFlags extracts, validates and configures argon2 by the based on the +// set flag options. +func parseFlags() (*config, error) { flag.Parse() cfg := &config{ @@ -98,7 +124,7 @@ func parseFlags() (*config, error) { return cfg, nil } -// parsePassword extracts and returns the password to hash. +// parsePassword extracts and returns the password to hash from args. func parsePassword() (string, error) { args := flag.Args() if len(args) == 0 { @@ -108,8 +134,8 @@ func parsePassword() (string, error) { return args[0], nil } -// genHash encodes and returns a stringified argon2 hash. -func genHash(cfg *config, password string) (string, error) { +// hash encodes and returns a stringified argon2 hash. +func hash(cfg *config, password string) (string, error) { cliPrintf(cfg, "Generating argon2id hash with m=%d, t=%d, p=%d4...\n\n", cfg.argon.MemoryCost, From 61cb37fe12b64816bd5f3ced63930318e376c8d5 Mon Sep 17 00:00:00 2001 From: Matthew Hartstonge Date: Fri, 20 Dec 2024 13:21:32 +1300 Subject: [PATCH 3/3] ci(cmd/argon2): removes lower bounds checking for uint. --- cmd/argon2/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/argon2/main.go b/cmd/argon2/main.go index 867cc3f..0a5a738 100644 --- a/cmd/argon2/main.go +++ b/cmd/argon2/main.go @@ -153,7 +153,7 @@ func hash(cfg *config, password string) (string, error) { // isUint8 performs bounds checking for uint8 func isUint8(i uint) (uint8, error) { - if i < 0 || i > math.MaxUint8 { + if i > math.MaxUint8 { return 0, fmt.Errorf("argon2: invalid uint8 value: %d", i) } @@ -162,7 +162,7 @@ func isUint8(i uint) (uint8, error) { // isUint32 performs bounds checking for uint32 func isUint32(i uint) (uint32, error) { - if i < 0 || i > math.MaxUint32 { + if i > math.MaxUint32 { return 0, fmt.Errorf("argon2: invalid uint32 value: %d", i) }