GO Lang AES Encrypt/Decrypt

From time to time there is a need to encrypt/decrypt something

Based on previous notes here is small helper

package main

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/base64"
	"errors"
	"io"
)

func main() {
	text := "Hello, World!"

	cryptographer, err := New()
	if err != nil {
		panic(err)
	}

	encrypted, err := cryptographer.Encrypt(text)
	if err != nil {
		panic(err)
	}

	decrypted, err := cryptographer.Decrypt(encrypted)
	if err != nil {
		panic(err)
	}

	if text != decrypted {
		panic("decrypted text does not match original")
	}

	println("Original:", text)
	println("Encrypted:", encrypted)
	println("Decrypted:", decrypted)
}

type Cryptographer struct {
	block cipher.Block
}

// func NewWithKey(secret []byte) (*Cryptographer, error) {
// 	if len(secret) != 32 {
// 		return nil, errors.New("cryptographer: invalid key size")
// 	}

// 	block, err := aes.NewCipher(secret)
// 	if err != nil {
// 		return nil, err
// 	}

// 	return &Cryptographer{block: block}, nil
// }

func New() (*Cryptographer, error) {
	secret := make([]byte, 32)
	_, err := rand.Read(secret)
	if err != nil {
		return nil, err
	}

	block, err := aes.NewCipher(secret)
	if err != nil {
		return nil, err
	}

	return &Cryptographer{block: block}, nil
}

func (c *Cryptographer) Encrypt(input string) (string, error) {
	if input == "" {
		return "", errors.New("cryptographer: empty input")
	}

	bytes := []byte(input)
	output := make([]byte, aes.BlockSize+len(bytes))

	iv := output[:aes.BlockSize]           // get first N (aes.BlockSize) bytes
	_, err := io.ReadFull(rand.Reader, iv) // fill the iv with random bytes
	if err != nil {
		return "", err
	}

	// encrypt
	stream := cipher.NewCTR(c.block, iv)
	stream.XORKeyStream(output[aes.BlockSize:], bytes)

	// encode
	return base64.URLEncoding.EncodeToString(output), nil
}

func (c *Cryptographer) Decrypt(input string) (string, error) {
	if input == "" {
		return "", errors.New("cryptographer: empty input")
	}

	// decode
	bytes, err := base64.URLEncoding.DecodeString(input)
	if err != nil {
		return "", err
	}

	if len(bytes) < aes.BlockSize {
		return "", errors.New("cryptographer: ciphertext too short")
	}

	// split bytes into iv and encrypted
	iv := bytes[:aes.BlockSize]
	encrypted := bytes[aes.BlockSize:]

	output := make([]byte, len(encrypted))

	// decrypt
	stream := cipher.NewCTR(c.block, iv)
	stream.XORKeyStream(output, encrypted)

	return string(output), nil
}

Notes:

  • in my case, it is used to encrypt/decrypt short living cookies, so by intent, i want key to be generated each time - aka it will rotate eventually and there is no need to hide/steal it
  • in case of persistance is required just pass key from outside
  • for AES key must be 32 bytes and IV - 16 bytes
  • to simplify things we are writing/reading IV first, and then dealing with rest of the bytes, otherwise they should be passed separate