Skip to content

Instantly share code, notes, and snippets.

@JonasDoe
Last active November 13, 2022 09:24
Show Gist options
  • Save JonasDoe/48cda246b080d2fe3b55736c8bd1b998 to your computer and use it in GitHub Desktop.
Save JonasDoe/48cda246b080d2fe3b55736c8bd1b998 to your computer and use it in GitHub Desktop.
Full example of how to use AES with CBC mode and PKCS7 padding
package function
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"fmt"
"io"
)
type EncryptionCommand struct {
ToTransform string // to be encrypted or decrypted
Encrypt bool // decrypt, if false
}
type TransformationOp string
const (
Encrypt TransformationOp = "encrypt"
Decrypt TransformationOp = "decrypt"
)
func Transform(toTransform string, operation TransformationOp, key string) (transformed string, err error) {
blockCipher, err := aes.NewCipher([]byte(key))
if err != nil {
return transformed, fmt.Errorf("cannot create new aes cipher: %s", err)
}
switch operation {
case Encrypt:
ciphertext := pkcs7Pad([]byte(toTransform))
cbc, err := encryptCBC(blockCipher, ciphertext)
if err != nil {
return transformed, fmt.Errorf("cannot encrypt text: %s", err)
}
return base64.StdEncoding.EncodeToString(cbc), nil
case Decrypt:
toTransformDecoded, err := base64.StdEncoding.DecodeString(toTransform)
if err != nil {
return transformed, fmt.Errorf("cannot decode string to transform: %s", err)
}
plaintext, err := decryptCBC(blockCipher, toTransformDecoded)
if err != nil {
return transformed, fmt.Errorf("cannot decrypt text: %s", err)
}
unpadded, err := pkcs7UnPad(plaintext)
if err != nil {
return transformed, fmt.Errorf("cannot unpad decryption result: %s", err)
}
return string(unpadded), nil
default:
return transformed, fmt.Errorf("unknown transformation operation: %q", operation)
}
}
// pkcs7UnPad removes any padding from the given plain text.
// Internally it expects the padding character itself to express number of padded characters.
// https://gist.github.com/nanmu42/b838acc10d393bc51cb861128ce7f89c
func pkcs7UnPad(toUnpad []byte) (withoutPadding []byte, err error) {
blockSize := aes.BlockSize
length := len(toUnpad)
if length == 0 {
return nil, errors.New("pkcs7: data is empty")
}
if length%blockSize != 0 {
return nil, errors.New("pkcs7: data is not block-aligned")
}
padLen := int(toUnpad[length-1])
ref := bytes.Repeat([]byte{byte(padLen)}, padLen)
if padLen > blockSize || padLen == 0 || !bytes.HasSuffix(toUnpad, ref) {
return nil, errors.New("pkcs7: invalid padding")
}
return toUnpad[:length-padLen], nil
}
// pkcs7Pad adds padding to the given plain text.
// Internally applies the number of required padding characters as padding character itself.
// https://gist.github.com/nanmu42/b838acc10d393bc51cb861128ce7f89c
func pkcs7Pad(toPad []byte) (withPadding []byte) {
plaintext := toPad
padLen := aes.BlockSize - len(plaintext)%aes.BlockSize
padding := bytes.Repeat([]byte{byte(padLen)}, padLen)
return append(plaintext, padding...)
}
// encryptCBC encrypts the given (padded) plaintext in Cipher Block Chaining mode.
// https://gist.github.com/locked/b066aa1ddeb2b28e855e
func encryptCBC(block cipher.Block, plaintext []byte) (ciphertext []byte, err error) {
if len(plaintext)%aes.BlockSize != 0 {
return ciphertext, errors.New("plaintext is not a multiple of the block size")
}
ciphertext = make([]byte, aes.BlockSize+len(plaintext))
// the first 16 Bytes of the whole ciphertest is where we put the iv
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return ciphertext, fmt.Errorf("cannot read random iv: %s", err)
}
cbc := cipher.NewCBCEncrypter(block, iv)
// the encrypted data will be written right after the iv, so we start writing at position 16
cbc.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)
return ciphertext, nil
}
// encryptCBC decrypts the given encrypted (and padded) text in Cipher Block Chaining mode.
// https://gist.github.com/locked/b066aa1ddeb2b28e855e
func decryptCBC(block cipher.Block, ciphertext []byte) (plaintext []byte, err error) {
if len(ciphertext) < aes.BlockSize {
return plaintext, errors.New("ciphertext too short")
}
// Since we put the iv at the start of the ciphertext on encryption, we read it from here now
iv := ciphertext[:aes.BlockSize]
// ... and remove it from the ciphertext to be decrypted
ciphertext = ciphertext[aes.BlockSize:]
cbc := cipher.NewCBCDecrypter(block, iv)
cbc.CryptBlocks(ciphertext, ciphertext)
return ciphertext, nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment