Last active
November 13, 2022 09:24
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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