├── .gitignore ├── Makefile ├── README.md ├── build ├── go.mod ├── go.sum └── passhash.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | passhash 24 | passhash_* 25 | gopath/* 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: passhash strip 2 | 3 | cross: 4 | GOARM=5 GOARCH=arm GOOS=linux go build -o passhash_arm5 passhash.go 5 | GOARM=6 GOARCH=arm GOOS=linux go build -o passhash_arm6 passhash.go 6 | GOARM=7 GOARCH=arm GOOS=linux go build -o passhash_arm7 passhash.go 7 | GOARCH=386 GOOS=windows go build -o passhash_32.exe passhash.go 8 | GOARCH=amd64 GOOS=windows go build -o passhash_64.exe passhash.go 9 | 10 | passhash: passhash.go 11 | go build 12 | 13 | format: 14 | gofmt -s -tabs=false -tabwidth=4 -w=true passhash.go 15 | 16 | strip: passhash 17 | strip --strip-all passhash 18 | 19 | clean: 20 | rm -f passhash passhash_arm7 passhash_arm6 passhash_arm5 passhash_32.exe passhash_64.exe 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | passhash 2 | ======== 3 | 4 | Create Secure Password Hashes with different algorithms. 5 | 6 | I/O format is base64 conforming to RFC 4648 (also known as url safe base64 encoding). 7 | If no salt is provided a cryptographically strong pseudo-random generator is used to generate 8 | the salt through crypto/rand.Read (which uses either /dev/urandom on Unix like systems or 9 | CryptGenRandom API on Windows). 10 | 11 | Supported Key Derivation Functions with Default Parameters: 12 | 13 | *scrypt* default (CPU/memory cost parameter 1<<14)) 14 | bcrypt (cost value = 14) 15 | pbkdf2 (sha256 with 50000 rounds) 16 | 17 | Supported Algorithms (pbkdf2): 18 | 19 | sha1, sha256, sha224, sha384, sha512 20 | md4, md5 21 | 22 | Synopsis 23 | -------- 24 | 25 | Usage: 26 | passhash [OPTIONS] [salt] 27 | 28 | Help Options: 29 | -h, --help Show this help message 30 | 31 | Application Options: 32 | -r, --rounds Number of rounds (50000) 33 | --hash Hash to use (sha256) 34 | --kd Key derivation function (scrypt) 35 | -c, --cost Cost parameter to key derivation functions (14) 36 | 37 | Examples 38 | -------- 39 | 40 | Create scrypt hash with random generated salt 41 | 42 | % passhash foo 43 | CD41CIOQwUI9edSLfTrzLZMbPzcsw0wKURumS-AotvE=$Yg52dwMlJh2yuKoYAaBKEskDNtv961hRxWeW6AMXrnQ= 44 | 45 | Create default pbkdf2 hash 46 | 47 | % passhash --kd pbkdf2 foo 48 | 4NWdtaXy9ck3vT-yQiOQ2nkRKJWbUQWZFq9CHBf44DA=$8s5ucFdFBMEubBwIzaYLssqqRUO4Tag2MRqg-q8V-HY= 49 | 50 | Create pbkdf2 hash with user provided salt 51 | 52 | % passhash -r 10000 --hash sha1 --kd pbkdf2 foo SMI22KXjdX_s6vzNIUuZIBl7BaA= 53 | SMI22KXjdX_s6vzNIUuZIBl7BaA=$izW_dQvHLt8pCoud04kjlqc47gM= 54 | 55 | -------------------------------------------------------------------------------- /build: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | ORG_PATH="github.com/gebi" 4 | REPO_PATH="${ORG_PATH}/passhash" 5 | 6 | export GOPATH=${PWD}/gopath 7 | 8 | rm -f $GOPATH/src/${REPO_PATH} 9 | mkdir -p $GOPATH/src/${ORG_PATH} 10 | ln -s ${PWD} $GOPATH/src/${REPO_PATH} 11 | 12 | eval $(go env) 13 | 14 | go build -o passhash ${REPO_PATH} 15 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gebi/passhash 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/jessevdk/go-flags v1.5.0 7 | golang.org/x/crypto v0.18.0 8 | ) 9 | 10 | require golang.org/x/sys v0.16.0 // indirect 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= 2 | github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= 3 | golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= 4 | golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= 5 | golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 6 | golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= 7 | golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 8 | -------------------------------------------------------------------------------- /passhash.go: -------------------------------------------------------------------------------- 1 | /*passhash is a command line utility to generate secure password hashes with scrypt bcrypt pbkdf3 md5 sha1 sha256 sha512 2 | 3 | I/O format is base64 conforming to RFC 4648 (also known as url safe base64 encoding). 4 | If no salt is provided a cryptographically strong pseudo-random generator is used to generate 5 | the salt through crypto/rand.Read (which uses either /dev/urandom on Unix like systems or 6 | CryptGenRandom API on Windows). 7 | 8 | Supported Key Derivation Functions with Default Parameters: 9 | 10 | *scrypt* default (CPU/memory cost parameter 1<<14)) 11 | bcrypt (cost value = 14) 12 | pbkdf2 (sha256 with 50000 rounds) 13 | 14 | Supported Algorithms (pbkdf2): 15 | 16 | sha1, sha256, sha224, sha384, sha512 17 | md4, md5 18 | */ 19 | package main 20 | 21 | import ( 22 | "crypto/rand" 23 | "fmt" 24 | flags "github.com/jessevdk/go-flags" 25 | "hash" 26 | "log" 27 | "os" 28 | 29 | // hash 30 | "crypto/hmac" 31 | "crypto/md5" 32 | "crypto/sha1" 33 | "crypto/sha256" 34 | "crypto/sha512" 35 | "golang.org/x/crypto/md4" 36 | 37 | // key derivation function 38 | "golang.org/x/crypto/bcrypt" 39 | "golang.org/x/crypto/pbkdf2" 40 | "golang.org/x/crypto/scrypt" 41 | 42 | // output encoding 43 | "encoding/base64" 44 | ) 45 | 46 | var ( 47 | str2hash = map[string](func() hash.Hash){ 48 | "md4": md4.New, 49 | "md5": md5.New, 50 | "sha1": sha1.New, 51 | "sha224": sha256.New224, 52 | "sha256": sha256.New, 53 | "sha384": sha512.New384, 54 | "sha512": sha512.New, 55 | } 56 | ) 57 | 58 | func main() { 59 | var opts struct { 60 | Rounds int `short:"r" long:"rounds" default:"50000" description:"Number of rounds"` 61 | Hashname string `long:"hash" default:"sha256" description:"Hash to use"` 62 | Kdname string `long:"kd" description:"Key derivation function"` 63 | Cost int `short:"c" long:"cost" default:"14" description:"Cost parameter to key derivation functions"` 64 | Hmacenc string `long:"hmacenc" default:"" description:"Base64 encoded password for final hmac encryption step"` 65 | } 66 | opts.Rounds = 50000 67 | opts.Hashname = "sha256" 68 | opts.Kdname = "scrypt" 69 | opts.Cost = 14 70 | parser := flags.NewParser(&opts, flags.Default) 71 | parser.Usage = "[OPTIONS] [salt]" 72 | parser.Usage += "\n\nSupported:\n" 73 | parser.Usage += "\tscrpyt bcrypt pbkdf2\n" 74 | args, err := parser.Parse() 75 | if err != nil { 76 | os.Exit(1) 77 | } 78 | if len(args) == 0 { 79 | log.Fatal("Error: ", "Parameter password missing") 80 | } 81 | 82 | if opts.Kdname == "bcrypt" && opts.Hmacenc != "" { 83 | log.Fatal("Error: bcrypt hash output can not be encrypted") 84 | } 85 | if opts.Kdname == "scrypt" { 86 | opts.Hashname = "sha256" 87 | } 88 | var hmacencBin []byte 89 | if opts.Hmacenc != "" { 90 | hmacencBin, err = base64.URLEncoding.DecodeString(opts.Hmacenc) 91 | if err != nil { 92 | log.Fatal("Unable to decode hmac encryption password: ", err) 93 | } 94 | } 95 | //println(opts.Rounds); println(opts.Hashname); println(opts.Kdname); println(opts.Cost) 96 | h, hashAvailable := str2hash[opts.Hashname] 97 | if !hashAvailable { 98 | log.Fatal("Error: ", "Unknown hash given: ", opts.Hashname) 99 | } 100 | hashlength := h().Size() 101 | salt := make([]byte, hashlength) 102 | pw := []byte(args[0]) 103 | if len(args) == 2 { 104 | if opts.Kdname == "bcrypt" { 105 | log.Fatal("Error: ", "Salt not supported for bcrypt") 106 | } 107 | salt, err = base64.URLEncoding.DecodeString(args[1]) 108 | if err != nil { 109 | log.Fatal("Error: ", "Could not base64 decode salt: ", err) 110 | } 111 | if len(salt) != hashlength { 112 | log.Fatalf("Error: Salt not required size: %d needing %d bytes", len(salt), hashlength) 113 | } 114 | } else { 115 | n, err := rand.Read(salt) 116 | if n != len(salt) || err != nil { 117 | log.Fatal("Error: ", "Could not generate salt: ", err) 118 | } 119 | } 120 | var dk []byte 121 | 122 | switch opts.Kdname { 123 | case "pbkdf2": 124 | dk = pbkdf2.Key(pw, salt, opts.Rounds, hashlength, h) 125 | case "scrypt": 126 | dk, err = scrypt.Key(pw, salt, 1< bcrypt.MaxCost { 132 | log.Fatal("Error: ", "bcrypt: unsupported cost value") 133 | } 134 | dk, err = bcrypt.GenerateFromPassword(pw, opts.Cost) 135 | if err != nil { 136 | log.Fatal("Error: ", "in bcrypt: ", err) 137 | } 138 | // safeguard against bcrypt working with wrong cost value 139 | if realCost, err := bcrypt.Cost(dk); err != nil { 140 | panic(err) 141 | } else if opts.Cost != realCost { 142 | log.Fatal("Error: ", "bcrypt did not generate hash with user provided cost value") 143 | } 144 | default: 145 | log.Fatal("Error: unknown key derivation") 146 | } 147 | 148 | if opts.Hmacenc != "" { 149 | hmacEnc := hmac.New(h, hmacencBin) 150 | if _, err = hmacEnc.Write(dk); err != nil { 151 | log.Fatal("Error: error encrypting hash with hmac: ", err) 152 | } 153 | dk = hmacEnc.Sum(nil) 154 | } 155 | 156 | saltB64 := base64.URLEncoding.EncodeToString(salt) 157 | pwhashB64 := base64.URLEncoding.EncodeToString(dk) 158 | 159 | fmt.Printf("%s$%s\n", saltB64, pwhashB64) 160 | //fmt.Printf("%x\n", dk) 161 | } 162 | --------------------------------------------------------------------------------