How to Encrypt and Decrypt a String Using AES CBC in Go

Created
Modified

Ehrsam, Meyer, Smith and Tuchman invented the cipher block chaining (CBC) mode of operation in 1976. In CBC mode, each block of plaintext is XORed with the previous ciphertext block before being encrypted.

Using NewCBCEncrypter Function

The cipher.NewCBCEncrypter(cipher Block) function returns returns a BlockMode which encrypts in cipher block chaining mode, using the given Block. The length of iv must be the same as the Block's block size.

The following example should cover whatever you are trying to do:

package main

import (
  "bytes"
  "crypto/aes"
  "crypto/cipher"
  "crypto/rand"
  "encoding/hex"
  "errors"
  "fmt"
  "io"
)

func PKCS7Padding(ciphertext []byte, blockSize, after int) []byte {
  padding := (blockSize - len(ciphertext)%blockSize)
  padtext := bytes.Repeat([]byte{byte(padding)}, padding)
  return append(ciphertext, padtext...)
}

func PKCS7Unpad(b []byte, blocksize int) ([]byte, error) {
  if blocksize <= 0 {
    return nil, errors.New("invalid blocksize")
  }
  if len(b) == 0 {
    return nil, errors.New("unpad error")
  }
  if len(b)%blocksize != 0 {
    return nil, errors.New("unpad error")
  }
  c := b[len(b)-1]
  n := int(c)
  if n == 0 || n > len(b) {
    return nil, errors.New("unpad error")
  }
  for i := 0; i < n; i++ {
    if b[len(b)-n+i] != c {
      return nil, errors.New("unpad error")
    }
  }
  return b[:len(b)-n], nil
}

func AesCBCEncrypt(key string, text string) (string, error) {

  plaintext := PKCS7Padding([]byte(text), aes.BlockSize, len(text))

  // CBC mode works on blocks so plaintexts may need to be padded to the
  // next whole block.
  if len(plaintext)%aes.BlockSize != 0 {
    return "", errors.New("plaintext is not a multiple of the block size")
  }

  block, err := aes.NewCipher([]byte(key))
  if err != nil {
    return "", err
  }

  // The IV needs to be unique, but not secure. Therefore it's common to
  // include it at the beginning of the ciphertext.
  ciphertext := make([]byte, aes.BlockSize+len(plaintext))
  iv := ciphertext[:aes.BlockSize]
  if _, err := io.ReadFull(rand.Reader, iv); err != nil {
    panic(err)
  }

  mode := cipher.NewCBCEncrypter(block, iv)
  mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)

  // It's important to remember that ciphertexts must be authenticated
  // (i.e. by using crypto/hmac) as well as being encrypted in order to
  // be secure.

  return hex.EncodeToString(ciphertext), nil
}

func AesCBCDecrypt(key string, text string) (string, error) {

  ciphertext, err := hex.DecodeString(text)
  if err != nil {
    return "", err
  }

  block, err := aes.NewCipher([]byte(key))
  if err != nil {
    return "", err
  }

  if len(ciphertext) < aes.BlockSize {
    return "", errors.New("ciphertext too short")
  }
  iv := ciphertext[:aes.BlockSize]
  ciphertext = ciphertext[aes.BlockSize:]

  // CBC mode always works in whole blocks.
  if len(ciphertext)%aes.BlockSize != 0 {
    return "", errors.New("ciphertext is not a multiple of the block size")
  }

  mode := cipher.NewCBCDecrypter(block, iv)
  mode.CryptBlocks(ciphertext, ciphertext)

  // If the original plaintext lengths are not a multiple of the block
  // size, padding would have to be added when encrypting, which would be
  // removed at this point.
  ciphertext, _ = PKCS7Unpad(ciphertext, aes.BlockSize)

  return string(ciphertext[:]), nil
}

func main() {
  s := "Hello"
  key := "zb0SLh88rdSHswjcgcC6949ZUuopGXTt"

  ciphertext, _ := AesCBCEncrypt(key, s)
  fmt.Println(ciphertext)

  plaintext, _ := AesCBCDecrypt(key, ciphertext)
  fmt.Printf("Decrypt:: %s\n", plaintext)
}
22fb92560a7a1e7a9ba881943d5e078c517343133049b8ff4b5492a7b2276b3e
Decrypt:: Hello

Related Tags

#encrypt# #aes# #cbc#