github.com/cosmos/cosmos-sdk@v0.50.1/crypto/keys/bcrypt/bcrypt.go (about) 1 // MODIFIED BY TENDERMINT TO EXPOSE `salt` in `GenerateFromPassword`. 2 // Copyright 2011 The Go Authors. All rights reserved. 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file. 5 6 // Package bcrypt implements Provos and Mazières's bcrypt adaptive hashing 7 // algorithm. See http://www.usenix.org/event/usenix99/provos/provos.pdf 8 package bcrypt 9 10 // The code is a port of Provos and Mazières's C implementation. 11 12 import ( 13 "crypto/subtle" 14 "errors" 15 "fmt" 16 "strconv" 17 18 "golang.org/x/crypto/blowfish" //nolint:staticcheck // used in original https://cs.opensource.google/go/x/crypto/+/master:bcrypt/bcrypt.go 19 ) 20 21 const ( 22 MinCost uint32 = 4 // the minimum allowable cost as passed in to GenerateFromPassword 23 MaxCost uint32 = 31 // the maximum allowable cost as passed in to GenerateFromPassword 24 DefaultCost uint32 = 10 // the cost that will actually be set if a cost below MinCost is passed into GenerateFromPassword 25 ) 26 27 // ErrMismatchedHashAndPassword is 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 // ErrHashTooShort is 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 uint32 // 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(salt, password []byte, cost uint32) ([]byte, error) { 90 if len(salt) != maxSaltSize { 91 return nil, fmt.Errorf("salt len must be %v", maxSaltSize) 92 } 93 p, err := newFromPassword(salt, password, cost) 94 if err != nil { 95 return nil, err 96 } 97 return p.Hash(), nil 98 } 99 100 // CompareHashAndPassword compares a bcrypt hashed password with its possible 101 // plaintext equivalent. Returns nil on success, or an error on failure. 102 func CompareHashAndPassword(hashedPassword, password []byte) error { 103 p, err := newFromHash(hashedPassword) 104 if err != nil { 105 return err 106 } 107 108 otherHash, err := bcrypt(password, p.cost, p.salt) 109 if err != nil { 110 return err 111 } 112 113 otherP := &hashed{otherHash, p.salt, p.cost, p.major, p.minor} 114 if subtle.ConstantTimeCompare(p.Hash(), otherP.Hash()) == 1 { 115 return nil 116 } 117 118 return ErrMismatchedHashAndPassword 119 } 120 121 // Cost returns the hashing cost used to create the given hashed 122 // password. When, in the future, the hashing cost of a password system needs 123 // to be increased in order to adjust for greater computational power, this 124 // function allows one to establish which passwords need to be updated. 125 func Cost(hashedPassword []byte) (uint32, error) { 126 p, err := newFromHash(hashedPassword) 127 if err != nil { 128 return 0, err 129 } 130 return p.cost, nil 131 } 132 133 func newFromPassword(salt, password []byte, cost uint32) (*hashed, error) { 134 if cost < MinCost { 135 cost = DefaultCost 136 } 137 p := new(hashed) 138 p.major = majorVersion 139 p.minor = minorVersion 140 141 err := checkCost(cost) 142 if err != nil { 143 return nil, err 144 } 145 p.cost = cost 146 147 p.salt = base64Encode(salt) 148 hash, err := bcrypt(password, p.cost, p.salt) 149 if err != nil { 150 return nil, err 151 } 152 p.hash = hash 153 return p, err 154 } 155 156 func newFromHash(hashedSecret []byte) (*hashed, error) { 157 if len(hashedSecret) < minHashSize { 158 return nil, ErrHashTooShort 159 } 160 p := new(hashed) 161 n, err := p.decodeVersion(hashedSecret) 162 if err != nil { 163 return nil, err 164 } 165 hashedSecret = hashedSecret[n:] 166 n, err = p.decodeCost(hashedSecret) 167 if err != nil { 168 return nil, err 169 } 170 hashedSecret = hashedSecret[n:] 171 172 // The "+2" is here because we'll have to append at most 2 '=' to the salt 173 // when base64 decoding it in expensiveBlowfishSetup(). 174 p.salt = make([]byte, encodedSaltSize, encodedSaltSize+2) 175 copy(p.salt, hashedSecret[:encodedSaltSize]) 176 177 hashedSecret = hashedSecret[encodedSaltSize:] 178 p.hash = make([]byte, len(hashedSecret)) 179 copy(p.hash, hashedSecret) 180 181 return p, nil 182 } 183 184 func bcrypt(password []byte, cost uint32, salt []byte) ([]byte, error) { 185 cipherData := make([]byte, len(magicCipherData)) 186 copy(cipherData, magicCipherData) 187 188 c, err := expensiveBlowfishSetup(password, cost, salt) 189 if err != nil { 190 return nil, err 191 } 192 193 for i := 0; i < 24; i += 8 { 194 for j := 0; j < 64; j++ { 195 c.Encrypt(cipherData[i:i+8], cipherData[i:i+8]) 196 } 197 } 198 199 // Bug compatibility with C bcrypt implementations. We only encode 23 of 200 // the 24 bytes encrypted. 201 hsh := base64Encode(cipherData[:maxCryptedHashSize]) 202 return hsh, nil 203 } 204 205 func expensiveBlowfishSetup(key []byte, cost uint32, salt []byte) (*blowfish.Cipher, error) { 206 csalt, err := base64Decode(salt) 207 if err != nil { 208 return nil, err 209 } 210 211 // Bug compatibility with C bcrypt implementations. They use the trailing 212 // NULL in the key string during expansion. 213 // We copy the key to prevent changing the underlying array. 214 ckey := append(key[:len(key):len(key)], 0) 215 216 c, err := blowfish.NewSaltedCipher(ckey, csalt) 217 if err != nil { 218 return nil, err 219 } 220 221 var i, rounds uint64 222 rounds = 1 << cost 223 for i = 0; i < rounds; i++ { 224 blowfish.ExpandKey(ckey, c) 225 blowfish.ExpandKey(csalt, c) 226 } 227 228 return c, nil 229 } 230 231 func (p *hashed) Hash() []byte { 232 arr := make([]byte, 60) 233 arr[0] = '$' 234 arr[1] = p.major 235 n := 2 236 if p.minor != 0 { 237 arr[2] = p.minor 238 n = 3 239 } 240 arr[n] = '$' 241 n++ 242 copy(arr[n:], []byte(fmt.Sprintf("%02d", p.cost))) 243 n += 2 244 arr[n] = '$' 245 n++ 246 copy(arr[n:], p.salt) 247 n += encodedSaltSize 248 copy(arr[n:], p.hash) 249 n += encodedHashSize 250 return arr[:n] 251 } 252 253 func (p *hashed) decodeVersion(sbytes []byte) (int, error) { 254 if sbytes[0] != '$' { 255 return -1, InvalidHashPrefixError(sbytes[0]) 256 } 257 if sbytes[1] > majorVersion { 258 return -1, HashVersionTooNewError(sbytes[1]) 259 } 260 p.major = sbytes[1] 261 n := 3 262 if sbytes[2] != '$' { 263 p.minor = sbytes[2] 264 n++ 265 } 266 return n, nil 267 } 268 269 // sbytes should begin where decodeVersion left off. 270 func (p *hashed) decodeCost(sbytes []byte) (int, error) { 271 cost, err := strconv.Atoi(string(sbytes[0:2])) 272 if err != nil { 273 return -1, err 274 } 275 err = checkCost(uint32(cost)) 276 if err != nil { 277 return -1, err 278 } 279 p.cost = uint32(cost) 280 return 3, nil 281 } 282 283 func (p *hashed) String() string { 284 return fmt.Sprintf("&{hash: %#v, salt: %#v, cost: %d, major: %c, minor: %c}", string(p.hash), p.salt, p.cost, p.major, p.minor) 285 } 286 287 func checkCost(cost uint32) error { 288 if cost < MinCost || cost > MaxCost { 289 return InvalidCostError(cost) 290 } 291 return nil 292 }