Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(818)

Delta Between Two Patch Sets: src/pkg/crypto/bcrypt/bcrypt.go

Issue 4964078: code review 4964078: crypto/bcrypt: new package (Closed)
Left Patch Set: diff -r d21944c38c39 https://go.googlecode.com/hg/ Created 12 years, 6 months ago
Right Patch Set: diff -r d21944c38c39 https://go.googlecode.com/hg/ Created 12 years, 6 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « src/pkg/crypto/bcrypt/base64.go ('k') | src/pkg/crypto/bcrypt/bcrypt_test.go » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 // Copyright 2011 The Go Authors. All rights reserved. 1 // Copyright 2011 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style 2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file. 3 // license that can be found in the LICENSE file.
4 4
5 // Package bcrypt implements Provos and Mazières's bcrypt adapative hashing 5 // Package bcrypt implements Provos and Mazières's bcrypt adapative hashing
6 // algorithm. See http://www.usenix.org/event/usenix99/provos/provos.pdf 6 // algorithm. See http://www.usenix.org/event/usenix99/provos/provos.pdf
7 package bcrypt 7 package bcrypt
8 8
9 // The code is a port of Provos and Mazières's C implementation.· 9 // The code is a port of Provos and Mazières's C implementation.·
10 import ( 10 import (
11 "crypto/blowfish" 11 "crypto/blowfish"
12 "crypto/rand" 12 "crypto/rand"
13 "crypto/subtle" 13 "crypto/subtle"
14 "fmt" 14 "fmt"
15 "io" 15 "io"
16 "os" 16 "os"
17 "strconv" 17 "strconv"
18 ) 18 )
19 19
20 const ( 20 const (
21 MinCost int = 4 // the minimum allowable cost as passed in to Gener ateFromPassword 21 MinCost int = 4 // the minimum allowable cost as passed in to Gener ateFromPassword
22 MaxCost int = 31 // the maximum allowable cost as passed in to Gener ateFromPassword 22 MaxCost int = 31 // the maximum allowable cost as passed in to Gener ateFromPassword
23 DefaultCost int = 10 // the cost that will actually be set if a cost bel ow MinCost is passed into GenerateFromPassword 23 DefaultCost int = 10 // the cost that will actually be set if a cost bel ow MinCost is passed into GenerateFromPassword
24 ) 24 )
25 25
26 // The error returned from CompareHashAndPassword when a password and hash do 26 // The error returned from CompareHashAndPassword when a password and hash do
27 // not match. 27 // not match.
28 var MismatchedHashAndPassword = os.NewError("crypto:bcrypt: hashedPassword is no t the hash of the given password") 28 var MismatchedHashAndPasswordError = os.NewError("crypto/bcrypt: hashedPassword is not the hash of the given password")
agl1 2011/09/16 20:43:47 The convention seems to be that errors have a name
agl1 2011/09/16 20:43:47 s!crypto:!crypto/!
29 29
30 // The error returned from CompareHashAndPassword when a hash is too short to 30 // The error returned from CompareHashAndPassword when a hash is too short to
31 // be a bcrypt hash. 31 // be a bcrypt hash.
32 var HashTooShort = os.NewError("crypto/bcrypt: hashedSecret too short to be a bc rypted password") 32 var HashTooShortError = os.NewError("crypto/bcrypt: hashedSecret too short to be a bcrypted password")
33 33
34 // The error returned from CompareHashAndPassword when a hash was created with 34 // The error returned from CompareHashAndPassword when a hash was created with
35 // a bcrypt algorithm newer than this implementation. 35 // a bcrypt algorithm newer than this implementation.
36 type HashVersionTooNew byte 36 type HashVersionTooNewError byte
37 37
38 func (hv HashVersionTooNew) String() string { 38 func (hv HashVersionTooNewError) String() string {
39 return fmt.Sprintf("crypto/bcrypt: bcrypt algorithm version '%c' request ed is newer than current version '%c'", byte(hv), majorVersion) 39 return fmt.Sprintf("crypto/bcrypt: bcrypt algorithm version '%c' request ed is newer than current version '%c'", byte(hv), majorVersion)
40 } 40 }
41 41
42 // The error returned from CompareHashAndPassword when a hash starts with someth ing other than '$' 42 // The error returned from CompareHashAndPassword when a hash starts with someth ing other than '$'
43 type InvalidHashPrefix byte 43 type InvalidHashPrefixError byte
44 44
45 func (ih InvalidHashPrefix) String() string { 45 func (ih InvalidHashPrefixError) String() string {
46 return fmt.Sprintf("crypto/bcrypt: bcrypt hashes must start with '$', bu t hashedSecret started with '%c'", byte(ih)) 46 return fmt.Sprintf("crypto/bcrypt: bcrypt hashes must start with '$', bu t hashedSecret started with '%c'", byte(ih))
47 } 47 }
48 48
49 type InvalidCost int 49 type InvalidCostError int
50 50
51 func (ic InvalidCost) String() string { 51 func (ic InvalidCostError) String() string {
52 return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d, %d)", int(ic), int(MinCost), int(MaxCost)) 52 return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d, %d)", int(ic), int(MinCost), int(MaxCost))
53 } 53 }
54 54
55 const ( 55 const (
56 majorVersion = '2' 56 majorVersion = '2'
57 minorVersion = 'a' 57 minorVersion = 'a'
58 maxSaltSize = 16 58 maxSaltSize = 16
59 maxCryptedHashSize = 23 59 maxCryptedHashSize = 23
60 encodedSaltSize = 22 60 encodedSaltSize = 22
61 encodedHashSize = 31 61 encodedHashSize = 31
62 minHashSize = 59
62 ) 63 )
63 64
64 // magicCipherData is an IV for the 64 Blowfish encryption calls in 65 // magicCipherData is an IV for the 64 Blowfish encryption calls in
65 // bcrypt(). It's the string "OrpheanBeholderScryDoubt" in big-endian bytes. 66 // bcrypt(). It's the string "OrpheanBeholderScryDoubt" in big-endian bytes.
66 var magicCipherData = []byte{ 67 var magicCipherData = []byte{
67 0x4f, 0x72, 0x70, 0x68, 68 0x4f, 0x72, 0x70, 0x68,
68 0x65, 0x61, 0x6e, 0x42, 69 0x65, 0x61, 0x6e, 0x42,
69 0x65, 0x68, 0x6f, 0x6c, 70 0x65, 0x68, 0x6f, 0x6c,
70 0x64, 0x65, 0x72, 0x53, 71 0x64, 0x65, 0x72, 0x53,
71 0x63, 0x72, 0x79, 0x44, 72 0x63, 0x72, 0x79, 0x44,
72 0x6f, 0x75, 0x62, 0x74, 73 0x6f, 0x75, 0x62, 0x74,
73 } 74 }
74 75
75 type hashed struct { 76 type hashed struct {
76 hash []byte 77 hash []byte
77 salt []byte 78 salt []byte
78 cost uint32 // allowed range is MinCost to MaxCost 79 cost uint32 // allowed range is MinCost to MaxCost
79 major byte 80 major byte
80 minor byte 81 minor byte
81 } 82 }
82 83
83 // GenerateFromPassword returns the bcrypt hash of the password at the given 84 // GenerateFromPassword returns the bcrypt hash of the password at the given
84 // cost. If the cost given is less than MinCost, the cost will be set to 85 // cost. If the cost given is less than MinCost, the cost will be set to
85 // MinCost, instead. Use Equal, as defined in this package, to compare the 86 // MinCost, instead. Use CompareHashAndPassword, as defined in this package,
agl1 2011/09/16 20:43:47 Equal has been renamed, right?
86 // returned hashed password with its cleartext version. 87 // to compare the returned hashed password with its cleartext version.
87 func GenerateFromPassword(password []byte, cost int) ([]byte, os.Error) { 88 func GenerateFromPassword(password []byte, cost int) ([]byte, os.Error) {
88 p, err := newFromPassword(password, cost) 89 p, err := newFromPassword(password, cost)
89 if err != nil { 90 if err != nil {
90 return nil, err 91 return nil, err
91 } 92 }
92 return p.Hash(), nil 93 return p.Hash(), nil
93 } 94 }
94 95
95 // CompareHashAndPassword compares a bcrypt hashed password with its possible 96 // CompareHashAndPassword compares a bcrypt hashed password with its possible
96 // plaintext equivalent. Note: Using bytes.Equal for this job is 97 // plaintext equivalent. Note: Using bytes.Equal for this job is
97 // insecure. Returns nil on success, or an error on failure. 98 // insecure. Returns nil on success, or an error on failure.
98 func CompareHashAndPassword(hashedPassword, password []byte) os.Error { 99 func CompareHashAndPassword(hashedPassword, password []byte) os.Error {
99 p, err := newFromHash(hashedPassword) 100 p, err := newFromHash(hashedPassword)
100 if err != nil { 101 if err != nil {
101 return err 102 return err
102 } 103 }
103 104
104 otherHash, err := bcrypt(password, p.cost, p.salt) 105 otherHash, err := bcrypt(password, p.cost, p.salt)
105 if err != nil { 106 if err != nil {
106 return err 107 return err
107 } 108 }
108 109
109 otherP := &hashed{otherHash, p.salt, p.cost, p.major, p.minor} 110 otherP := &hashed{otherHash, p.salt, p.cost, p.major, p.minor}
110 if subtle.ConstantTimeCompare(p.Hash(), otherP.Hash()) == 1 { 111 if subtle.ConstantTimeCompare(p.Hash(), otherP.Hash()) == 1 {
111 return nil 112 return nil
112 } 113 }
113 114
114 » return MismatchedHashAndPassword 115 » return MismatchedHashAndPasswordError
115 } 116 }
116 117
117 func newFromPassword(password []byte, cost int) (*hashed, os.Error) { 118 func newFromPassword(password []byte, cost int) (*hashed, os.Error) {
118 if cost < MinCost { 119 if cost < MinCost {
119 cost = DefaultCost 120 cost = DefaultCost
120 } 121 }
121 p := new(hashed) 122 p := new(hashed)
122 p.major = majorVersion 123 p.major = majorVersion
123 p.minor = minorVersion 124 p.minor = minorVersion
124 125
125 err := checkCost(cost) 126 err := checkCost(cost)
126 if err != nil { 127 if err != nil {
127 return nil, err 128 return nil, err
128 } 129 }
129 p.cost = uint32(cost) 130 p.cost = uint32(cost)
130 131
131 unencodedSalt := make([]byte, maxSaltSize) 132 unencodedSalt := make([]byte, maxSaltSize)
132 » io.ReadFull(rand.Reader, unencodedSalt) 133 » _, err = io.ReadFull(rand.Reader, unencodedSalt)
134 » if err != nil {
135 » » return nil, err
136 » }
133 137
134 p.salt = base64Encode(unencodedSalt) 138 p.salt = base64Encode(unencodedSalt)
135 hash, err := bcrypt(password, p.cost, p.salt) 139 hash, err := bcrypt(password, p.cost, p.salt)
136 if err != nil { 140 if err != nil {
137 return nil, err 141 return nil, err
138 } 142 }
139 p.hash = hash 143 p.hash = hash
140 return p, err 144 return p, err
141 } 145 }
142 146
143 func newFromHash(hashedSecret []byte) (*hashed, os.Error) { 147 func newFromHash(hashedSecret []byte) (*hashed, os.Error) {
148 if len(hashedSecret) < minHashSize {
149 return nil, HashTooShortError
150 }
144 p := new(hashed) 151 p := new(hashed)
145 n, err := p.decodeVersion(hashedSecret) 152 n, err := p.decodeVersion(hashedSecret)
146 if err != nil { 153 if err != nil {
147 return nil, err 154 return nil, err
148 } 155 }
149 hashedSecret = hashedSecret[n:] 156 hashedSecret = hashedSecret[n:]
150 n, err = p.decodeCost(hashedSecret) 157 n, err = p.decodeCost(hashedSecret)
151 if err != nil { 158 if err != nil {
152 return nil, err 159 return nil, err
153 } 160 }
154 hashedSecret = hashedSecret[n:] 161 hashedSecret = hashedSecret[n:]
155
156 if len(hashedSecret) < encodedSaltSize+encodedHashSize {
157 return nil, HashTooShort
158 }
159 162
160 // The "+2" is here because we'll have to append at most 2 '=' to the sa lt 163 // The "+2" is here because we'll have to append at most 2 '=' to the sa lt
161 // when base64 decoding it in expensiveBlowfishSetup(). 164 // when base64 decoding it in expensiveBlowfishSetup().
162 p.salt = make([]byte, encodedSaltSize, encodedSaltSize+2) 165 p.salt = make([]byte, encodedSaltSize, encodedSaltSize+2)
163 copy(p.salt, hashedSecret[:encodedSaltSize]) 166 copy(p.salt, hashedSecret[:encodedSaltSize])
164 167
165 hashedSecret = hashedSecret[encodedSaltSize:] 168 hashedSecret = hashedSecret[encodedSaltSize:]
166 p.hash = make([]byte, len(hashedSecret)) 169 p.hash = make([]byte, len(hashedSecret))
167 copy(p.hash, hashedSecret) 170 copy(p.hash, hashedSecret)
168 171
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after
232 n += 1 235 n += 1
233 copy(arr[n:], p.salt) 236 copy(arr[n:], p.salt)
234 n += encodedSaltSize 237 n += encodedSaltSize
235 copy(arr[n:], p.hash) 238 copy(arr[n:], p.hash)
236 n += encodedHashSize 239 n += encodedHashSize
237 return arr[:n] 240 return arr[:n]
238 } 241 }
239 242
240 func (p *hashed) decodeVersion(sbytes []byte) (int, os.Error) { 243 func (p *hashed) decodeVersion(sbytes []byte) (int, os.Error) {
241 if sbytes[0] != '$' { 244 if sbytes[0] != '$' {
242 » » return -1, InvalidHashPrefix(sbytes[0]) 245 » » return -1, InvalidHashPrefixError(sbytes[0])
243 } 246 }
244 if sbytes[1] > majorVersion { 247 if sbytes[1] > majorVersion {
245 » » return -1, HashVersionTooNew(sbytes[1]) 248 » » return -1, HashVersionTooNewError(sbytes[1])
246 } 249 }
247 p.major = sbytes[1] 250 p.major = sbytes[1]
248 n := 3 251 n := 3
249 if sbytes[2] != '$' { 252 if sbytes[2] != '$' {
250 p.minor = sbytes[2] 253 p.minor = sbytes[2]
251 n++ 254 n++
252 } 255 }
253 return n, nil 256 return n, nil
254 } 257 }
255 258
(...skipping 10 matching lines...) Expand all
266 p.cost = uint32(cost) 269 p.cost = uint32(cost)
267 return 3, nil 270 return 3, nil
268 } 271 }
269 272
270 func (p *hashed) String() string { 273 func (p *hashed) String() string {
271 return fmt.Sprintf("&{hash: %#v, salt: %#v, cost: %d, major: %c, minor: %c}", string(p.hash), p.salt, p.cost, p.major, p.minor) 274 return fmt.Sprintf("&{hash: %#v, salt: %#v, cost: %d, major: %c, minor: %c}", string(p.hash), p.salt, p.cost, p.major, p.minor)
272 } 275 }
273 276
274 func checkCost(cost int) os.Error { 277 func checkCost(cost int) os.Error {
275 if cost < MinCost || cost > MaxCost { 278 if cost < MinCost || cost > MaxCost {
276 » » return InvalidCost(cost) 279 » » return InvalidCostError(cost)
277 } 280 }
278 return nil 281 return nil
279 } 282 }
LEFTRIGHT

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b