Skip to content

Commit df6e25a

Browse files
committed
slight optimizations in bcrypt folder as well as new biicrypt version of bcrypt meant to allow secure storage of password hash into database, and only use salt to generate subsequent password hashes.
1 parent 3f0842a commit df6e25a

File tree

5 files changed

+491
-50
lines changed

5 files changed

+491
-50
lines changed

bcrypt/bcrypt.go

Lines changed: 51 additions & 47 deletions
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

Lines changed: 3 additions & 3 deletions
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")

biicrypt/base64.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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 biicrypt
6+
7+
import (
8+
"encoding/base64"
9+
)
10+
11+
func base64Encode(src []byte) []byte {
12+
n := base64.StdEncoding.EncodedLen(len(src))
13+
dst := make([]byte, n)
14+
base64.StdEncoding.Encode(dst, src)
15+
for dst[n-1] == '=' {
16+
n--
17+
}
18+
return dst[:n]
19+
}
20+
21+
func base64Decode(src []byte) ([]byte, error) {
22+
numOfEquals := 4 - (len(src) % 4)
23+
for i := 0; i < numOfEquals; i++ {
24+
src = append(src, '=')
25+
}
26+
27+
dst := make([]byte, base64.StdEncoding.DecodedLen(len(src)))
28+
n, err := base64.StdEncoding.Decode(dst, src)
29+
if err != nil {
30+
return nil, err
31+
}
32+
return dst[:n], nil
33+
}
34+
35+
// This function is used to decode hashes previously encoded with the
36+
// legacy encoding. It should not serve any other purpose.
37+
func reEncodeFromLegacy(src []byte) ([]byte, error) {
38+
39+
unencoded, err := legacyDecoder(src)
40+
if err != nil {
41+
return nil, err
42+
}
43+
44+
encodedValue := base64Encode(unencoded)
45+
return encodedValue, nil
46+
}
47+
48+
// Decoder taken from crypto/bcrypt.
49+
func legacyDecoder(src []byte) ([]byte, error) {
50+
51+
const alphabet = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
52+
53+
var bcEncoding = base64.NewEncoding(alphabet)
54+
55+
numOfEquals := 4 - (len(src) % 4)
56+
for i := 0; i < numOfEquals; i++ {
57+
src = append(src, '=')
58+
}
59+
60+
dst := make([]byte, bcEncoding.DecodedLen(len(src)))
61+
n, err := bcEncoding.Decode(dst, src)
62+
if err != nil {
63+
return nil, err
64+
}
65+
return dst[:n], nil
66+
}

0 commit comments

Comments
 (0)