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