package storage import ( "crypto/aes" "crypto/cipher" "crypto/rand" "errors" "io" ) // ErrCiphertextTooShort is returned when ciphertext is shorter than the GCM nonce size. var ErrCiphertextTooShort = errors.New("ciphertext too short") // Encrypt encrypts plaintext using AES-256-GCM with a random nonce. // The nonce is prepended to the returned ciphertext. // key must be exactly 32 bytes (AES-256). func Encrypt(plaintext []byte, key []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } gcm, err := cipher.NewGCM(block) if err != nil { return nil, err } nonce := make([]byte, gcm.NonceSize()) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { return nil, err } // Seal appends encrypted data to nonce, so nonce is prepended ciphertext := gcm.Seal(nonce, nonce, plaintext, nil) return ciphertext, nil } // Decrypt decrypts ciphertext produced by Encrypt. // Expects the nonce to be prepended to the ciphertext. func Decrypt(ciphertext []byte, key []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } gcm, err := cipher.NewGCM(block) if err != nil { return nil, err } nonceSize := gcm.NonceSize() if len(ciphertext) < nonceSize { return nil, ErrCiphertextTooShort } nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] return gcm.Open(nil, nonce, ciphertext, nil) }