/
passhash.go
161 lines (145 loc) · 4.52 KB
/
passhash.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/*passhash is a command line utility to generate secure password hashes with scrypt bcrypt pbkdf3 md5 sha1 sha256 sha512
I/O format is base64 conforming to RFC 4648 (also known as url safe base64 encoding).
If no salt is provided a cryptographically strong pseudo-random generator is used to generate
the salt through crypto/rand.Read (which uses either /dev/urandom on Unix like systems or
CryptGenRandom API on Windows).
Supported Key Derivation Functions with Default Parameters:
*scrypt* default (CPU/memory cost parameter 1<<14))
bcrypt (cost value = 14)
pbkdf2 (sha256 with 50000 rounds)
Supported Algorithms (pbkdf2):
sha1, sha256, sha224, sha384, sha512
md4, md5
*/
package main
import (
"crypto/rand"
"fmt"
flags "github.com/jessevdk/go-flags"
"hash"
"log"
"os"
// hash
"crypto/hmac"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"golang.org/x/crypto/md4"
// key derivation function
"golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/crypto/scrypt"
// output encoding
"encoding/base64"
)
var (
str2hash = map[string](func() hash.Hash){
"md4": md4.New,
"md5": md5.New,
"sha1": sha1.New,
"sha224": sha256.New224,
"sha256": sha256.New,
"sha384": sha512.New384,
"sha512": sha512.New,
}
)
func main() {
var opts struct {
Rounds int `short:"r" long:"rounds" default:"50000" description:"Number of rounds"`
Hashname string `long:"hash" default:"sha256" description:"Hash to use"`
Kdname string `long:"kd" description:"Key derivation function"`
Cost int `short:"c" long:"cost" default:"14" description:"Cost parameter to key derivation functions"`
Hmacenc string `long:"hmacenc" default:"" description:"Base64 encoded password for final hmac encryption step"`
}
opts.Rounds = 50000
opts.Hashname = "sha256"
opts.Kdname = "scrypt"
opts.Cost = 14
parser := flags.NewParser(&opts, flags.Default)
parser.Usage = "[OPTIONS] <password> [salt]"
parser.Usage += "\n\nSupported:\n"
parser.Usage += "\tscrpyt bcrypt pbkdf2\n"
args, err := parser.Parse()
if err != nil {
os.Exit(1)
}
if len(args) == 0 {
log.Fatal("Error: ", "Parameter password missing")
}
if opts.Kdname == "bcrypt" && opts.Hmacenc != "" {
log.Fatal("Error: bcrypt hash output can not be encrypted")
}
if opts.Kdname == "scrypt" {
opts.Hashname = "sha256"
}
var hmacencBin []byte
if opts.Hmacenc != "" {
hmacencBin, err = base64.URLEncoding.DecodeString(opts.Hmacenc)
if err != nil {
log.Fatal("Unable to decode hmac encryption password: ", err)
}
}
//println(opts.Rounds); println(opts.Hashname); println(opts.Kdname); println(opts.Cost)
h, hashAvailable := str2hash[opts.Hashname]
if !hashAvailable {
log.Fatal("Error: ", "Unknown hash given: ", opts.Hashname)
}
hashlength := h().Size()
salt := make([]byte, hashlength)
pw := []byte(args[0])
if len(args) == 2 {
if opts.Kdname == "bcrypt" {
log.Fatal("Error: ", "Salt not supported for bcrypt")
}
salt, err = base64.URLEncoding.DecodeString(args[1])
if err != nil {
log.Fatal("Error: ", "Could not base64 decode salt: ", err)
}
if len(salt) != hashlength {
log.Fatalf("Error: Salt not required size: %d needing %d bytes", len(salt), hashlength)
}
} else {
n, err := rand.Read(salt)
if n != len(salt) || err != nil {
log.Fatal("Error: ", "Could not generate salt: ", err)
}
}
var dk []byte
switch opts.Kdname {
case "pbkdf2":
dk = pbkdf2.Key(pw, salt, opts.Rounds, hashlength, h)
case "scrypt":
dk, err = scrypt.Key(pw, salt, 1<<uint(opts.Cost), 8, 1, 32)
if err != nil {
log.Fatal("Error: ", "in scrypt: ", err)
}
case "bcrypt":
if opts.Cost < bcrypt.MinCost || opts.Cost > bcrypt.MaxCost {
log.Fatal("Error: ", "bcrypt: unsupported cost value")
}
dk, err = bcrypt.GenerateFromPassword(pw, opts.Cost)
if err != nil {
log.Fatal("Error: ", "in bcrypt: ", err)
}
// safeguard against bcrypt working with wrong cost value
if realCost, err := bcrypt.Cost(dk); err != nil {
panic(err)
} else if opts.Cost != realCost {
log.Fatal("Error: ", "bcrypt did not generate hash with user provided cost value")
}
default:
log.Fatal("Error: unknown key derivation")
}
if opts.Hmacenc != "" {
hmacEnc := hmac.New(h, hmacencBin)
if _, err = hmacEnc.Write(dk); err != nil {
log.Fatal("Error: error encrypting hash with hmac: ", err)
}
dk = hmacEnc.Sum(nil)
}
saltB64 := base64.URLEncoding.EncodeToString(salt)
pwhashB64 := base64.URLEncoding.EncodeToString(dk)
fmt.Printf("%s$%s\n", saltB64, pwhashB64)
//fmt.Printf("%x\n", dk)
}