forked from Freeaqingme/phpass
-
Notifications
You must be signed in to change notification settings - Fork 0
/
hash.go
156 lines (138 loc) · 3.75 KB
/
hash.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
// Package phpass Provides the ability to create and validate PHPass hashed
// passwords. See http://www.openwall.com/phpass/ for more details. The code
// here is more or less a direct port of the PHP implimentation found inside
// the official download. Or will be once it has been completed.
//
// The code as it stands is not 100% complete in that it does not work with
// all of the options that can/should be speficied. It does work with the
// default options, and is compatible with WordPress's use of PHPass for
// hasing passwords in the database
package phpass
// BUG(): Non-Default configurations are not supported at this time. Obviously
// they are planned, but have not been gotten around to yet.
import (
"bytes"
"crypto/md5"
"crypto/rand"
"crypto/subtle"
"errors"
"hash"
"strings"
)
var (
errBadID = errors.New("bad ID")
errBadCount = errors.New("bad count")
)
// Hash allows you to hash, and validate PHPass hashed passwords. The Hash
// structure is not thread safe. If you plan to use a single hasher you'll want
// to synchronize with your own syc.Mutex
type Hash struct {
Config *Config
MD5er hash.Hash
}
// Hash takes a returns a PHPass hash given the input password
func (h *Hash) Hash(pw []byte) ([]byte, error) {
// $random = $this->get_random_bytes(6);
// $this->crypt_private($password, $this->gensalt_private($random));
return h.crypt(pw, h.salt())
}
// Check validates the given password against the given hash, returning true if
// they match, otherwise false
func (h *Hash) Check(pw, pwhash []byte) bool {
generated, err := h.crypt(pw, pwhash)
if err != nil {
return false
}
//if generated[0] == 42 {
// generated = ?crypt?(password, hash)
//}
return 1 == subtle.ConstantTimeCompare(generated, pwhash)
}
func (h *Hash) crypt(pw, pwhash []byte) ([]byte, error) {
var rval []byte
if bytes.Equal(pwhash[0:2], []byte("*0")) {
rval = []byte("*1")
} else {
rval = []byte("*0")
}
var id = string(pwhash[0:3])
if id != "$P$" && id != "$H$" {
return rval, errBadID
}
var count = uint(strings.IndexByte(h.Config.Itoa, pwhash[3]))
if count < 7 || count > 30 {
return rval, errBadCount
}
count = 1 << count
var salt = pwhash[4:12]
h.MD5er.Reset()
h.MD5er.Write(salt)
h.MD5er.Write(pw)
var checksum = h.MD5er.Sum(nil)
for i := uint(0); i < count; i++ {
h.MD5er.Reset()
h.MD5er.Write(checksum)
h.MD5er.Write(pw)
checksum = h.MD5er.Sum(nil)
}
rval = []byte{}
rval = append(rval, pwhash[:12]...)
rval = append(rval, h.encode(checksum, 16)...)
return rval, nil
}
func (h *Hash) encode(input []byte, count int) []byte {
var rval = []byte{}
var i = 0
for {
value := int(input[i])
i++
rval = append(rval, h.Config.Itoa[(value&0x3f)])
if i < count {
value = value | int(input[i])<<8
}
rval = append(rval, h.Config.Itoa[((value>>6)&0x3f)])
if i >= count {
break
}
i++
if i < count {
value = value | int(input[i])<<16
}
rval = append(rval, h.Config.Itoa[((value>>12)&0x3f)])
if i >= count {
break
}
i++
rval = append(rval, h.Config.Itoa[((value>>18)&0x3f)])
if i >= count {
break
}
}
return rval
}
func (h *Hash) salt() []byte {
var input = make([]byte, 6)
if _, err := rand.Read(input); err != nil {
panic(err)
}
var i = h.Config.Count + 5
if i > 30 {
i = 30
}
return append([]byte("$P$"), append([]byte{h.Config.Itoa[i]}, h.encode(input, 6)...)...)
}
// New returns a new Hash structure against which you can make Hash() and
// Check() calls for creating and validating PHPass hashed passwords. If
// you pass nil as the config a default config will be provided for you.
func New(config *Config) *Hash {
if config == nil {
config = NewConfig()
}
if config.Count < 4 || config.Count > 31 {
config.Count = 8
}
return &Hash{
Config: config,
MD5er: md5.New(),
}
}