Skip to content

Commit

Permalink
Add a custom hasher for old users migration
Browse files Browse the repository at this point in the history
  • Loading branch information
shuofan committed Sep 28, 2021
1 parent 4fe76af commit 13c9b93
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .docker/Dockerfile-build
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM golang:1.16-alpine AS builder
FROM golang:1.17-alpine AS builder

RUN apk -U --no-cache add build-base git gcc bash

Expand Down
2 changes: 1 addition & 1 deletion driver/config/.schema/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1570,7 +1570,7 @@
"description": "One of the values: argon2, bcrypt",
"type": "string",
"default": "bcrypt",
"enum": ["argon2", "bcrypt"]
"enum": ["argon2", "bcrypt", "custom"]
},
"argon2": {
"title": "Configuration for the Argon2id hasher.",
Expand Down
5 changes: 4 additions & 1 deletion driver/registry_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,8 +361,11 @@ func (m *RegistryDefault) SessionHandler() *session.Handler {

func (m *RegistryDefault) Hasher() hash.Hasher {
if m.passwordHasher == nil {
if m.c.HasherPasswordHashingAlgorithm() == "bcrypt" {
hashingAlgorithm := m.c.HasherPasswordHashingAlgorithm()
if hashingAlgorithm == "bcrypt" {
m.passwordHasher = hash.NewHasherBcrypt(m)
} else if hashingAlgorithm == "custom" {
m.passwordHasher = hash.NewHasherCustom(m)
} else {
m.passwordHasher = hash.NewHasherArgon2(m)
}
Expand Down
24 changes: 24 additions & 0 deletions hash/hash_comparator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package hash

import (
"context"
"crypto/sha512"
"crypto/subtle"
"encoding/base64"
"encoding/hex"
"fmt"
"regexp"
"strings"
Expand All @@ -22,6 +24,8 @@ func Compare(ctx context.Context, password []byte, hash []byte) error {
return CompareBcrypt(ctx, password, hash)
} else if IsArgon2idHash(hash) {
return CompareArgon2id(ctx, password, hash)
} else if IsCustomHash(hash) {
return CompareCustom(ctx, password, hash)
} else {
return ErrUnknownHashAlgorithm
}
Expand Down Expand Up @@ -60,6 +64,21 @@ func CompareArgon2id(_ context.Context, password []byte, hash []byte) error {
return ErrMismatchedHashAndPassword
}

func CompareCustom(_ context.Context, password []byte, hash []byte) error {
parts := strings.Split(string(hash), "@")
if len(parts) < 2 {
return ErrInvalidHash
}
salt, realPass := parts[0], parts[1]
sha := sha512.New()
sha.Write([]byte(string(password) + salt))
encoded := hex.EncodeToString(sha.Sum(nil))[:20]
if subtle.ConstantTimeCompare([]byte(encoded), []byte(realPass)) == 1 {
return nil
}
return ErrMismatchedHashAndPassword
}

func IsBcryptHash(hash []byte) bool {
res, _ := regexp.Match("^\\$2[abzy]?\\$", hash)
return res
Expand All @@ -70,6 +89,11 @@ func IsArgon2idHash(hash []byte) bool {
return res
}

func IsCustomHash(hash []byte) bool {
res, _ := regexp.Match("^.{4}@.{20}$", hash)
return res
}

func decodeArgon2idHash(encodedHash string) (p *config.Argon2, salt, hash []byte, err error) {
parts := strings.Split(encodedHash, "$")
if len(parts) != 6 {
Expand Down
38 changes: 38 additions & 0 deletions hash/hasher_custom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package hash

import (
"context"
"crypto/md5"
"crypto/sha512"
"encoding/hex"
"io"
"strconv"
"strings"
"time"

"github.com/ory/kratos/driver/config"
"github.com/ory/kratos/x"
)

type Custom struct {
c CustomConfiguration
}

type CustomConfiguration interface {
config.Provider
}

func NewHasherCustom(c CustomConfiguration) *Custom {
return &Custom{c: c}
}

func (h *Custom) Generate(ctx context.Context, password []byte) ([]byte, error) {
hash := md5.New()
io.WriteString(hash, x.NewUUID().String()+strconv.FormatInt(time.Now().UnixMilli(), 10))
salt := hex.EncodeToString(hash.Sum(nil))[:4]

sha := sha512.New()
sha.Write([]byte(string(password) + salt))
encodedPass := hex.EncodeToString(sha.Sum(nil))[:20]
return []byte(strings.Join([]string{salt, encodedPass}, "@")), nil
}
23 changes: 23 additions & 0 deletions hash/hasher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,27 @@ func TestCompare(t *testing.T) {
assert.Nil(t, hash.Compare(context.Background(), []byte("test"), []byte("$argon2id$v=19$m=32,t=5,p=4$cm94YnRVOW5jZzFzcVE4bQ$fBxypOL0nP/zdPE71JtAV71i487LbX3fJI5PoTN6Lp4")))
assert.Nil(t, hash.CompareArgon2id(context.Background(), []byte("test"), []byte("$argon2id$v=19$m=32,t=5,p=4$cm94YnRVOW5jZzFzcVE4bQ$fBxypOL0nP/zdPE71JtAV71i487LbX3fJI5PoTN6Lp4")))
assert.Error(t, hash.Compare(context.Background(), []byte("test"), []byte("$argon2id$v=19$m=32,t=5,p=4$cm94YnRVOW5jZzFzcVE4bQ$fBxypOL0nP/zdPE71JtAV71i487LbX3fJI5PoTN6Lp5")))

assert.Nil(t, hash.CompareCustom(context.Background(), []byte("test"), []byte("f1d2@5cb452466722347b4e52")))
assert.Nil(t, hash.CompareCustom(context.Background(), []byte("test"), []byte("f451@b441460d45f3bd05e7ac")))
assert.Error(t, hash.CompareCustom(context.Background(), []byte("test"), []byte("f4c6@ed3c22ffd294a6a98d67")))
}

func TestComparatorCustomFail(t *testing.T) {
for k, pw := range [][]byte{
mkpw(t, 8),
mkpw(t, 16),
mkpw(t, 32),
mkpw(t, 64),
mkpw(t, 72),
} {
t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) {
mod := make([]byte, len(pw))
copy(mod, pw)
mod[len(pw)-1] = ^pw[len(pw)-1]

err := hash.CompareCustom(context.Background(), pw, mod)
assert.Error(t, err)
})
}
}

0 comments on commit 13c9b93

Please sign in to comment.