]>
Commit | Line | Data |
---|---|---|
9b12e4fe JC |
1 | // Copyright 2011 The Go Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style | |
3 | // license that can be found in the LICENSE file. | |
4 | ||
5 | // Package bcrypt implements Provos and Mazières's bcrypt adaptive hashing | |
6 | // algorithm. See http://www.usenix.org/event/usenix99/provos/provos.pdf | |
7 | package bcrypt // import "golang.org/x/crypto/bcrypt" | |
8 | ||
9 | // The code is a port of Provos and Mazières's C implementation. | |
10 | import ( | |
11 | "crypto/rand" | |
12 | "crypto/subtle" | |
13 | "errors" | |
14 | "fmt" | |
9b12e4fe JC |
15 | "io" |
16 | "strconv" | |
07971ca3 AP |
17 | |
18 | "golang.org/x/crypto/blowfish" | |
9b12e4fe JC |
19 | ) |
20 | ||
21 | const ( | |
22 | MinCost int = 4 // the minimum allowable cost as passed in to GenerateFromPassword | |
23 | MaxCost int = 31 // the maximum allowable cost as passed in to GenerateFromPassword | |
24 | DefaultCost int = 10 // the cost that will actually be set if a cost below MinCost is passed into GenerateFromPassword | |
25 | ) | |
26 | ||
27 | // The error returned from CompareHashAndPassword when a password and hash do | |
28 | // not match. | |
29 | var ErrMismatchedHashAndPassword = errors.New("crypto/bcrypt: hashedPassword is not the hash of the given password") | |
30 | ||
31 | // The error returned from CompareHashAndPassword when a hash is too short to | |
32 | // be a bcrypt hash. | |
33 | var ErrHashTooShort = errors.New("crypto/bcrypt: hashedSecret too short to be a bcrypted password") | |
34 | ||
35 | // The error returned from CompareHashAndPassword when a hash was created with | |
36 | // a bcrypt algorithm newer than this implementation. | |
37 | type HashVersionTooNewError byte | |
38 | ||
39 | func (hv HashVersionTooNewError) Error() string { | |
40 | return fmt.Sprintf("crypto/bcrypt: bcrypt algorithm version '%c' requested is newer than current version '%c'", byte(hv), majorVersion) | |
41 | } | |
42 | ||
43 | // The error returned from CompareHashAndPassword when a hash starts with something other than '$' | |
44 | type InvalidHashPrefixError byte | |
45 | ||
46 | func (ih InvalidHashPrefixError) Error() string { | |
47 | return fmt.Sprintf("crypto/bcrypt: bcrypt hashes must start with '$', but hashedSecret started with '%c'", byte(ih)) | |
48 | } | |
49 | ||
50 | type InvalidCostError int | |
51 | ||
52 | func (ic InvalidCostError) Error() string { | |
53 | return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d,%d)", int(ic), int(MinCost), int(MaxCost)) | |
54 | } | |
55 | ||
56 | const ( | |
57 | majorVersion = '2' | |
58 | minorVersion = 'a' | |
59 | maxSaltSize = 16 | |
60 | maxCryptedHashSize = 23 | |
61 | encodedSaltSize = 22 | |
62 | encodedHashSize = 31 | |
63 | minHashSize = 59 | |
64 | ) | |
65 | ||
66 | // magicCipherData is an IV for the 64 Blowfish encryption calls in | |
67 | // bcrypt(). It's the string "OrpheanBeholderScryDoubt" in big-endian bytes. | |
68 | var magicCipherData = []byte{ | |
69 | 0x4f, 0x72, 0x70, 0x68, | |
70 | 0x65, 0x61, 0x6e, 0x42, | |
71 | 0x65, 0x68, 0x6f, 0x6c, | |
72 | 0x64, 0x65, 0x72, 0x53, | |
73 | 0x63, 0x72, 0x79, 0x44, | |
74 | 0x6f, 0x75, 0x62, 0x74, | |
75 | } | |
76 | ||
77 | type hashed struct { | |
78 | hash []byte | |
79 | salt []byte | |
80 | cost int // allowed range is MinCost to MaxCost | |
81 | major byte | |
82 | minor byte | |
83 | } | |
84 | ||
85 | // GenerateFromPassword returns the bcrypt hash of the password at the given | |
86 | // cost. If the cost given is less than MinCost, the cost will be set to | |
87 | // DefaultCost, instead. Use CompareHashAndPassword, as defined in this package, | |
88 | // to compare the returned hashed password with its cleartext version. | |
89 | func GenerateFromPassword(password []byte, cost int) ([]byte, error) { | |
90 | p, err := newFromPassword(password, cost) | |
91 | if err != nil { | |
92 | return nil, err | |
93 | } | |
94 | return p.Hash(), nil | |
95 | } | |
96 | ||
97 | // CompareHashAndPassword compares a bcrypt hashed password with its possible | |
98 | // plaintext equivalent. Returns nil on success, or an error on failure. | |
99 | func CompareHashAndPassword(hashedPassword, password []byte) error { | |
100 | p, err := newFromHash(hashedPassword) | |
101 | if err != nil { | |
102 | return err | |
103 | } | |
104 | ||
105 | otherHash, err := bcrypt(password, p.cost, p.salt) | |
106 | if err != nil { | |
107 | return err | |
108 | } | |
109 | ||
110 | otherP := &hashed{otherHash, p.salt, p.cost, p.major, p.minor} | |
111 | if subtle.ConstantTimeCompare(p.Hash(), otherP.Hash()) == 1 { | |
112 | return nil | |
113 | } | |
114 | ||
115 | return ErrMismatchedHashAndPassword | |
116 | } | |
117 | ||
118 | // Cost returns the hashing cost used to create the given hashed | |
119 | // password. When, in the future, the hashing cost of a password system needs | |
120 | // to be increased in order to adjust for greater computational power, this | |
121 | // function allows one to establish which passwords need to be updated. | |
122 | func Cost(hashedPassword []byte) (int, error) { | |
123 | p, err := newFromHash(hashedPassword) | |
124 | if err != nil { | |
125 | return 0, err | |
126 | } | |
127 | return p.cost, nil | |
128 | } | |
129 | ||
130 | func newFromPassword(password []byte, cost int) (*hashed, error) { | |
131 | if cost < MinCost { | |
132 | cost = DefaultCost | |
133 | } | |
134 | p := new(hashed) | |
135 | p.major = majorVersion | |
136 | p.minor = minorVersion | |
137 | ||
138 | err := checkCost(cost) | |
139 | if err != nil { | |
140 | return nil, err | |
141 | } | |
142 | p.cost = cost | |
143 | ||
144 | unencodedSalt := make([]byte, maxSaltSize) | |
145 | _, err = io.ReadFull(rand.Reader, unencodedSalt) | |
146 | if err != nil { | |
147 | return nil, err | |
148 | } | |
149 | ||
150 | p.salt = base64Encode(unencodedSalt) | |
151 | hash, err := bcrypt(password, p.cost, p.salt) | |
152 | if err != nil { | |
153 | return nil, err | |
154 | } | |
155 | p.hash = hash | |
156 | return p, err | |
157 | } | |
158 | ||
159 | func newFromHash(hashedSecret []byte) (*hashed, error) { | |
160 | if len(hashedSecret) < minHashSize { | |
161 | return nil, ErrHashTooShort | |
162 | } | |
163 | p := new(hashed) | |
164 | n, err := p.decodeVersion(hashedSecret) | |
165 | if err != nil { | |
166 | return nil, err | |
167 | } | |
168 | hashedSecret = hashedSecret[n:] | |
169 | n, err = p.decodeCost(hashedSecret) | |
170 | if err != nil { | |
171 | return nil, err | |
172 | } | |
173 | hashedSecret = hashedSecret[n:] | |
174 | ||
175 | // The "+2" is here because we'll have to append at most 2 '=' to the salt | |
176 | // when base64 decoding it in expensiveBlowfishSetup(). | |
177 | p.salt = make([]byte, encodedSaltSize, encodedSaltSize+2) | |
178 | copy(p.salt, hashedSecret[:encodedSaltSize]) | |
179 | ||
180 | hashedSecret = hashedSecret[encodedSaltSize:] | |
181 | p.hash = make([]byte, len(hashedSecret)) | |
182 | copy(p.hash, hashedSecret) | |
183 | ||
184 | return p, nil | |
185 | } | |
186 | ||
187 | func bcrypt(password []byte, cost int, salt []byte) ([]byte, error) { | |
188 | cipherData := make([]byte, len(magicCipherData)) | |
189 | copy(cipherData, magicCipherData) | |
190 | ||
191 | c, err := expensiveBlowfishSetup(password, uint32(cost), salt) | |
192 | if err != nil { | |
193 | return nil, err | |
194 | } | |
195 | ||
196 | for i := 0; i < 24; i += 8 { | |
197 | for j := 0; j < 64; j++ { | |
198 | c.Encrypt(cipherData[i:i+8], cipherData[i:i+8]) | |
199 | } | |
200 | } | |
201 | ||
202 | // Bug compatibility with C bcrypt implementations. We only encode 23 of | |
203 | // the 24 bytes encrypted. | |
204 | hsh := base64Encode(cipherData[:maxCryptedHashSize]) | |
205 | return hsh, nil | |
206 | } | |
207 | ||
208 | func expensiveBlowfishSetup(key []byte, cost uint32, salt []byte) (*blowfish.Cipher, error) { | |
9b12e4fe JC |
209 | csalt, err := base64Decode(salt) |
210 | if err != nil { | |
211 | return nil, err | |
212 | } | |
213 | ||
214 | // Bug compatibility with C bcrypt implementations. They use the trailing | |
215 | // NULL in the key string during expansion. | |
07971ca3 AP |
216 | // We copy the key to prevent changing the underlying array. |
217 | ckey := append(key[:len(key):len(key)], 0) | |
9b12e4fe JC |
218 | |
219 | c, err := blowfish.NewSaltedCipher(ckey, csalt) | |
220 | if err != nil { | |
221 | return nil, err | |
222 | } | |
223 | ||
224 | var i, rounds uint64 | |
225 | rounds = 1 << cost | |
226 | for i = 0; i < rounds; i++ { | |
227 | blowfish.ExpandKey(ckey, c) | |
228 | blowfish.ExpandKey(csalt, c) | |
229 | } | |
230 | ||
231 | return c, nil | |
232 | } | |
233 | ||
234 | func (p *hashed) Hash() []byte { | |
235 | arr := make([]byte, 60) | |
236 | arr[0] = '$' | |
237 | arr[1] = p.major | |
238 | n := 2 | |
239 | if p.minor != 0 { | |
240 | arr[2] = p.minor | |
241 | n = 3 | |
242 | } | |
243 | arr[n] = '$' | |
244 | n += 1 | |
245 | copy(arr[n:], []byte(fmt.Sprintf("%02d", p.cost))) | |
246 | n += 2 | |
247 | arr[n] = '$' | |
248 | n += 1 | |
249 | copy(arr[n:], p.salt) | |
250 | n += encodedSaltSize | |
251 | copy(arr[n:], p.hash) | |
252 | n += encodedHashSize | |
253 | return arr[:n] | |
254 | } | |
255 | ||
256 | func (p *hashed) decodeVersion(sbytes []byte) (int, error) { | |
257 | if sbytes[0] != '$' { | |
258 | return -1, InvalidHashPrefixError(sbytes[0]) | |
259 | } | |
260 | if sbytes[1] > majorVersion { | |
261 | return -1, HashVersionTooNewError(sbytes[1]) | |
262 | } | |
263 | p.major = sbytes[1] | |
264 | n := 3 | |
265 | if sbytes[2] != '$' { | |
266 | p.minor = sbytes[2] | |
267 | n++ | |
268 | } | |
269 | return n, nil | |
270 | } | |
271 | ||
272 | // sbytes should begin where decodeVersion left off. | |
273 | func (p *hashed) decodeCost(sbytes []byte) (int, error) { | |
274 | cost, err := strconv.Atoi(string(sbytes[0:2])) | |
275 | if err != nil { | |
276 | return -1, err | |
277 | } | |
278 | err = checkCost(cost) | |
279 | if err != nil { | |
280 | return -1, err | |
281 | } | |
282 | p.cost = cost | |
283 | return 3, nil | |
284 | } | |
285 | ||
286 | func (p *hashed) String() string { | |
287 | return fmt.Sprintf("&{hash: %#v, salt: %#v, cost: %d, major: %c, minor: %c}", string(p.hash), p.salt, p.cost, p.major, p.minor) | |
288 | } | |
289 | ||
290 | func checkCost(cost int) error { | |
291 | if cost < MinCost || cost > MaxCost { | |
292 | return InvalidCostError(cost) | |
293 | } | |
294 | return nil | |
295 | } |