package crypto import ( "crypto/aes" "crypto/cipher" "crypto/hmac" "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/hex" "fmt" ) // Encrypt encrypts plaintext using AES-256-GCM with the given 32-byte key. // The returned ciphertext has a 12-byte random nonce prepended. func Encrypt(key, plaintext []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, fmt.Errorf("aes new cipher: %w", err) } gcm, err := cipher.NewGCM(block) if err != nil { return nil, fmt.Errorf("gcm new: %w", err) } nonce := make([]byte, gcm.NonceSize()) // 12 bytes if _, err := rand.Read(nonce); err != nil { return nil, fmt.Errorf("generate nonce: %w", err) } ciphertext := gcm.Seal(nonce, nonce, plaintext, nil) return ciphertext, nil } // Decrypt decrypts ciphertext produced by Encrypt. It expects the first // 12 bytes to be the nonce followed by the GCM-sealed data. func Decrypt(key, ciphertext []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, fmt.Errorf("aes new cipher: %w", err) } gcm, err := cipher.NewGCM(block) if err != nil { return nil, fmt.Errorf("gcm new: %w", err) } nonceSize := gcm.NonceSize() if len(ciphertext) < nonceSize { return nil, fmt.Errorf("ciphertext too short") } nonce, sealed := ciphertext[:nonceSize], ciphertext[nonceSize:] plaintext, err := gcm.Open(nil, nonce, sealed, nil) if err != nil { return nil, fmt.Errorf("gcm open: %w", err) } return plaintext, nil } // HMAC computes HMAC-SHA256 of data with the given key and returns a hex-encoded string. func HMAC(key, data []byte) string { mac := hmac.New(sha256.New, key) mac.Write(data) return hex.EncodeToString(mac.Sum(nil)) } // ParseKey base64-decodes b64Key and validates the result is exactly 32 bytes // (required for AES-256). func ParseKey(b64Key string) ([]byte, error) { key, err := base64.StdEncoding.DecodeString(b64Key) if err != nil { return nil, fmt.Errorf("base64 decode: %w", err) } if len(key) != 32 { return nil, fmt.Errorf("key must be exactly 32 bytes, got %d", len(key)) } return key, nil }