v0.0.171
This commit is contained in:
parent
c320bb3d90
commit
710c257c64
@ -3,6 +3,7 @@ package cryptext
|
|||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
@ -14,14 +15,15 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const LatestPassHashVersion = 4
|
const LatestPassHashVersion = 5
|
||||||
|
|
||||||
// PassHash
|
// PassHash
|
||||||
// - [v0]: plaintext password ( `0|...` )
|
// - [v0]: plaintext password ( `0|...` ) // simple, used to write PW's directly in DB
|
||||||
// - [v1]: sha256(plaintext)
|
// - [v1]: sha256(plaintext) // simple hashing
|
||||||
// - [v2]: seed | sha256<seed>(plaintext)
|
// - [v2]: seed | sha256<seed>(plaintext) // add seed
|
||||||
// - [v3]: seed | sha256<seed>(plaintext) | [hex(totp)]
|
// - [v3]: seed | sha256<seed>(plaintext) | [hex(totp)] // add TOTP support
|
||||||
// - [v4]: bcrypt(plaintext) | [hex(totp)]
|
// - [v4]: bcrypt(plaintext) | [hex(totp)] // use proper bcrypt
|
||||||
|
// - [v5]: bcrypt(sha512(plaintext)) | [hex(totp)] // hash pw before bcrypt (otherwise max pw-len = 72)
|
||||||
type PassHash string
|
type PassHash string
|
||||||
|
|
||||||
func (ph PassHash) Valid() bool {
|
func (ph PassHash) Valid() bool {
|
||||||
@ -109,7 +111,21 @@ func (ph PassHash) Data() (_version int, _seed []byte, _payload []byte, _totp bo
|
|||||||
totp := false
|
totp := false
|
||||||
totpsecret := make([]byte, 0)
|
totpsecret := make([]byte, 0)
|
||||||
if split[2] != "0" {
|
if split[2] != "0" {
|
||||||
totpsecret, err = hex.DecodeString(split[3])
|
totpsecret, err = hex.DecodeString(split[2])
|
||||||
|
totp = true
|
||||||
|
}
|
||||||
|
return int(version), nil, payload, totp, totpsecret, true
|
||||||
|
}
|
||||||
|
|
||||||
|
if version == 5 {
|
||||||
|
if len(split) != 3 {
|
||||||
|
return -1, nil, nil, false, nil, false
|
||||||
|
}
|
||||||
|
payload := []byte(split[1])
|
||||||
|
totp := false
|
||||||
|
totpsecret := make([]byte, 0)
|
||||||
|
if split[2] != "0" {
|
||||||
|
totpsecret, err = hex.DecodeString(split[2])
|
||||||
totp = true
|
totp = true
|
||||||
}
|
}
|
||||||
return int(version), nil, payload, totp, totpsecret, true
|
return int(version), nil, payload, totp, totpsecret, true
|
||||||
@ -156,6 +172,14 @@ func (ph PassHash) Verify(plainpass string, totp *string) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if version == 5 {
|
||||||
|
if !hastotp {
|
||||||
|
return bcrypt.CompareHashAndPassword(payload, hash512(plainpass)) == nil
|
||||||
|
} else {
|
||||||
|
return bcrypt.CompareHashAndPassword(payload, hash512(plainpass)) == nil && totpext.Validate(totpsecret, *totp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,6 +233,12 @@ func (ph PassHash) ClearTOTP() (PassHash, error) {
|
|||||||
return PassHash(strings.Join(split, "|")), nil
|
return PassHash(strings.Join(split, "|")), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if version == 5 {
|
||||||
|
split := strings.Split(string(ph), "|")
|
||||||
|
split[2] = "0"
|
||||||
|
return PassHash(strings.Join(split, "|")), nil
|
||||||
|
}
|
||||||
|
|
||||||
return "", errors.New("unknown version")
|
return "", errors.New("unknown version")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,6 +272,12 @@ func (ph PassHash) WithTOTP(totpSecret []byte) (PassHash, error) {
|
|||||||
return PassHash(strings.Join(split, "|")), nil
|
return PassHash(strings.Join(split, "|")), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if version == 5 {
|
||||||
|
split := strings.Split(string(ph), "|")
|
||||||
|
split[2] = hex.EncodeToString(totpSecret)
|
||||||
|
return PassHash(strings.Join(split, "|")), nil
|
||||||
|
}
|
||||||
|
|
||||||
return "", errors.New("unknown version")
|
return "", errors.New("unknown version")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,6 +307,10 @@ func (ph PassHash) Change(newPlainPass string) (PassHash, error) {
|
|||||||
return HashPasswordV4(newPlainPass, langext.Conditional(hastotp, totpsecret, nil))
|
return HashPasswordV4(newPlainPass, langext.Conditional(hastotp, totpsecret, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if version == 5 {
|
||||||
|
return HashPasswordV5(newPlainPass, langext.Conditional(hastotp, totpsecret, nil))
|
||||||
|
}
|
||||||
|
|
||||||
return "", errors.New("unknown version")
|
return "", errors.New("unknown version")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,7 +319,24 @@ func (ph PassHash) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func HashPassword(plainpass string, totpSecret []byte) (PassHash, error) {
|
func HashPassword(plainpass string, totpSecret []byte) (PassHash, error) {
|
||||||
return HashPasswordV4(plainpass, totpSecret)
|
return HashPasswordV5(plainpass, totpSecret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HashPasswordV5(plainpass string, totpSecret []byte) (PassHash, error) {
|
||||||
|
var strtotp string
|
||||||
|
|
||||||
|
if totpSecret == nil {
|
||||||
|
strtotp = "0"
|
||||||
|
} else {
|
||||||
|
strtotp = hex.EncodeToString(totpSecret)
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, err := bcrypt.GenerateFromPassword(hash512(plainpass), bcrypt.MinCost)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return PassHash(fmt.Sprintf("5|%s|%s", string(payload), strtotp)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func HashPasswordV4(plainpass string, totpSecret []byte) (PassHash, error) {
|
func HashPasswordV4(plainpass string, totpSecret []byte) (PassHash, error) {
|
||||||
@ -340,6 +397,13 @@ func HashPasswordV0(plainpass string) (PassHash, error) {
|
|||||||
return PassHash(fmt.Sprintf("0|%s", plainpass)), nil
|
return PassHash(fmt.Sprintf("0|%s", plainpass)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hash512(s string) []byte {
|
||||||
|
h := sha512.New()
|
||||||
|
h.Write([]byte(s))
|
||||||
|
bs := h.Sum(nil)
|
||||||
|
return bs
|
||||||
|
}
|
||||||
|
|
||||||
func hash256(s string) []byte {
|
func hash256(s string) []byte {
|
||||||
h := sha256.New()
|
h := sha256.New()
|
||||||
h.Write([]byte(s))
|
h.Write([]byte(s))
|
||||||
|
210
cryptext/passHash_test.go
Normal file
210
cryptext/passHash_test.go
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
package cryptext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||||
|
"gogs.mikescher.com/BlackForestBytes/goext/totpext"
|
||||||
|
"gogs.mikescher.com/BlackForestBytes/goext/tst"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPassHash1(t *testing.T) {
|
||||||
|
ph, err := HashPassword("test123", nil)
|
||||||
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
|
tst.AssertTrue(t, ph.Valid())
|
||||||
|
tst.AssertFalse(t, ph.HasTOTP())
|
||||||
|
tst.AssertFalse(t, ph.NeedsPasswordUpgrade())
|
||||||
|
|
||||||
|
tst.AssertTrue(t, ph.Verify("test123", nil))
|
||||||
|
tst.AssertFalse(t, ph.Verify("test124", nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPassHashTOTP(t *testing.T) {
|
||||||
|
sec, err := totpext.GenerateSecret()
|
||||||
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
|
ph, err := HashPassword("test123", sec)
|
||||||
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
|
tst.AssertTrue(t, ph.Valid())
|
||||||
|
tst.AssertTrue(t, ph.HasTOTP())
|
||||||
|
tst.AssertFalse(t, ph.NeedsPasswordUpgrade())
|
||||||
|
|
||||||
|
tst.AssertFalse(t, ph.Verify("test123", nil))
|
||||||
|
tst.AssertFalse(t, ph.Verify("test124", nil))
|
||||||
|
tst.AssertTrue(t, ph.Verify("test123", langext.Ptr(totpext.TOTP(sec))))
|
||||||
|
tst.AssertFalse(t, ph.Verify("test124", nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPassHashUpgrade_V0(t *testing.T) {
|
||||||
|
ph, err := HashPasswordV0("test123")
|
||||||
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
|
tst.AssertTrue(t, ph.Valid())
|
||||||
|
tst.AssertFalse(t, ph.HasTOTP())
|
||||||
|
tst.AssertTrue(t, ph.NeedsPasswordUpgrade())
|
||||||
|
|
||||||
|
tst.AssertTrue(t, ph.Verify("test123", nil))
|
||||||
|
tst.AssertFalse(t, ph.Verify("test124", nil))
|
||||||
|
|
||||||
|
ph, err = ph.Upgrade("test123")
|
||||||
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
|
tst.AssertTrue(t, ph.Valid())
|
||||||
|
tst.AssertFalse(t, ph.HasTOTP())
|
||||||
|
tst.AssertFalse(t, ph.NeedsPasswordUpgrade())
|
||||||
|
|
||||||
|
tst.AssertTrue(t, ph.Verify("test123", nil))
|
||||||
|
tst.AssertFalse(t, ph.Verify("test124", nil))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPassHashUpgrade_V1(t *testing.T) {
|
||||||
|
ph, err := HashPasswordV1("test123")
|
||||||
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
|
tst.AssertTrue(t, ph.Valid())
|
||||||
|
tst.AssertFalse(t, ph.HasTOTP())
|
||||||
|
tst.AssertTrue(t, ph.NeedsPasswordUpgrade())
|
||||||
|
|
||||||
|
tst.AssertTrue(t, ph.Verify("test123", nil))
|
||||||
|
tst.AssertFalse(t, ph.Verify("test124", nil))
|
||||||
|
|
||||||
|
ph, err = ph.Upgrade("test123")
|
||||||
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
|
tst.AssertTrue(t, ph.Valid())
|
||||||
|
tst.AssertFalse(t, ph.HasTOTP())
|
||||||
|
tst.AssertFalse(t, ph.NeedsPasswordUpgrade())
|
||||||
|
|
||||||
|
tst.AssertTrue(t, ph.Verify("test123", nil))
|
||||||
|
tst.AssertFalse(t, ph.Verify("test124", nil))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPassHashUpgrade_V2(t *testing.T) {
|
||||||
|
ph, err := HashPasswordV2("test123")
|
||||||
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
|
tst.AssertTrue(t, ph.Valid())
|
||||||
|
tst.AssertFalse(t, ph.HasTOTP())
|
||||||
|
tst.AssertTrue(t, ph.NeedsPasswordUpgrade())
|
||||||
|
|
||||||
|
tst.AssertTrue(t, ph.Verify("test123", nil))
|
||||||
|
tst.AssertFalse(t, ph.Verify("test124", nil))
|
||||||
|
|
||||||
|
ph, err = ph.Upgrade("test123")
|
||||||
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
|
tst.AssertTrue(t, ph.Valid())
|
||||||
|
tst.AssertFalse(t, ph.HasTOTP())
|
||||||
|
tst.AssertFalse(t, ph.NeedsPasswordUpgrade())
|
||||||
|
|
||||||
|
tst.AssertTrue(t, ph.Verify("test123", nil))
|
||||||
|
tst.AssertFalse(t, ph.Verify("test124", nil))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPassHashUpgrade_V3(t *testing.T) {
|
||||||
|
ph, err := HashPasswordV3("test123", nil)
|
||||||
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
|
tst.AssertTrue(t, ph.Valid())
|
||||||
|
tst.AssertFalse(t, ph.HasTOTP())
|
||||||
|
tst.AssertTrue(t, ph.NeedsPasswordUpgrade())
|
||||||
|
|
||||||
|
tst.AssertTrue(t, ph.Verify("test123", nil))
|
||||||
|
tst.AssertFalse(t, ph.Verify("test124", nil))
|
||||||
|
|
||||||
|
ph, err = ph.Upgrade("test123")
|
||||||
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
|
tst.AssertTrue(t, ph.Valid())
|
||||||
|
tst.AssertFalse(t, ph.HasTOTP())
|
||||||
|
tst.AssertFalse(t, ph.NeedsPasswordUpgrade())
|
||||||
|
|
||||||
|
tst.AssertTrue(t, ph.Verify("test123", nil))
|
||||||
|
tst.AssertFalse(t, ph.Verify("test124", nil))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPassHashUpgrade_V3_TOTP(t *testing.T) {
|
||||||
|
sec, err := totpext.GenerateSecret()
|
||||||
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
|
ph, err := HashPasswordV3("test123", sec)
|
||||||
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
|
tst.AssertTrue(t, ph.Valid())
|
||||||
|
tst.AssertTrue(t, ph.HasTOTP())
|
||||||
|
tst.AssertTrue(t, ph.NeedsPasswordUpgrade())
|
||||||
|
|
||||||
|
tst.AssertFalse(t, ph.Verify("test123", nil))
|
||||||
|
tst.AssertFalse(t, ph.Verify("test124", nil))
|
||||||
|
tst.AssertTrue(t, ph.Verify("test123", langext.Ptr(totpext.TOTP(sec))))
|
||||||
|
tst.AssertFalse(t, ph.Verify("test124", nil))
|
||||||
|
|
||||||
|
ph, err = ph.Upgrade("test123")
|
||||||
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
|
tst.AssertTrue(t, ph.Valid())
|
||||||
|
tst.AssertTrue(t, ph.HasTOTP())
|
||||||
|
tst.AssertFalse(t, ph.NeedsPasswordUpgrade())
|
||||||
|
|
||||||
|
tst.AssertFalse(t, ph.Verify("test123", nil))
|
||||||
|
tst.AssertFalse(t, ph.Verify("test124", nil))
|
||||||
|
tst.AssertTrue(t, ph.Verify("test123", langext.Ptr(totpext.TOTP(sec))))
|
||||||
|
tst.AssertFalse(t, ph.Verify("test124", nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPassHashUpgrade_V4(t *testing.T) {
|
||||||
|
ph, err := HashPasswordV4("test123", nil)
|
||||||
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
|
tst.AssertTrue(t, ph.Valid())
|
||||||
|
tst.AssertFalse(t, ph.HasTOTP())
|
||||||
|
tst.AssertTrue(t, ph.NeedsPasswordUpgrade())
|
||||||
|
|
||||||
|
tst.AssertTrue(t, ph.Verify("test123", nil))
|
||||||
|
tst.AssertFalse(t, ph.Verify("test124", nil))
|
||||||
|
|
||||||
|
ph, err = ph.Upgrade("test123")
|
||||||
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
|
tst.AssertTrue(t, ph.Valid())
|
||||||
|
tst.AssertFalse(t, ph.HasTOTP())
|
||||||
|
tst.AssertFalse(t, ph.NeedsPasswordUpgrade())
|
||||||
|
|
||||||
|
tst.AssertTrue(t, ph.Verify("test123", nil))
|
||||||
|
tst.AssertFalse(t, ph.Verify("test124", nil))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPassHashUpgrade_V4_TOTP(t *testing.T) {
|
||||||
|
sec, err := totpext.GenerateSecret()
|
||||||
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
|
ph, err := HashPasswordV4("test123", sec)
|
||||||
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
|
tst.AssertTrue(t, ph.Valid())
|
||||||
|
tst.AssertTrue(t, ph.HasTOTP())
|
||||||
|
tst.AssertTrue(t, ph.NeedsPasswordUpgrade())
|
||||||
|
|
||||||
|
tst.AssertFalse(t, ph.Verify("test123", nil))
|
||||||
|
tst.AssertFalse(t, ph.Verify("test124", nil))
|
||||||
|
tst.AssertTrue(t, ph.Verify("test123", langext.Ptr(totpext.TOTP(sec))))
|
||||||
|
tst.AssertFalse(t, ph.Verify("test124", nil))
|
||||||
|
|
||||||
|
ph, err = ph.Upgrade("test123")
|
||||||
|
tst.AssertNoErr(t, err)
|
||||||
|
|
||||||
|
tst.AssertTrue(t, ph.Valid())
|
||||||
|
tst.AssertTrue(t, ph.HasTOTP())
|
||||||
|
tst.AssertFalse(t, ph.NeedsPasswordUpgrade())
|
||||||
|
|
||||||
|
tst.AssertFalse(t, ph.Verify("test123", nil))
|
||||||
|
tst.AssertFalse(t, ph.Verify("test124", nil))
|
||||||
|
tst.AssertTrue(t, ph.Verify("test123", langext.Ptr(totpext.TOTP(sec))))
|
||||||
|
tst.AssertFalse(t, ph.Verify("test124", nil))
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
package goext
|
package goext
|
||||||
|
|
||||||
const GoextVersion = "0.0.170"
|
const GoextVersion = "0.0.171"
|
||||||
|
|
||||||
const GoextVersionTimestamp = "2023-07-17T12:42:49+0200"
|
const GoextVersionTimestamp = "2023-07-18T13:34:54+0200"
|
||||||
|
@ -2,6 +2,7 @@ package tst
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"runtime/debug"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -54,12 +55,18 @@ func AssertHexEqual(t *testing.T, expected string, actual []byte) {
|
|||||||
|
|
||||||
func AssertTrue(t *testing.T, value bool) {
|
func AssertTrue(t *testing.T, value bool) {
|
||||||
if !value {
|
if !value {
|
||||||
t.Error("value should be true")
|
t.Error("value should be true\n" + string(debug.Stack()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func AssertFalse(t *testing.T, value bool) {
|
func AssertFalse(t *testing.T, value bool) {
|
||||||
if value {
|
if value {
|
||||||
t.Error("value should be false")
|
t.Error("value should be false\n" + string(debug.Stack()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AssertNoErr(t *testing.T, anerr error) {
|
||||||
|
if anerr != nil {
|
||||||
|
t.Error("Function returned an error: " + anerr.Error() + "\n" + string(debug.Stack()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user