v0.0.47
This commit is contained in:
parent
2224db8e85
commit
7f88a0726c
270
cryptext/passHash.go
Normal file
270
cryptext/passHash.go
Normal file
@ -0,0 +1,270 @@
|
||||
package cryptext
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/totpext"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const LatestPassHashVersion = 4
|
||||
|
||||
// PassHash
|
||||
// - [v0]: plaintext password ( `0|...` )
|
||||
// - [v1]: sha256(plaintext)
|
||||
// - [v2]: seed | sha256<seed>(plaintext)
|
||||
// - [v3]: seed | sha256<seed>(plaintext) | [hex(totp)]
|
||||
// - [v4]: bcrypt(plaintext) | [hex(totp)]
|
||||
type PassHash string
|
||||
|
||||
func (ph PassHash) Valid() bool {
|
||||
_, _, _, _, _, valid := ph.Data()
|
||||
return valid
|
||||
}
|
||||
|
||||
func (ph PassHash) HasTOTP() bool {
|
||||
_, _, _, otp, _, _ := ph.Data()
|
||||
return otp
|
||||
}
|
||||
|
||||
func (ph PassHash) Data() (_version int, _seed []byte, _payload []byte, _totp bool, _totpsecret []byte, _valid bool) {
|
||||
|
||||
split := strings.Split(string(ph), "|")
|
||||
if len(split) == 0 {
|
||||
return -1, nil, nil, false, nil, false
|
||||
}
|
||||
|
||||
version, err := strconv.ParseInt(split[0], 10, 32)
|
||||
if err != nil {
|
||||
return -1, nil, nil, false, nil, false
|
||||
}
|
||||
|
||||
if version == 0 {
|
||||
if len(split) != 2 {
|
||||
return -1, nil, nil, false, nil, false
|
||||
}
|
||||
return int(version), nil, []byte(split[1]), false, nil, true
|
||||
}
|
||||
|
||||
if version == 1 {
|
||||
if len(split) != 2 {
|
||||
return -1, nil, nil, false, nil, false
|
||||
}
|
||||
payload, err := base64.RawStdEncoding.DecodeString(split[1])
|
||||
if err != nil {
|
||||
return -1, nil, nil, false, nil, false
|
||||
}
|
||||
return int(version), nil, payload, false, nil, true
|
||||
}
|
||||
|
||||
//
|
||||
if version == 2 {
|
||||
if len(split) != 3 {
|
||||
return -1, nil, nil, false, nil, false
|
||||
}
|
||||
seed, err := base64.RawStdEncoding.DecodeString(split[1])
|
||||
if err != nil {
|
||||
return -1, nil, nil, false, nil, false
|
||||
}
|
||||
payload, err := base64.RawStdEncoding.DecodeString(split[2])
|
||||
if err != nil {
|
||||
return -1, nil, nil, false, nil, false
|
||||
}
|
||||
return int(version), seed, payload, false, nil, true
|
||||
}
|
||||
|
||||
if version == 3 {
|
||||
if len(split) != 4 {
|
||||
return -1, nil, nil, false, nil, false
|
||||
}
|
||||
seed, err := base64.RawStdEncoding.DecodeString(split[1])
|
||||
if err != nil {
|
||||
return -1, nil, nil, false, nil, false
|
||||
}
|
||||
payload, err := base64.RawStdEncoding.DecodeString(split[2])
|
||||
if err != nil {
|
||||
return -1, nil, nil, false, nil, false
|
||||
}
|
||||
totp := false
|
||||
totpsecret := make([]byte, 0)
|
||||
if split[3] != "0" {
|
||||
totpsecret, err = hex.DecodeString(split[3])
|
||||
totp = true
|
||||
}
|
||||
return int(version), seed, payload, totp, totpsecret, true
|
||||
}
|
||||
|
||||
if version == 4 {
|
||||
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[3])
|
||||
totp = true
|
||||
}
|
||||
return int(version), nil, payload, totp, totpsecret, true
|
||||
}
|
||||
|
||||
return -1, nil, nil, false, nil, false
|
||||
}
|
||||
|
||||
func (ph PassHash) Verify(plainpass string, totp *string) bool {
|
||||
version, seed, payload, hastotp, totpsecret, valid := ph.Data()
|
||||
if !valid {
|
||||
return false
|
||||
}
|
||||
|
||||
if hastotp && totp == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if version == 0 {
|
||||
return langext.ArrEqualsExact([]byte(plainpass), payload)
|
||||
}
|
||||
|
||||
if version == 1 {
|
||||
return langext.ArrEqualsExact(hash256(plainpass), payload)
|
||||
}
|
||||
|
||||
if version == 2 {
|
||||
return langext.ArrEqualsExact(hash256Seeded(plainpass, seed), payload)
|
||||
}
|
||||
|
||||
if version == 3 {
|
||||
if !hastotp {
|
||||
return langext.ArrEqualsExact(hash256Seeded(plainpass, seed), payload)
|
||||
} else {
|
||||
return langext.ArrEqualsExact(hash256Seeded(plainpass, seed), payload) && totpext.Validate(totpsecret, *totp)
|
||||
}
|
||||
}
|
||||
|
||||
if version == 4 {
|
||||
if !hastotp {
|
||||
return bcrypt.CompareHashAndPassword(payload, []byte(plainpass)) != nil
|
||||
} else {
|
||||
return bcrypt.CompareHashAndPassword(payload, []byte(plainpass)) != nil && totpext.Validate(totpsecret, *totp)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (ph PassHash) NeedsPasswordUpgrade() bool {
|
||||
version, _, _, _, _, valid := ph.Data()
|
||||
return valid && version < LatestPassHashVersion
|
||||
}
|
||||
|
||||
func (ph PassHash) Upgrade(plainpass string) (PassHash, error) {
|
||||
version, _, _, hastotp, totpsecret, valid := ph.Data()
|
||||
if !valid {
|
||||
return "", errors.New("invalid password")
|
||||
}
|
||||
if version == LatestPassHashVersion {
|
||||
return ph, nil
|
||||
}
|
||||
if hastotp {
|
||||
return HashPassword(plainpass, totpsecret)
|
||||
} else {
|
||||
return HashPassword(plainpass, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (ph PassHash) String() string {
|
||||
return string(ph)
|
||||
}
|
||||
|
||||
func HashPassword(plainpass string, totpSecret []byte) (PassHash, error) {
|
||||
return HashPasswordV4(plainpass, totpSecret)
|
||||
}
|
||||
|
||||
func HashPasswordV4(plainpass string, totpSecret []byte) (PassHash, error) {
|
||||
var strtotp string
|
||||
|
||||
if totpSecret == nil {
|
||||
strtotp = "0"
|
||||
} else {
|
||||
strtotp = hex.EncodeToString(totpSecret)
|
||||
}
|
||||
|
||||
payload, err := bcrypt.GenerateFromPassword([]byte(plainpass), bcrypt.MinCost)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return PassHash(fmt.Sprintf("4|%s|%s", string(payload), strtotp)), nil
|
||||
}
|
||||
|
||||
func HashPasswordV3(plainpass string, totpSecret []byte) (PassHash, error) {
|
||||
var strtotp string
|
||||
|
||||
if totpSecret == nil {
|
||||
strtotp = "0"
|
||||
} else {
|
||||
strtotp = hex.EncodeToString(totpSecret)
|
||||
}
|
||||
|
||||
seed, err := newSeed()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
checksum := hash256Seeded(plainpass, seed)
|
||||
|
||||
return PassHash(fmt.Sprintf("3|%s|%s|%s",
|
||||
base64.RawStdEncoding.EncodeToString(seed),
|
||||
base64.RawStdEncoding.EncodeToString(checksum),
|
||||
strtotp)), nil
|
||||
}
|
||||
|
||||
func HashPasswordV2(plainpass string) (PassHash, error) {
|
||||
seed, err := newSeed()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
checksum := hash256Seeded(plainpass, seed)
|
||||
|
||||
return PassHash(fmt.Sprintf("2|%s|%s", base64.RawStdEncoding.EncodeToString(seed), base64.RawStdEncoding.EncodeToString(checksum))), nil
|
||||
}
|
||||
|
||||
func HashPasswordV1(plainpass string) (PassHash, error) {
|
||||
return PassHash(fmt.Sprintf("1|%s", base64.RawStdEncoding.EncodeToString(hash256(plainpass)))), nil
|
||||
}
|
||||
|
||||
func HashPasswordV0(plainpass string) (PassHash, error) {
|
||||
return PassHash(fmt.Sprintf("0|%s", plainpass)), nil
|
||||
}
|
||||
|
||||
func hash256(s string) []byte {
|
||||
h := sha256.New()
|
||||
h.Write([]byte(s))
|
||||
bs := h.Sum(nil)
|
||||
return bs
|
||||
}
|
||||
|
||||
func hash256Seeded(s string, seed []byte) []byte {
|
||||
h := sha256.New()
|
||||
h.Write(seed)
|
||||
h.Write([]byte(s))
|
||||
bs := h.Sum(nil)
|
||||
return bs
|
||||
}
|
||||
|
||||
func newSeed() ([]byte, error) {
|
||||
secret := make([]byte, 32)
|
||||
_, err := rand.Read(secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return secret, nil
|
||||
}
|
9
go.mod
9
go.mod
@ -3,8 +3,11 @@ module gogs.mikescher.com/BlackForestBytes/goext
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
golang.org/x/sys v0.1.0
|
||||
golang.org/x/term v0.1.0
|
||||
golang.org/x/sys v0.3.0
|
||||
golang.org/x/term v0.3.0
|
||||
)
|
||||
|
||||
require github.com/jmoiron/sqlx v1.3.5 // indirect
|
||||
require (
|
||||
github.com/jmoiron/sqlx v1.3.5 // indirect
|
||||
golang.org/x/crypto v0.4.0 // indirect
|
||||
)
|
||||
|
6
go.sum
6
go.sum
@ -3,7 +3,13 @@ github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
||||
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
|
||||
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
|
||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
|
@ -51,7 +51,7 @@ func (r *StructScanner) Start(dest any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// StructScan forked from github.com/jmoiron/sqlx@v1.3.5/sqlx.go
|
||||
// StructScanExt forked from github.com/jmoiron/sqlx@v1.3.5/sqlx.go
|
||||
// does also wok with nullabel structs (from LEFT JOIN's)
|
||||
func (r *StructScanner) StructScanExt(dest any) error {
|
||||
v := reflect.ValueOf(dest)
|
||||
@ -148,7 +148,7 @@ func (r *StructScanner) StructScanExt(dest any) error {
|
||||
return r.rows.Err()
|
||||
}
|
||||
|
||||
// StructScan forked from github.com/jmoiron/sqlx@v1.3.5/sqlx.go
|
||||
// StructScanBase forked from github.com/jmoiron/sqlx@v1.3.5/sqlx.go
|
||||
// without (relevant) changes
|
||||
func (r *StructScanner) StructScanBase(dest any) error {
|
||||
v := reflect.ValueOf(dest)
|
||||
|
Loading…
Reference in New Issue
Block a user