github.com/mdaxf/iac@v0.0.0-20240519030858-58a061660378/vendor_skip/golang.org/x/crypto/bcrypt/bcrypt.go (about) 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 "io" 16 "strconv" 17 18 "golang.org/x/crypto/blowfish" 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), MinCost, 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 // ErrPasswordTooLong is returned when the password passed to 86 // GenerateFromPassword is too long (i.e. > 72 bytes). 87 var ErrPasswordTooLong = errors.New("bcrypt: password length exceeds 72 bytes") 88 89 // GenerateFromPassword returns the bcrypt hash of the password at the given 90 // cost. If the cost given is less than MinCost, the cost will be set to 91 // DefaultCost, instead. Use CompareHashAndPassword, as defined in this package, 92 // to compare the returned hashed password with its cleartext version. 93 // GenerateFromPassword does not accept passwords longer than 72 bytes, which 94 // is the longest password bcrypt will operate on. 95 func GenerateFromPassword(password []byte, cost int) ([]byte, error) { 96 if len(password) > 72 { 97 return nil, ErrPasswordTooLong 98 } 99 p, err := newFromPassword(password, cost) 100 if err != nil { 101 return nil, err 102 } 103 return p.Hash(), nil 104 } 105 106 // CompareHashAndPassword compares a bcrypt hashed password with its possible 107 // plaintext equivalent. Returns nil on success, or an error on failure. 108 func CompareHashAndPassword(hashedPassword, password []byte) error { 109 p, err := newFromHash(hashedPassword) 110 if err != nil { 111 return err 112 } 113 114 otherHash, err := bcrypt(password, p.cost, p.salt) 115 if err != nil { 116 return err 117 } 118 119 otherP := &hashed{otherHash, p.salt, p.cost, p.major, p.minor} 120 if subtle.ConstantTimeCompare(p.Hash(), otherP.Hash()) == 1 { 121 return nil 122 } 123 124 return ErrMismatchedHashAndPassword 125 } 126 127 // Cost returns the hashing cost used to create the given hashed 128 // password. When, in the future, the hashing cost of a password system needs 129 // to be increased in order to adjust for greater computational power, this 130 // function allows one to establish which passwords need to be updated. 131 func Cost(hashedPassword []byte) (int, error) { 132 p, err := newFromHash(hashedPassword) 133 if err != nil { 134 return 0, err 135 } 136 return p.cost, nil 137 } 138 139 func newFromPassword(password []byte, cost int) (*hashed, error) { 140 if cost < MinCost { 141 cost = DefaultCost 142 } 143 p := new(hashed) 144 p.major = majorVersion 145 p.minor = minorVersion 146 147 err := checkCost(cost) 148 if err != nil { 149 return nil, err 150 } 151 p.cost = cost 152 153 unencodedSalt := make([]byte, maxSaltSize) 154 _, err = io.ReadFull(rand.Reader, unencodedSalt) 155 if err != nil { 156 return nil, err 157 } 158 159 p.salt = base64Encode(unencodedSalt) 160 hash, err := bcrypt(password, p.cost, p.salt) 161 if err != nil { 162 return nil, err 163 } 164 p.hash = hash 165 return p, err 166 } 167 168 func newFromHash(hashedSecret []byte) (*hashed, error) { 169 if len(hashedSecret) < minHashSize { 170 return nil, ErrHashTooShort 171 } 172 p := new(hashed) 173 n, err := p.decodeVersion(hashedSecret) 174 if err != nil { 175 return nil, err 176 } 177 hashedSecret = hashedSecret[n:] 178 n, err = p.decodeCost(hashedSecret) 179 if err != nil { 180 return nil, err 181 } 182 hashedSecret = hashedSecret[n:] 183 184 // The "+2" is here because we'll have to append at most 2 '=' to the salt 185 // when base64 decoding it in expensiveBlowfishSetup(). 186 p.salt = make([]byte, encodedSaltSize, encodedSaltSize+2) 187 copy(p.salt, hashedSecret[:encodedSaltSize]) 188 189 hashedSecret = hashedSecret[encodedSaltSize:] 190 p.hash = make([]byte, len(hashedSecret)) 191 copy(p.hash, hashedSecret) 192 193 return p, nil 194 } 195 196 func bcrypt(password []byte, cost int, salt []byte) ([]byte, error) { 197 cipherData := make([]byte, len(magicCipherData)) 198 copy(cipherData, magicCipherData) 199 200 c, err := expensiveBlowfishSetup(password, uint32(cost), salt) 201 if err != nil { 202 return nil, err 203 } 204 205 for i := 0; i < 24; i += 8 { 206 for j := 0; j < 64; j++ { 207 c.Encrypt(cipherData[i:i+8], cipherData[i:i+8]) 208 } 209 } 210 211 // Bug compatibility with C bcrypt implementations. We only encode 23 of 212 // the 24 bytes encrypted. 213 hsh := base64Encode(cipherData[:maxCryptedHashSize]) 214 return hsh, nil 215 } 216 217 func expensiveBlowfishSetup(key []byte, cost uint32, salt []byte) (*blowfish.Cipher, error) { 218 csalt, err := base64Decode(salt) 219 if err != nil { 220 return nil, err 221 } 222 223 // Bug compatibility with C bcrypt implementations. They use the trailing 224 // NULL in the key string during expansion. 225 // We copy the key to prevent changing the underlying array. 226 ckey := append(key[:len(key):len(key)], 0) 227 228 c, err := blowfish.NewSaltedCipher(ckey, csalt) 229 if err != nil { 230 return nil, err 231 } 232 233 var i, rounds uint64 234 rounds = 1 << cost 235 for i = 0; i < rounds; i++ { 236 blowfish.ExpandKey(ckey, c) 237 blowfish.ExpandKey(csalt, c) 238 } 239 240 return c, nil 241 } 242 243 func (p *hashed) Hash() []byte { 244 arr := make([]byte, 60) 245 arr[0] = '$' 246 arr[1] = p.major 247 n := 2 248 if p.minor != 0 { 249 arr[2] = p.minor 250 n = 3 251 } 252 arr[n] = '$' 253 n++ 254 copy(arr[n:], []byte(fmt.Sprintf("%02d", p.cost))) 255 n += 2 256 arr[n] = '$' 257 n++ 258 copy(arr[n:], p.salt) 259 n += encodedSaltSize 260 copy(arr[n:], p.hash) 261 n += encodedHashSize 262 return arr[:n] 263 } 264 265 func (p *hashed) decodeVersion(sbytes []byte) (int, error) { 266 if sbytes[0] != '$' { 267 return -1, InvalidHashPrefixError(sbytes[0]) 268 } 269 if sbytes[1] > majorVersion { 270 return -1, HashVersionTooNewError(sbytes[1]) 271 } 272 p.major = sbytes[1] 273 n := 3 274 if sbytes[2] != '$' { 275 p.minor = sbytes[2] 276 n++ 277 } 278 return n, nil 279 } 280 281 // sbytes should begin where decodeVersion left off. 282 func (p *hashed) decodeCost(sbytes []byte) (int, error) { 283 cost, err := strconv.Atoi(string(sbytes[0:2])) 284 if err != nil { 285 return -1, err 286 } 287 err = checkCost(cost) 288 if err != nil { 289 return -1, err 290 } 291 p.cost = cost 292 return 3, nil 293 } 294 295 func (p *hashed) String() string { 296 return fmt.Sprintf("&{hash: %#v, salt: %#v, cost: %d, major: %c, minor: %c}", string(p.hash), p.salt, p.cost, p.major, p.minor) 297 } 298 299 func checkCost(cost int) error { 300 if cost < MinCost || cost > MaxCost { 301 return InvalidCostError(cost) 302 } 303 return nil 304 }