Skip to content

Instantly share code, notes, and snippets.

@anyu
Last active August 6, 2021 17:57
Show Gist options
  • Save anyu/a9ebf0c8c7ac18323efd09166d04185a to your computer and use it in GitHub Desktop.
Save anyu/a9ebf0c8c7ac18323efd09166d04185a to your computer and use it in GitHub Desktop.
// Simple Go implementation of AES-GCM-256 encryption/decryption, with explanations
// https://play.golang.org/p/sQABEg5fR1T
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"fmt"
"io"
"log"
)
func main() {
plaintext := "the text to encrypt"
// AES-256 requires key size of 32 bytes (32 hexademical chars)
bytes := make([]byte, 32)
// Encode key to some storable format (string, base64, etc), save in secrets manager
key := string(bytes)
encrypted, err := encrypt(plaintext, key)
if err != nil {
log.Fatalf("error encrypting: %v", err)
}
fmt.Printf("encrypted: %s\n", encrypted)
decrypted, err := decrypt(encrypted, key)
if err != nil {
log.Fatalf("error decrypting: %v", err)
}
fmt.Printf("decrypted: %s\n", decrypted)
}
func encrypt(plaintextStr, keyStr string) (string, error) {
// Decode key from some stored format
key := []byte(keyStr)
// Create AES block cipher
c, err := aes.NewCipher(key)
if err != nil {
return "", fmt.Errorf("error creating AES block cipher: %s", err)
}
// Wrap cipher block in GCM
gcm, err := cipher.NewGCM(c)
if err != nil {
return "", fmt.Errorf("error wrapping block cipher in GCM: %s", err)
}
// Make a byte array the size of the nonce that must be passed to Seal below
// nonce needs to be NonceSize() bytes long for Seal/Open
nonce := make([]byte, gcm.NonceSize())
// populate nonce with secure random sequence
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return "", fmt.Errorf("error populating nonce: %s", err)
}
// Same nonce must be used for both encryption+decryption.
// Methods of ensuring this:
// - store nonce alongside encrypted data
// - prepend or append nonce to encrypted data
// Seal encrypts and authenticates plaintext, authenticates additional data (if any), appends nonce
ciphertext := gcm.Seal(nonce, nonce, []byte(plaintextStr), nil)
return string(ciphertext), nil
}
func decrypt(ciphertextStr, key string) (string, error) {
// Create AES block cipher from key
c, err := aes.NewCipher([]byte(key))
if err != nil {
return "", fmt.Errorf("error creating block cipher from key: %s", err)
}
// Wrap cipher block in GCM
gcm, err := cipher.NewGCM(c)
if err != nil {
return "", fmt.Errorf("error wrapping block cipher in GCM: %s", err)
}
// Decode ciphertext to bytes
ciphertext := []byte(ciphertextStr)
// Extract nonce from ciphertext
nonce := ciphertext[:gcm.NonceSize()]
ciphertextWithoutNonce := ciphertext[gcm.NonceSize():]
// Decrypt and authenticate ciphertext
plaintext, err := gcm.Open(nil, nonce, ciphertextWithoutNonce, nil)
if err != nil {
return "", fmt.Errorf("error decrypting/authenticating ciphertext: %s", err)
}
return string(plaintext), nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment