Skip to content

Commit cb2380f

Browse files
committed
bcrypt: MagicCypherData is now declared direcly within bcrypt(). Avoids global allocation and unecessary copying. Removed unecessary encoding/decoding of salt between newFromPassword() and expensiveBlowfishSetup(). Decode functions called within newFromHash() take direct reference to hash byteslice rather than copies and mutate them within the function, although they can be mutated after also.
1 parent 3f0842a commit cb2380f

File tree

2 files changed

+54
-50
lines changed

2 files changed

+54
-50
lines changed

bcrypt/bcrypt.go

+51-47
Original file line numberDiff line numberDiff line change
@@ -63,17 +63,6 @@ const (
6363
minHashSize = 59
6464
)
6565

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-
7766
type hashed struct {
7867
hash []byte
7968
salt []byte
@@ -111,6 +100,13 @@ func CompareHashAndPassword(hashedPassword, password []byte) error {
111100
return err
112101
}
113102

103+
// This is simply put here instead of in newfromhash only to avoid failed test
104+
// Altough failed test can be easily altered
105+
p.salt, err = base64Decode(p.salt)
106+
if err != nil {
107+
return err
108+
}
109+
114110
otherHash, err := bcrypt(password, p.cost, p.salt)
115111
if err != nil {
116112
return err
@@ -156,12 +152,14 @@ func newFromPassword(password []byte, cost int) (*hashed, error) {
156152
return nil, err
157153
}
158154

159-
p.salt = base64Encode(unencodedSalt)
160-
hash, err := bcrypt(password, p.cost, p.salt)
155+
hash, err := bcrypt(password, p.cost, unencodedSalt)
161156
if err != nil {
162157
return nil, err
163158
}
159+
160+
p.salt = base64Encode(unencodedSalt)
164161
p.hash = hash
162+
165163
return p, err
166164
}
167165

@@ -170,32 +168,38 @@ func newFromHash(hashedSecret []byte) (*hashed, error) {
170168
return nil, ErrHashTooShort
171169
}
172170
p := new(hashed)
173-
n, err := p.decodeVersion(hashedSecret)
171+
err := p.decodeVersion(&hashedSecret)
174172
if err != nil {
175173
return nil, err
176174
}
177-
hashedSecret = hashedSecret[n:]
178-
n, err = p.decodeCost(hashedSecret)
175+
176+
err = p.decodeCost(&hashedSecret)
179177
if err != nil {
180178
return nil, err
181179
}
182-
hashedSecret = hashedSecret[n:]
183180

184181
// The "+2" is here because we'll have to append at most 2 '=' to the salt
185182
// when base64 decoding it in expensiveBlowfishSetup().
186183
p.salt = make([]byte, encodedSaltSize, encodedSaltSize+2)
187184
copy(p.salt, hashedSecret[:encodedSaltSize])
188185

189-
hashedSecret = hashedSecret[encodedSaltSize:]
190-
p.hash = make([]byte, len(hashedSecret))
191-
copy(p.hash, hashedSecret)
186+
p.hash = make([]byte, encodedHashSize)
187+
copy(p.hash, hashedSecret[encodedSaltSize:])
192188

193189
return p, nil
194190
}
195191

