package verificationimp import ( "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/hex" "fmt" "github.com/tech/sendico/pkg/model" ) type TokenHasher interface { Hash(raw string, token *model.VerificationToken) string } type magicHasher struct{} func (magicHasher) Hash(raw string, _ *model.VerificationToken) string { return tokenHash(raw) } type otpHasher struct{} func (otpHasher) Hash(raw string, t *model.VerificationToken) string { return otpHash(raw, *t.Salt) } func hasherFor(t *model.VerificationToken) TokenHasher { if t.Salt != nil { return otpHasher{} } return magicHasher{} } const verificationTokenBytes = 32 const otpDigits = 6 func generateMagic() (raw, hash string, err error) { rawBytes := make([]byte, verificationTokenBytes) if _, err = rand.Read(rawBytes); err != nil { return } raw = base64.RawURLEncoding.EncodeToString(rawBytes) hash = tokenHash(raw) return } func generateDeterministicMagic(seed string) (raw, hash string) { sum := sha256.Sum256([]byte("magic:" + seed)) raw = base64.RawURLEncoding.EncodeToString(sum[:]) hash = tokenHash(raw) return } func generateOTP() (code, salt, hash string, err error) { // otpDigits-digit code n := make([]byte, 4) if _, err = rand.Read(n); err != nil { return } num := int(n[0])<<24 | int(n[1])<<16 | int(n[2])<<8 | int(n[3]) mod := 1 for i := 0; i < otpDigits; i++ { mod *= 10 } code = fmt.Sprintf("%0*d", otpDigits, num%mod) // per-token salt saltBytes := make([]byte, 16) if _, err = rand.Read(saltBytes); err != nil { return } salt = base64.RawURLEncoding.EncodeToString(saltBytes) hash = otpHash(code, salt) return } func generateDeterministicOTP(seed string) (code, salt, hash string) { sum := sha256.Sum256([]byte("otp:" + seed)) num := int(sum[0])<<24 | int(sum[1])<<16 | int(sum[2])<<8 | int(sum[3]) mod := 1 for i := 0; i < otpDigits; i++ { mod *= 10 } code = fmt.Sprintf("%0*d", otpDigits, num%mod) salt = base64.RawURLEncoding.EncodeToString(sum[4:20]) hash = otpHash(code, salt) return } // We store only the resulting hash (+salt) in DB, never the OTP itself. func otpHash(code, salt string) string { sum := sha256.Sum256([]byte(salt + ":" + code)) return hex.EncodeToString(sum[:]) } func tokenHash(rawToken string) string { hash := sha256.Sum256([]byte(rawToken)) return base64.RawURLEncoding.EncodeToString(hash[:]) }