package crypto import ( "crypto/aes" "crypto/cipher" "crypto/hmac" "crypto/rand" "crypto/sha256" "encoding/hex" "errors" "fmt" "strings" "golang.org/x/crypto/bcrypt" ) type Crypto struct { secret string } func NewCrypto(secret string) *Crypto { return &Crypto{secret: secret} } func (c *Crypto) EmailHash(email string) string { if email == "" { return email } normalized := strings.TrimSpace(strings.ToLower(email)) h := hmac.New(sha256.New, []byte(c.secret)) h.Write([]byte(normalized)) return hex.EncodeToString(h.Sum(nil)) } const bcryptCost = 12 func PasswordHash(password string) string { hash, err := bcrypt.GenerateFromPassword([]byte(password), bcryptCost) if err != nil { return "" } return string(hash) } func PasswordVerify(password, hash string) bool { err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) return err == nil } func (c *Crypto) getKey() []byte { h := sha256.New() h.Write([]byte(c.secret)) return h.Sum(nil) } func (c *Crypto) Encrypt(plaintext string) (string, error) { if plaintext == "" { return plaintext, nil } key := c.getKey() block, err := aes.NewCipher(key) if err != nil { return "", fmt.Errorf("failed to create cipher: %w", err) } aesGCM, err := cipher.NewGCM(block) if err != nil { return "", fmt.Errorf("failed to create GCM: %w", err) } iv := make([]byte, aesGCM.NonceSize()) if _, err := rand.Read(iv); err != nil { return "", fmt.Errorf("failed to generate IV: %w", err) } ciphertext := aesGCM.Seal(nil, iv, []byte(plaintext), nil) tag := ciphertext[len(ciphertext)-aesGCM.Overhead():] cipherOnly := ciphertext[:len(ciphertext)-aesGCM.Overhead()] return fmt.Sprintf("%s:%s:%s", hex.EncodeToString(iv), hex.EncodeToString(tag), hex.EncodeToString(cipherOnly), ), nil } func (c *Crypto) Decrypt(ciphertext string) (string, error) { if ciphertext == "" { return ciphertext, nil } parts := strings.Split(ciphertext, ":") if len(parts) != 3 { return "", errors.New("invalid encrypted value format") } ivHex, tagHex, cipherHex := parts[0], parts[1], parts[2] iv, err := hex.DecodeString(ivHex) if err != nil { return "", fmt.Errorf("failed to decode IV: %w", err) } tag, err := hex.DecodeString(tagHex) if err != nil { return "", fmt.Errorf("failed to decode tag: %w", err) } cipherOnly, err := hex.DecodeString(cipherHex) if err != nil { return "", fmt.Errorf("failed to decode ciphertext: %w", err) } key := c.getKey() block, err := aes.NewCipher(key) if err != nil { return "", fmt.Errorf("failed to create cipher: %w", err) } aesGCM, err := cipher.NewGCM(block) if err != nil { return "", fmt.Errorf("failed to create GCM: %w", err) } ciphertextWithTag := append(cipherOnly, tag...) plaintext, err := aesGCM.Open(nil, iv, ciphertextWithTag, nil) if err != nil { return "", fmt.Errorf("failed to decrypt: %w", err) } return string(plaintext), nil }