196192
func bcrypt(password []byte, cost int, salt []byte) ([]byte, error) {
197-
cipherData := make([]byte, len(magicCipherData))
198-
copy(cipherData, magicCipherData)
193+
// magicCipherData is an IV for the 64 Blowfish encryption calls in
194+
// bcrypt(). It's the string "OrpheanBeholderScryDoubt" in big-endian bytes.
195+
var magicCipherData = []byte{
196+
0x4f, 0x72, 0x70, 0x68,
197+
0x65, 0x61, 0x6e, 0x42,
198+
0x65, 0x68, 0x6f, 0x6c,
199+
0x64, 0x65, 0x72, 0x53,
200+
0x63, 0x72, 0x79, 0x44,
201+
0x6f, 0x75, 0x62, 0x74,
202+
}
199203

200204
c, err := expensiveBlowfishSetup(password, uint32(cost), salt)
201205
if err != nil {
@@ -204,28 +208,23 @@ func bcrypt(password []byte, cost int, salt []byte) ([]byte, error) {
204208

205209
for i := 0; i < 24; i += 8 {
206210
for j := 0; j < 64; j++ {
207-
c.Encrypt(cipherData[i:i+8], cipherData[i:i+8])
211+
c.Encrypt(magicCipherData[i:i+8], magicCipherData[i:i+8])
208212
}
209213
}
210214

211215
// Bug compatibility with C bcrypt implementations. We only encode 23 of
212216
// the 24 bytes encrypted.
213-
hsh := base64Encode(cipherData[:maxCryptedHashSize])
217+
hsh := base64Encode(magicCipherData[:maxCryptedHashSize])
214218
return hsh, nil
215219
}
216220

217221
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-
223222
// Bug compatibility with C bcrypt implementations. They use the trailing
224223
// NULL in the key string during expansion.
225224
// We copy the key to prevent changing the underlying array.
226225
ckey := append(key[:len(key):len(key)], 0)
227226

228-
c, err := blowfish.NewSaltedCipher(ckey, csalt)
227+
c, err := blowfish.NewSaltedCipher(ckey, salt)
229228
if err != nil {
230229
return nil, err
231230
}
@@ -234,7 +233,7 @@ func expensiveBlowfishSetup(key []byte, cost uint32, salt []byte) (*blowfish.Cip
234233
rounds = 1 << cost
235234
for i = 0; i < rounds; i++ {
236235
blowfish.ExpandKey(ckey, c)
237-
blowfish.ExpandKey(csalt, c)
236+
blowfish.ExpandKey(salt, c)
238237
}
239238

240239
return c, nil
@@ -262,34 +261,39 @@ func (p *hashed) Hash() []byte {
262261
return arr[:n]
263262
}
264263

265-
func (p *hashed) decodeVersion(sbytes []byte) (int, error) {
266-
if sbytes[0] != '$' {
267-
return -1, InvalidHashPrefixError(sbytes[0])
264+
func (p *hashed) decodeVersion(sbytes *[]byte) error {
265+
if (*sbytes)[0] != '$' {
266+
return InvalidHashPrefixError((*sbytes)[0])
268267
}
269-
if sbytes[1] > majorVersion {
270-
return -1, HashVersionTooNewError(sbytes[1])
268+
if (*sbytes)[1] > majorVersion {
269+
return HashVersionTooNewError((*sbytes)[1])
271270
}
272-
p.major = sbytes[1]
271+
p.major = (*sbytes)[1]
273272
n := 3
274-
if sbytes[2] != '$' {
275-
p.minor = sbytes[2]
273+
if (*sbytes)[2] != '$' {
274+
p.minor = (*sbytes)[2]
276275
n++
277276
}
278-
return n, nil
277+
278+
(*sbytes) = (*sbytes)[n:]
279+
280+
return nil
279281
}
280282

281283
// 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+
func (p *hashed) decodeCost(sbytes *[]byte) (err error) {
285+
p.cost, err = strconv.Atoi(string((*sbytes)[0:2]))
284286
if err != nil {
285-
return -1, err
287+
return
286288
}
287-
err = checkCost(cost)
289+
290+
err = checkCost(p.cost)
288291
if err != nil {
289-
return -1, err
292+
return
290293
}
291-
p.cost = cost
292-
return 3, nil
294+
295+
(*sbytes) = (*sbytes)[3:]
296+
return
293297
}
294298

295299
func (p *hashed) String() string {

bcrypt/bcrypt_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func TestBcryptingIsEasy(t *testing.T) {
3030

3131
func TestBcryptingIsCorrect(t *testing.T) {
3232
pass := []byte("allmine")
33-
salt := []byte("XajjQvNhvvRt5GSeFk1xFe")
33+
salt := []byte{101, 201, 101, 75, 19, 227, 199, 20, 239, 236, 133, 32, 30, 109, 243, 30}
3434
expectedHash := []byte("$2a$10$XajjQvNhvvRt5GSeFk1xFeyqRrsxkhBkUiQeg0dt.wU1qD4aFDcga")
3535

3636
hash, err := bcrypt(pass, 10, salt)
@@ -55,15 +55,15 @@ func TestBcryptingIsCorrect(t *testing.T) {
5555

5656
func TestVeryShortPasswords(t *testing.T) {
5757
key := []byte("k")
58-
salt := []byte("XajjQvNhvvRt5GSeFk1xFe")
58+
salt := []byte{101, 201, 101, 75, 19, 227, 199, 20, 239, 236, 133, 32, 30, 109, 243, 30}
5959
_, err := bcrypt(key, 10, salt)
6060
if err != nil {
6161
t.Errorf("One byte key resulted in error: %s", err)
6262
}
6363
}
6464

6565
func TestTooLongPasswordsWork(t *testing.T) {
66-
salt := []byte("XajjQvNhvvRt5GSeFk1xFe")
66+
salt := []byte{101, 201, 101, 75, 19, 227, 199, 20, 239, 236, 133, 32, 30, 109, 243, 30}
6767
// One byte over the usual 56 byte limit that blowfish has
6868
tooLongPass := []byte("012345678901234567890123456789012345678901234567890123456")
6969
tooLongExpected := []byte("$2a$10$XajjQvNhvvRt5GSeFk1xFe5l47dONXg781AmZtd869sO8zfsHuw7C")

0 commit comments

Comments
 (0)