git.sophuwu.com > gophuwu   
              293
            
             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 <string> 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")

}