Monday, February 21, 2022

Symmetric Encrypt and Decrypt in Golang

 



In this post we will review AES-CBC symmetric encryption in Golang.


We start by key generation.



import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"radware.com/euda/commons/global/encryption/padding"
)

const keySizeBytes = 32

type EncryptedData struct {
Cipher string `json:"cipher"`
Iv string `json:"iv"`
}

type Key struct {
key []byte
}

func GenerateKey() (*Key, error) {
key := make([]byte, keySizeBytes)
_, err := rand.Read(key)
if err != nil {
return nil, fmt.Errorf("read failed: %v", err)
}
k := Key{key: key}
return &k, nil
}



The key can be easily exported and imported.


func LoadKey(keyBase64 string) (*Key, error) {
bytes, err := base64.StdEncoding.DecodeString(keyBase64)
if err != nil {
return nil, fmt.Errorf("base64 decode failed: %v", err)
}
k := Key{key: bytes}
return &k, nil
}

func (k *Key) ExportKey() string {
return base64.StdEncoding.EncodeToString(k.key)
}


And finally we can encrypt and decrypt using the key.



func (k *Key) Encrypt(clearText string) (string, error) {
iv := make([]byte, aes.BlockSize)
_, err := rand.Read(iv)
if err != nil {
return "", fmt.Errorf("read failed: %v", err)
}

aesCipher, err := aes.NewCipher(k.key)
if err != nil {
return "", fmt.Errorf("create cipher failed: %v", err)
}

cbc := cipher.NewCBCEncrypter(aesCipher, iv)

clearBytes := []byte(clearText)
clearBytesPadded, err := padding.Pkcs7Pad(clearBytes, aes.BlockSize)
if err != nil {
return "", fmt.Errorf("padding failed: %v", err)
}

cipherBytes := make([]byte, len(clearBytesPadded))

cbc.CryptBlocks(cipherBytes, clearBytesPadded)
data := EncryptedData{
Cipher: base64.StdEncoding.EncodeToString(cipherBytes),
Iv: base64.StdEncoding.EncodeToString(iv),
}

jsonBytes, err := json.Marshal(data)
if err != nil {
return "", fmt.Errorf("marshal failed: %v", err)
}

return string(jsonBytes), nil
}

func (k *Key) Decrypt(jsonData string) (string, error) {
var data EncryptedData
err := json.Unmarshal([]byte(jsonData), &data)
if err != nil {
return "", fmt.Errorf("unmarshal failed: %v", err)
}

iv, err := base64.StdEncoding.DecodeString(data.Iv)
if err != nil {
return "", fmt.Errorf("decode iv failed: %v", err)
}

cipherBytes, err := base64.StdEncoding.DecodeString(data.Cipher)
if err != nil {
return "", fmt.Errorf("decode cipher failed: %v", err)
}

aesCipher, err := aes.NewCipher(k.key)
if err != nil {
return "", fmt.Errorf("create cipher failed: %v", err)
}
cbc := cipher.NewCBCDecrypter(aesCipher, iv)

paddedClearTextBytes := make([]byte, len(cipherBytes))
cbc.CryptBlocks(paddedClearTextBytes, cipherBytes)

clearTextBytes, err := padding.Pkcs7Unpad(paddedClearTextBytes, aes.BlockSize)
if err != nil {
return "", fmt.Errorf("un-padding failed: %v", err)
}

clearText := string(clearTextBytes)
return clearText, nil
}



Notice that the encryption result includes Initialization Vector (IV), which is a random buffer that is used in the encryption. The IV is returned from the encrypt function unchanged. The decrypt function uses both the cipher result and the IV along with the AES key to do its work. To simplify the usage of both IV and cipher, we use a JSON format.



We can test our code using the following:



func Test(t *testing.T) {
key, err := GenerateKey()
if err != nil {
t.Fatal(err)
}

encrypted, err := key.Encrypt("123456789012345")
if err != nil {
t.Fatal(err)
}

t.Logf("encrypted is %v", encrypted)

exportedKey := key.ExportKey()
t.Logf("exported key is %v", exportedKey)

importedKey, err := LoadKey(exportedKey)
if err != nil {
t.Fatal(err)
}

decrypted, err := importedKey.Decrypt(encrypted)
if err != nil {
t.Fatal(err)
}
t.Logf("decrypted is %v", decrypted)
}




Final Note


This post is part of a series of posts about encryption. The full posts list is below.




No comments:

Post a Comment