Skip to content

Commit dc1ecd0

Browse files
committed
use pbkdf2-hmac-sha256 for password hash
1 parent 3648e17 commit dc1ecd0

File tree

5 files changed

+66
-23
lines changed

5 files changed

+66
-23
lines changed

client/client.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bufio"
55
"crypto/sha256"
66
"crypto/tls"
7+
"encoding/hex"
78
"encoding/json"
89
"fmt"
910
"io"
@@ -16,6 +17,7 @@ import (
1617

1718
"github.com/contribsys/faktory/internal/pool"
1819
"github.com/contribsys/faktory/util"
20+
"golang.org/x/crypto/pbkdf2"
1921
)
2022

2123
const (
@@ -684,12 +686,11 @@ func readResponse(rdr *bufio.Reader) ([]byte, error) {
684686
}
685687

686688
func hash(pwd, salt string, iterations int) string {
687-
data := []byte(pwd + salt)
688-
hash := sha256.Sum256(data)
689-
if iterations > 1 {
690-
for i := 1; i < iterations; i++ {
691-
hash = sha256.Sum256(hash[:])
692-
}
693-
}
694-
return fmt.Sprintf("%x", hash)
689+
pwdBytes := []byte(pwd)
690+
saltBytes := []byte(salt)
691+
692+
// The '32' parameter specifies the key length in bytes (256 bits for SHA-256)
693+
hash := pbkdf2.Key(pwdBytes, saltBytes, iterations, 32, sha256.New)
694+
695+
return hex.EncodeToString(hash)
695696
}

docs/protocol-specification.md

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -307,17 +307,55 @@ commands.
307307

308308
When the server `HI` includes an iteration count `i` and a salt `s`,
309309
a client MUST include a `pwdhash` String-typed field in their `HELLO`.
310-
This field should be the hexadecimal representation of the `i`th SHA256
311-
hash of the client password concatenated with the value in `s`.
310+
This field should be the PBKDF2 digest using HMAC and SHA256 represented
311+
as a hex string.
312312

313-
```example
314-
hash = password + s
315-
for 0..i {
316-
hash = sha256(hash)
313+
Here's how to implement that in Go.
314+
315+
```go
316+
import (
317+
"crypto/sha256"
318+
"golang.org/x/crypto/pbkdf2"
319+
"encoding/hex"
320+
)
321+
322+
func hash(pwd, salt string, iterations int) string {
323+
pwdBytes := []byte(pwd)
324+
saltBytes := []byte(salt)
325+
326+
// Generate the hash using PBKDF2-HMAC-SHA256. The '32' parameter
327+
// specifies the key length in bytes (256 bits for SHA-256).
328+
hash := pbkdf2.Key(pwdBytes, saltBytes, iterations, 32, sha256.New)
329+
330+
return hex.EncodeToString(hash)
317331
}
318-
hex(hash)
319332
```
320333

334+
And in Python.
335+
336+
```python
337+
import hashlib
338+
339+
def hash(pwd: str, salt: str, iterations: int) -> str:
340+
pwd_bytes = pwd.encode('utf-8')
341+
salt_bytes = salt.encode('utf-8')
342+
343+
# Generate the hash using PBKDF2-HMAC-SHA256. The dklen parameter
344+
# specifies the key length in bytest (256 bits for SHA-256).
345+
hash = hashlib.pbkdf2_hmac(
346+
'sha256',
347+
pwd_bytes,
348+
salt_bytes,
349+
iterations,
350+
dklen=32,
351+
)
352+
353+
return hash.hex()
354+
```
355+
356+
You can gut-check your own implementation by checking that `hash("password", "salt", 50)`
357+
returns `926891811ee18e4539b150e9c3888a1afb0eb6fb827ad0c01ab6b4b918a513ac`.
358+
321359
#### Required Fields for Consumers
322360

323361
A client that wishes to act as a consumer MUST include the following

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ require github.com/redis/go-redis/v9 v9.6.1
1313
require (
1414
github.com/cespare/xxhash/v2 v2.3.0 // indirect
1515
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
16+
golang.org/x/crypto v0.33.0 // indirect
1617
gopkg.in/yaml.v3 v3.0.1 // indirect
1718
)
1819

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0
2020
github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA=
2121
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
2222
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
23+
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
24+
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
2325
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
2426
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
2527
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

server/server.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"crypto/sha256"
77
"crypto/subtle"
88
"crypto/tls"
9+
"encoding/hex"
910
"errors"
1011
"fmt"
1112
"io"
@@ -24,6 +25,7 @@ import (
2425
"github.com/contribsys/faktory/storage"
2526
"github.com/contribsys/faktory/util"
2627
"github.com/redis/go-redis/v9"
28+
"golang.org/x/crypto/pbkdf2"
2729
)
2830

2931
type RuntimeStats struct {
@@ -230,14 +232,13 @@ func cleanupConnection(s *Server, c *Connection) {
230232
}
231233

232234
func hash(pwd, salt string, iterations int) string {
233-
bytes := []byte(pwd + salt)
234-
hash := sha256.Sum256(bytes)
235-
if iterations > 1 {
236-
for i := 1; i < iterations; i++ {
237-
hash = sha256.Sum256(hash[:])
238-
}
239-
}
240-
return fmt.Sprintf("%x", hash)
235+
pwdBytes := []byte(pwd)
236+
saltBytes := []byte(salt)
237+
238+
// The '32' parameter specifies the key length in bytes (256 bits for SHA-256)
239+
hash := pbkdf2.Key(pwdBytes, saltBytes, iterations, 32, sha256.New)
240+
241+
return hex.EncodeToString(hash)
241242
}
242243

243244
func startConnection(conn net.Conn, s *Server) *Connection {

0 commit comments

Comments
 (0)