// Filename: internal/crypto/crypto.go package crypto import ( "crypto/aes" "crypto/cipher" "crypto/rand" "encoding/hex" "fmt" "gemini-balancer/internal/config" // [NEW] Crypto service now depends on Config "io" "os" ) type Service struct { gcm cipher.AEAD } func NewService(cfg *config.Config) (*Service, error) { keyHex := cfg.EncryptionKey if keyHex == "" { // Fallback to environment variable if not in config file keyHex = os.Getenv("ENCRYPTION_KEY") if keyHex == "" { return nil, fmt.Errorf("encryption key is not configured: please set 'encryption_key' in config.yaml or the ENCRYPTION_KEY environment variable") } } key, err := hex.DecodeString(keyHex) if err != nil { return nil, fmt.Errorf("failed to decode encryption key from hex: %w", err) } if len(key) != 32 { return nil, fmt.Errorf("invalid encryption key length: must be 32 bytes (64 hex characters), got %d bytes", len(key)) } block, err := aes.NewCipher(key) if err != nil { // ... (rest is the same) return nil, fmt.Errorf("failed to create AES cipher block: %w", err) } gcm, err := cipher.NewGCM(block) if err != nil { return nil, fmt.Errorf("failed to create GCM block cipher: %w", err) } return &Service{gcm: gcm}, nil } // Encrypt encrypts plaintext and returns hex-encoded ciphertext. func (s *Service) Encrypt(plaintext string) (string, error) { nonce := make([]byte, s.gcm.NonceSize()) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { return "", fmt.Errorf("failed to generate nonce: %w", err) } ciphertext := s.gcm.Seal(nonce, nonce, []byte(plaintext), nil) return hex.EncodeToString(ciphertext), nil } // Decrypt decrypts a hex-encoded ciphertext and returns the plaintext. func (s *Service) Decrypt(hexCiphertext string) (string, error) { ciphertext, err := hex.DecodeString(hexCiphertext) if err != nil { return "", fmt.Errorf("ciphertext hex decode error: %w", err) } nonceSize := s.gcm.NonceSize() if len(ciphertext) < nonceSize { return "", fmt.Errorf("invalid ciphertext: too short") } nonce, encryptedMessage := ciphertext[:nonceSize], ciphertext[nonceSize:] plaintext, err := s.gcm.Open(nil, nonce, encryptedMessage, nil) if err != nil { return "", fmt.Errorf("failed to decrypt: %w", err) } return string(plaintext), nil }