Skip to content

Commit

Permalink
add scram hash method
Browse files Browse the repository at this point in the history
  • Loading branch information
Tommi2Day committed Mar 1, 2024
1 parent f09cb2e commit c807175
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 0 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Go Library

## [v1.11.5 - 2024-03-01]
### New
- add scram hash method
### Changed
- update dependencies

## [v1.11.4 - 2024-02-18]
### Changed
- hmlib: use plain url insead of httpclient query params encoded strings
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ require (
github.com/spf13/viper v1.18.2
github.com/stretchr/testify v1.8.4
github.com/wneessen/go-mail v0.4.1
github.com/xdg-go/scram v1.1.2
github.com/xlzd/gotp v0.1.0
golang.org/x/net v0.21.0
gopkg.in/ini.v1 v1.67.0
Expand Down Expand Up @@ -92,6 +93,8 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
Expand Down
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,12 @@ github.com/wneessen/go-mail v0.4.1 h1:m2rSg/sc8FZQCdtrV5M8ymHYOFrC6KJAQAIcgrXvqo
github.com/wneessen/go-mail v0.4.1/go.mod h1:zxOlafWCP/r6FEhAaRgH4IC1vg2YXxO0Nar9u0IScZ8=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
Expand Down Expand Up @@ -329,6 +335,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
Expand Down
62 changes: 62 additions & 0 deletions pwlib/scram.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package pwlib

// Origin: https://github.com/tv42/scram-password/tree/main/internal/scramble
// License: https://github.com/tv42/scram-password/blob/main/LICENSE

import (
"crypto/rand"
"encoding/base64"
"fmt"

"github.com/xdg-go/scram"
)

func makeSalt(size int) ([]byte, error) {
salt := make([]byte, size)
if _, err := rand.Read(salt); err != nil {
return nil, err
}
return salt, nil
}

func hashWithKF(username string, password string, kf scram.KeyFactors) (string, error) {
// We could expose this as a command-line flag, but first need a use case we can test against.
const authID = ""
// We could make the algorithm a command-line flag.
client, err := scram.SHA256.NewClient(username, password, authID)
if err != nil {
return "", err
}

credentials := client.GetStoredCredentials(kf)

// SCRAM-SHA-256$<iter>:<salt>$<StoredKey>:<ServerKey>
hashed := fmt.Sprintf("SCRAM-SHA-256$%d:%s$%s:%s",
credentials.Iters,
base64.StdEncoding.EncodeToString([]byte(credentials.Salt)),
base64.StdEncoding.EncodeToString(credentials.StoredKey),
base64.StdEncoding.EncodeToString(credentials.ServerKey),
)
return hashed, nil
}

// ScramPassword returns a SCRAM-SHA-256 password hash for the given username and password as used by postgresql11+
func ScramPassword(username string, password string) (string, error) {
// We could take a known salt (as base64) as a command-line flag.

// We could take salt size as a command-line flag.
//
// Postgres 14 uses salt size 16.
// We'd rather be ahead of the curve than behind.
const saltSize = 24
salt, err := makeSalt(saltSize)
if err != nil {
return "", err
}
kf := scram.KeyFactors{
Salt: string(salt),
// We could take iterations as a command-line flag.
Iters: 4096,
}
return hashWithKF(username, password, kf)
}
42 changes: 42 additions & 0 deletions pwlib/scram_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package pwlib

// Origin: https://github.com/tv42/scram-password/tree/main/internal/scramble
// License: https://github.com/tv42/scram-password/blob/main/LICENSE

import (
"testing"

"github.com/stretchr/testify/assert"

"github.com/xdg-go/scram"
)

func TestScram(t *testing.T) {
salt := [32]byte{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
}
hash, err := hashWithKF("jdoe", "s3kr1t", scram.KeyFactors{
Iters: 4096,
Salt: string(salt[:]),
})
if err != nil {
t.Fatal(err)
}
// nolint: gosec
const want = `SCRAM-SHA-256$4096:AAECAwQFBgcAAQIDBAUGBwABAgMEBQYHAAECAwQFBgc=$3OKulhqxk9w6FbPtpUHCuIkEsW+2F2cjX0/ABNgYsbI=:BZ55glbzmkm4V5VjvpHHENWSEZE/IVxZWuAqeLUsikQ=`
if g, e := hash, want; g != e {
t.Errorf("wrong hash:\n\tgot\t%s\n\twant\t%s\n", g, e)
}
}

func TestScramPassword(t *testing.T) {
username := "testuser"
password := "verySecret"
actual, err := ScramPassword(username, password)
assert.NoError(t, err, "should not return error:%s", err)
assert.NotEmpty(t, actual, "Value should not be empty")
t.Log(actual)
}

0 comments on commit c807175

Please sign in to comment.