owpengram-server/internal/app/account/srp.go
2026-08-02 14:12:04 +08:00

185 lines
5.1 KiB
Go

package account
import (
"bytes"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"math/big"
"telesrv/internal/domain"
)
const (
passwordHashSize = 256
)
var (
baseSalt1 = []byte{0xEC, 0xF8, 0x73, 0x76, 0x65, 0xBC, 0x77, 0x5A}
baseSalt2 = []byte{0xBE, 0xDE, 0x48, 0x88, 0x8C, 0x0F, 0x42, 0xAC, 0x34, 0xFF, 0xD1, 0xD4, 0x93, 0x5D, 0x8B, 0x21}
baseP = mustDecodeHex("c71caeb9c6b1c9048e6c522f70f13f73980d40238e3e21c14934d037563d930f48198a0aa7c14058229493d22530f4dbfa336f6e0ac925139543aed44cce7c3720fd51f69458705ac68cd4fe6b6b13abdc9746512969328454f18faf8c595f642477fe96bb2a941d5bcd1d4ac8cc49880708fa9b378e3c4f3a9060bee67cf9a4a4a695811051907e162753b56b0f6b410dba74d8a84b2a14b3144e0ef1284754fd17ed950d5965b4b9dd46582db1178d169c6bc465b0d6ff9ca3928fef5b9ae4e418fc15e83ebea0f87fa9ff5eed70050ded2849f47bf959d956850ce929851f0d8115f635b105ee2e4e15d04b2454bf6f4fadf034b10403119cd8e3b92fcc5b")
baseG = 3
)
func defaultPasswordAlgo() domain.PasswordKDFAlgo {
return domain.PasswordKDFAlgo{
Salt1: append([]byte(nil), baseSalt1...),
Salt2: append([]byte(nil), baseSalt2...),
G: baseG,
P: append([]byte(nil), baseP...),
}
}
func defaultSecureAlgo() domain.SecurePasswordKDFAlgo {
return domain.SecurePasswordKDFAlgo{
Kind: "pbkdf2_hmac_sha512_iter100000",
Salt: []byte{0x7D, 0x04, 0xB3, 0x4B, 0x94, 0x82, 0x8C, 0x3D},
}
}
func makeSRPChallenge(verifier []byte) (secret, b []byte, err error) {
secret = make([]byte, passwordHashSize)
if _, err := rand.Read(secret); err != nil {
return nil, nil, err
}
b, err = calcSRPB(secret, verifier)
if err != nil {
return nil, nil, err
}
return secret, b, nil
}
func calcSRPB(secret, verifier []byte) ([]byte, error) {
p := new(big.Int).SetBytes(baseP)
g := big.NewInt(int64(baseG))
v := new(big.Int).SetBytes(verifier)
b := new(big.Int).SetBytes(secret)
if v.Sign() <= 0 || v.Cmp(p) >= 0 {
return nil, domain.ErrPasswordHashInvalid
}
k := new(big.Int).SetBytes(hashBytes(padToHash(baseP), padToHash(g.Bytes())))
kv := new(big.Int).Mul(k, v)
kv.Mod(kv, p)
gb := new(big.Int).Exp(g, b, p)
out := new(big.Int).Add(kv, gb)
out.Mod(out, p)
return padToHash(out.Bytes()), nil
}
func checkSRP(settings domain.PasswordSettings, check domain.PasswordCheck) error {
if check.Empty {
if settings.HasPassword {
return domain.ErrPasswordHashInvalid
}
return nil
}
if !settings.HasPassword {
return domain.ErrPasswordHashInvalid
}
if settings.SRPID == 0 || settings.SRPID != check.SRPID {
return domain.ErrSRPIDInvalid
}
if len(settings.SRPVerifier) == 0 || len(settings.SRPBSecret) == 0 || len(settings.SRPB) == 0 {
return domain.ErrSRPPasswordChanged
}
got, err := calcSRPM1(settings, check.A)
if err != nil {
return err
}
if !bytes.Equal(got, check.M1) {
return domain.ErrPasswordHashInvalid
}
return nil
}
func calcSRPM1(settings domain.PasswordSettings, aBytes []byte) ([]byte, error) {
p := new(big.Int).SetBytes(baseP)
g := big.NewInt(int64(baseG))
a := new(big.Int).SetBytes(aBytes)
if !isGoodLarge(a, p) {
return nil, domain.ErrPasswordHashInvalid
}
v := new(big.Int).SetBytes(settings.SRPVerifier)
if !isGoodLarge(v, p) {
return nil, domain.ErrPasswordHashInvalid
}
b := new(big.Int).SetBytes(settings.SRPBSecret)
bForHash := padToHash(settings.SRPB)
aForHash := padToHash(aBytes)
u := new(big.Int).SetBytes(hashBytes(aForHash, bForHash))
if u.Sign() <= 0 {
return nil, domain.ErrPasswordHashInvalid
}
vu := new(big.Int).Exp(v, u, p)
sBase := new(big.Int).Mul(a, vu)
sBase.Mod(sBase, p)
s := new(big.Int).Exp(sBase, b, p)
k := hashBytes(padToHash(s.Bytes()))
salt1 := settings.NewAlgo.Salt1
if settings.CurrentAlgo != nil {
salt1 = settings.CurrentAlgo.Salt1
}
return hashBytes(
xorBytes(hashBytes(padToHash(baseP)), hashBytes(padToHash(g.Bytes()))),
hashBytes(salt1),
hashBytes(baseSalt2),
aForHash,
bForHash,
k,
), nil
}
func validateNewPasswordSettings(in domain.PasswordInputSettings) error {
if in.NewAlgo == nil || len(in.NewPasswordHash) == 0 {
return domain.ErrNewSettingsInvalid
}
algo := in.NewAlgo
if algo.G != baseG || !bytes.Equal(algo.P, baseP) || !bytes.Equal(algo.Salt2, baseSalt2) {
return domain.ErrNewSaltInvalid
}
if len(algo.Salt1) != len(baseSalt1)+32 || !bytes.Equal(algo.Salt1[:len(baseSalt1)], baseSalt1) {
return domain.ErrNewSaltInvalid
}
v := new(big.Int).SetBytes(in.NewPasswordHash)
if !isGoodLarge(v, new(big.Int).SetBytes(baseP)) {
return domain.ErrPasswordHashInvalid
}
return nil
}
func hashBytes(parts ...[]byte) []byte {
h := sha256.New()
for _, part := range parts {
_, _ = h.Write(part)
}
return h.Sum(nil)
}
func padToHash(in []byte) []byte {
if len(in) >= passwordHashSize {
return append([]byte(nil), in[len(in)-passwordHashSize:]...)
}
out := make([]byte, passwordHashSize)
copy(out[passwordHashSize-len(in):], in)
return out
}
func xorBytes(a, b []byte) []byte {
out := make([]byte, len(a))
for i := range a {
out[i] = a[i] ^ b[i]
}
return out
}
func isGoodLarge(n, p *big.Int) bool {
return n.Sign() > 0 && new(big.Int).Sub(p, n).Sign() > 0
}
func mustDecodeHex(s string) []byte {
out, err := hex.DecodeString(s)
if err != nil {
panic(err)
}
return out
}