package main import ( "crypto/md5" "crypto/sha1" "crypto/sha256" "crypto/sha3" "crypto/sha512" "encoding/base32" "encoding/base64" "encoding/hex" "errors" "fmt" "git.sophuwu.com/gophuwu/flags" "git.sophuwu.com/gophuwu/parsers" "golang.org/x/term" "hash" "io" "os" "strings" ) func fatal(err error, msg string) { if err != nil { println("fatal: " + msg + ": " + err.Error()) os.Exit(1) } } func ErrHand(err error) { fmt.Printf("Fatal Error: %s\n", err.Error()) os.Exit(1) } func init() { newFlag := flags.NewNewFlagWithHandler(ErrHand) newFlag("algorithm", "a", "hash algorithm to use, -l to see list", "sha256") newFlag("encoder", "e", "text encoder to use, -l to see list", "hex") newFlag("list", "l", "list of available algorithms and encoders", false) newFlag("output", "o", "write output to file instead of stdout", "stdout") newFlag("newline", "n", "add a newline to the end of the output, auto/yes/no", "auto") newFlag("show", "s", "do not hide password on manual input", false) newFlag("password", "p", "use as password instead of reading stdin", "") newFlag("file", "f", "hash a file instead of a password", "") err := flags.ParseArgs() fatal(err, "could not parse flags") } var encoders = map[string]func(src []byte) []byte{ "base32": func(src []byte) []byte { return []byte(base32.StdEncoding.EncodeToString(src)) }, "base64": func(src []byte) []byte { return []byte(base64.StdEncoding.EncodeToString(src)) }, "base64url": func(src []byte) []byte { return []byte(base64.URLEncoding.EncodeToString(src)) }, "hex": func(src []byte) []byte { return []byte(hex.EncodeToString(src)) }, "raw": func(src []byte) []byte { return src }, } var shortEncoders = map[string]string{ "32": "base32", "64": "base64", "url": "base64url", "16": "hex", "b": "raw", } var autoNewline = true func getEnc() func(src []byte) []byte { encoder, err := flags.GetStringFlag("encoder") fatal(err, "could not get encoder flag") encoder = strings.ToLower(encoder) if s, ok := shortEncoders[encoder]; ok { encoder = s } autoNewline = encoder != "raw" && autoNewline if enc, ok := encoders[encoder]; ok { return enc } return nil } const algosList = "md5 sha1 sha256 sha256_224 sha512 sha512_224 sha512_256 sha512_384 sha3_224 sha3_256 sha3_384 sha3_512" var fmtSPad string = "%-5s" func init() { var h func() hash.Hash var ok bool for algo := range algosListShort { if h, ok = algos[algo]; !ok { fatal(errors.New("unknown algorithm: "+algo), "invalid algorithm") } algos[algosListShort[algo]] = h } var fmtNameLength = 5 for s := range algos { if len(s) > fmtNameLength { fmtNameLength = len(s) } } for s := range encoders { if len(s) > fmtNameLength { fmtNameLength = len(s) } } fmtNameLength += 1 fmtSPad = fmt.Sprintf("%%-%ds", fmtNameLength) } var algosListShort = map[string]string{ "sha1": "1", "sha512_384": "3", "sha512": "5", } var algos = map[string]func() hash.Hash{ "md5": func() hash.Hash { return md5.New() }, "sha1": func() hash.Hash { return sha1.New() }, "sha256": func() hash.Hash { return sha256.New() }, "sha256_224": func() hash.Hash { return sha256.New224() }, "sha512": func() hash.Hash { return sha512.New() }, "sha512_224": func() hash.Hash { return sha512.New512_224() }, "sha512_384": func() hash.Hash { return sha512.New384() }, "sha512_256": func() hash.Hash { return sha512.New512_256() }, "sha3_224": func() hash.Hash { return sha3.New224() }, "sha3_256": func() hash.Hash { return sha3.New256() }, "sha3_384": func() hash.Hash { return sha3.New384() }, "sha3_512": func() hash.Hash { return sha3.New512() }, } func getHashFunc() hash.Hash { algorithm, err := flags.GetStringFlag("algorithm") fatal(err, "could not get algorithm flag") algorithm = strings.ReplaceAll(algorithm, "-", "_") if h, ok := algos[algorithm]; ok { return h() } fatal(errors.New("unknown algorithm: "+algorithm), "invalid algorithm") return nil } func getOutFunc() func(w []byte) error { output, err := flags.GetStringFlag("output") fatal(err, "could not get output flag") if output == "stdout" { autoNewline = autoNewline && term.IsTerminal(1) return func(w []byte) error { _, e := os.Stdout.Write(w) return e } } if output != "" { _, err = os.Stat(output) if !errors.Is(err, os.ErrNotExist) { fmt.Printf("output file already exists, do you want to overwrite it? (y/n) ") line, _ := parsers.ReadLineString(os.Stdin) if line == "y" { fmt.Printf("\roverwriting: %s\n", output) } } return func(w []byte) error { f, e := os.Create(output) if e != nil { return e } _, e = f.Write(w) f.Close() return e } } fatal(errors.New("invalid output: "+output), "invalid output") return nil } func getInput() []byte { var err error var s string var password []byte s, err = flags.GetStringFlag("file") fatal(err, "could not get file flag") if s != "" { password, err = os.ReadFile(s) fatal(err, "could not read file") if len(password) == 0 { fatal(errors.New("file is empty"), "invalid file") } return password } s, err = flags.GetStringFlag("password") fatal(err, "could not get password flag") if s != "" { return []byte(s) } if !term.IsTerminal(0) { password, err = io.ReadAll(os.Stdin) fatal(err, "could not read from stdin") return password } var show bool show, err = flags.GetBoolFlag("show") fatal(err, "could not get show flag") if show { fmt.Print("Enter password to hash: ") password, err = parsers.ReadLine(os.Stdin) fatal(err, "could not read password") return password } fmt.Print("Enter password to hash: ") password, err = term.ReadPassword(0) fmt.Println() fatal(err, "could not read password") fmt.Print("Enter password again: ") var pas []byte pas, err = term.ReadPassword(0) fmt.Println() fatal(err, "could not read password") if string(pas) != string(password) { fmt.Println("Passwords do not match, please try again.") os.Exit(1) } return password } func pad(s string, l int) string { if len(s) >= l { return s } return fmt.Sprintf(fmt.Sprintf("%%-%ds", l), s) } func listAlgos() { ok, err := flags.GetBoolFlag("list") fatal(err, "could not get list flag") if !ok { return } fmt.Println("Available algorithms (short):") var short string var algo string for _, algo = range strings.Split(algosList, " ") { if short, ok = algosListShort[algo]; ok { fmt.Printf(" - "+fmtSPad+" (%s)\n", algo, short) continue } fmt.Printf(" - "+fmtSPad+"\n", algo) } fmt.Println("\nAvailable encoders (short):") for sh, lg := range shortEncoders { fmt.Printf(" - "+fmtSPad+" (%s)\n", lg, sh) } os.Exit(0) } func main() { listAlgos() var err error hashFunc := getHashFunc() enc := getEnc() out := getOutFunc() var line string line, err = flags.GetStringFlag("newline") fatal(err, "could not get newline flag") password := getInput() if len(password) == 0 { fatal(errors.New("password cannot be empty"), "invalid password") } var n int n, err = hashFunc.Write(password) fatal(err, "could not write password to hash function") if n != len(password) { fatal(errors.New("could not write all bytes to hash function"), "invalid password") } encHash := hashFunc.Sum(nil) encHash = enc(encHash) if line == "yes" || (line == "auto" && autoNewline) { encHash = append(encHash, '\n') } err = out(encHash) fatal(err, "could not write output") }