package symmetric import ( "crypto/aes" "crypto/cipher" "crypto/rand" "fmt" ) // Encrypt encrypts plaintext using AES-256-GCM. // Returns (ciphertext, nonce). func Encrypt(key, plaintext, associatedData []byte) (ciphertext, nonce []byte, err error) { if len(key) != 32 { return nil, nil, fmt.Errorf("key must be 32 bytes (AES-256), got %d", len(key)) } block, err := aes.NewCipher(key) if err != nil { return nil, nil, fmt.Errorf("creating AES cipher: %w", err) } gcm, err := cipher.NewGCM(block) if err != nil { return nil, nil, fmt.Errorf("creating GCM: %w", err) } nonce = make([]byte, gcm.NonceSize()) if _, err := rand.Read(nonce); err != nil { return nil, nil, fmt.Errorf("generating nonce: %w", err) } ciphertext = gcm.Seal(nil, nonce, plaintext, associatedData) return ciphertext, nonce, nil } // Decrypt decrypts ciphertext using AES-256-GCM. func Decrypt(key, ciphertext, nonce, associatedData []byte) ([]byte, error) { if len(key) != 32 { return nil, fmt.Errorf("key must be 32 bytes (AES-256), got %d", len(key)) } block, err := aes.NewCipher(key) if err != nil { return nil, fmt.Errorf("creating AES cipher: %w", err) } gcm, err := cipher.NewGCM(block) if err != nil { return nil, fmt.Errorf("creating GCM: %w", err) } plaintext, err := gcm.Open(nil, nonce, ciphertext, associatedData) if err != nil { return nil, fmt.Errorf("decrypting: %w", err) } return plaintext, nil } // EncryptWithNonce encrypts plaintext using AES-256-GCM with a specified nonce. func EncryptWithNonce(key, plaintext, nonce, associatedData []byte) ([]byte, error) { if len(key) != 32 { return nil, fmt.Errorf("key must be 32 bytes (AES-256), got %d", len(key)) } block, err := aes.NewCipher(key) if err != nil { return nil, fmt.Errorf("creating AES cipher: %w", err) } gcm, err := cipher.NewGCM(block) if err != nil { return nil, fmt.Errorf("creating GCM: %w", err) } return gcm.Seal(nil, nonce, plaintext, associatedData), nil }