Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bufio"
"crypto/sha256"
"crypto/tls"
"encoding/hex"
"encoding/json"
"fmt"
"io"
Expand All @@ -16,6 +17,7 @@ import (

"github.com/contribsys/faktory/internal/pool"
"github.com/contribsys/faktory/util"
"golang.org/x/crypto/pbkdf2"
)

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

func hash(pwd, salt string, iterations int) string {
data := []byte(pwd + salt)
hash := sha256.Sum256(data)
if iterations > 1 {
for i := 1; i < iterations; i++ {
hash = sha256.Sum256(hash[:])
}
}
return fmt.Sprintf("%x", hash)
pwdBytes := []byte(pwd)
saltBytes := []byte(salt)

// The '32' parameter specifies the key length in bytes (256 bits for SHA-256)
hash := pbkdf2.Key(pwdBytes, saltBytes, iterations, 32, sha256.New)

return fmt.Sprintf("sha256:%s", hex.EncodeToString(hash))
}
53 changes: 46 additions & 7 deletions docs/protocol-specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,17 +307,56 @@ commands.

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

```example
hash = password + s
for 0..i {
hash = sha256(hash)
Here's how to implement that in Go.

```go
import (
"crypto/sha256"
"fmt"
"golang.org/x/crypto/pbkdf2"
"encoding/hex"
)

func hash(pwd, salt string, iterations int) string {
pwdBytes := []byte(pwd)
saltBytes := []byte(salt)

// Generate the hash using PBKDF2-HMAC-SHA256. The '32' parameter
// specifies the key length in bytes (256 bits for SHA-256).
hash := pbkdf2.Key(pwdBytes, saltBytes, iterations, 32, sha256.New)

return fmt.Sprintf("sha256:%s", hex.EncodeToString(hash))
}
hex(hash)
```

And in Python.

```python
import hashlib

def hash(pwd: str, salt: str, iterations: int) -> str:
pwd_bytes = pwd.encode('utf-8')
salt_bytes = salt.encode('utf-8')

# Generate the hash using PBKDF2-HMAC-SHA256. The dklen parameter
# specifies the key length in bytest (256 bits for SHA-256).
hash = hashlib.pbkdf2_hmac(
'sha256',
pwd_bytes,
salt_bytes,
iterations,
dklen=32,
)

return f"sha256:{hash.hex()}"
```

You can gut-check your own implementation by checking that `hash("password", "salt", 50)`
returns `926891811ee18e4539b150e9c3888a1afb0eb6fb827ad0c01ab6b4b918a513ac`.

#### Required Fields for Consumers

A client that wishes to act as a consumer MUST include the following
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require github.com/redis/go-redis/v9 v9.6.1
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
golang.org/x/crypto v0.33.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0
github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
27 changes: 21 additions & 6 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"crypto/sha256"
"crypto/subtle"
"crypto/tls"
"encoding/hex"
"errors"
"fmt"
"io"
Expand All @@ -24,6 +25,7 @@ import (
"github.com/contribsys/faktory/storage"
"github.com/contribsys/faktory/util"
"github.com/redis/go-redis/v9"
"golang.org/x/crypto/pbkdf2"
)

type RuntimeStats struct {
Expand Down Expand Up @@ -230,14 +232,27 @@ func cleanupConnection(s *Server, c *Connection) {
}

func hash(pwd, salt string, iterations int) string {
bytes := []byte(pwd + salt)
hash := sha256.Sum256(bytes)
if iterations > 1 {
for i := 1; i < iterations; i++ {
hash = sha256.Sum256(hash[:])
if strings.HasPrefix(pwd, "sha256:") {
pwd := strings.TrimPrefix(pwd, "sha256:")
pwdBytes := []byte(pwd)
saltBytes := []byte(salt)

// The '32' parameter specifies the key length in bytes (256 bits for SHA-256)
hash := pbkdf2.Key(pwdBytes, saltBytes, iterations, 32, sha256.New)

return hex.EncodeToString(hash)
} else {
bytes := []byte(pwd + salt)
hash := sha256.Sum256(bytes)

if iterations > 1 {
for i := 1; i < iterations; i++ {
hash = sha256.Sum256(hash[:])
}
}

return fmt.Sprintf("%x", hash)
}
return fmt.Sprintf("%x", hash)
}

func startConnection(conn net.Conn, s *Server) *Connection {
Expand Down
Loading