With AES, we can have a block cipher mode, a stream cipher mode, and AEAD (Authenicated Encryption With Additional Data). In this case we will implement GCM (AEAD), CFB (Stream) and CBC (Block), and will use PBKDF2 to generate an encryption key of a given size (128-bit, 192-bit and 256-bit).
AES Modes: GCM (AEAD), CFB (Stream) and CBC (Block) |
Outline
The workhorse of the cybersecurity industry is AES (Advanced Encryption Standard) and which is used to encrypt and decrypt data. The method comes from the winner of a NIST competition in 2001, and was previously known as the Rijndael (“Rain Dahl”) cipher.
AES is a symmetric key method, and where Bob and Alice have the same encryption key. In the following, Bob and Alice share an encryption key, and where Bob converts his plaintext into ciphertext, and then Alice converts the ciphertext back into plaintext using a shared secret key:
The problem with this setup is that the same plaintext will always result in the same ciphertext, so we typically add salt into the encryption process. We also need a way for Bob and Alice to generate the same secret key. This is either typically done through a key exchange method (such as with the Diffie-Hellman method) or by a KDF (Key Derivation Function). One of the most popular KDFs is PBKDF2 and which allows a password to be converted into an encryption key of a defined size:
The size of the key is typically either 128 bits or 256 bits. AES (Advanced Encryption Standard) can be applied into three modes:
- Block cipher. This can be implemented with CBC (Cipher Block Chaining) and which implements a block cipher. In the case of AES, the block size will be 16 bytes, and thus the ciphertext will be a multiple of the block size. As we use a block cipher, we need to pad the data for the cipher process, and then unpad the decrypted data. The most common method is PKCS7, and which pads with the value which equals the number of padding bytes.
- AEAD (Authenticated Encryption with Additional Data). This can be implemented with GCM (Galois Counter Mode) and which implements with a stream cipher with the addition of additional data. This additional data can include something related to the ciphertext, such as with a session ID or TCP port. There is no padding required for the plaintext text, and thus the ciphertext will have the same length as the plaintext.
- Stream cipher. This can be implemented with CFB (Cipher FeedBack) and which implements a stream cipher. As we use a stream cipher, we do not need to pad the data, and where the ciphertext size will be the same as the plaintext. Overall we use a simple XOR operation between the plaintext stream and a key stream (derived from the main encryption key). The decryption is performed by just XOR’ing the ciphertext stream with the same key stream.
Code
Here is the code:
package main import ( "crypto/aes" "crypto/cipher" "crypto/rand" "crypto/sha256" "fmt" "io" "os" "strconv" "strings" "github.com/enceve/crypto/pad" "golang.org/x/crypto/pbkdf2" ) func getSalt(n int) []byte { nonce := make([]byte, n) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { panic(err.Error()) } return (nonce) } func main() { msg := "hello" passwd := "qwerty" mode := "cbc" size := 16 argCount := len(os.Args[1:]) if argCount > 0 { msg = os.Args[1] } if argCount > 1 { passwd = os.Args[2] } if argCount > 2 { mode = os.Args[3] } if argCount > 3 { size, _ = strconv.Atoi(os.Args[4]) } pwsalt := getSalt(12) // 96 bits for nonce/IV key := pbkdf2.Key([]byte(passwd), pwsalt, 10000, size, sha256.New) block, _ := aes.NewCipher(key) var salt []byte var plain []byte var ciphertext []byte plaintext := []byte(msg) if mode == "gcm" { // AEAD salt = getSalt(12) aesgcm, _ := cipher.NewGCM(block) ciphertext = aesgcm.Seal(nil, salt, plaintext, nil) plain, _ = aesgcm.Open(nil, salt, ciphertext, nil) } else if mode == "cbc" { // Block cipher plain = make([]byte, (len(plaintext)/16+1)*aes.BlockSize) ciphertext = make([]byte, (len(plaintext)/16+1)*aes.BlockSize) salt = getSalt(16) pkcs7 := pad.NewPKCS7(aes.BlockSize) pad1 := pkcs7.Pad(plaintext) blk := cipher.NewCBCEncrypter(block, salt) blk.CryptBlocks(ciphertext, pad1) blk = cipher.NewCBCDecrypter(block, salt) blk.CryptBlocks(plain, ciphertext) plain, _ = pkcs7.Unpad(plain) } else if mode == "cfb" { // Stream cipher salt = getSalt(aes.BlockSize) plain = make([]byte, len(plaintext)) ciphertext = make([]byte, len(plaintext)) stream := cipher.NewCFBEncrypter(block, salt) stream.XORKeyStream(ciphertext, plaintext) stream = cipher.NewCFBDecrypter(block, salt) stream.XORKeyStream(plain, ciphertext) } fmt.Printf("Mode:\t\t%s\n", strings.ToUpper(mode)) fmt.Printf("Key size:\t%d bits\n", size*8) fmt.Printf("Message:\t%s\n", msg) fmt.Printf("Password:\t%s\n", passwd) fmt.Printf("Password Salt:\t%x\n", pwsalt) fmt.Printf("\nKey:\t\t%x\n", key) fmt.Printf("\nCipher:\t\t%x\n", ciphertext) fmt.Printf("Salt:\t\t%x\n", salt) fmt.Printf("\nDecrypted:\t%s\n", plain) }
A sample run for AES GCM shows that the ciphertext size is the same as the plaintext size:
Mode: GCM Key size: 128 bits Message: The quick brown fox jumps over the lazy dog Password: Qwerty123 Password Salt: 1ef49a22fb2bc0eb23d8592b Key: f6a8bfd068df313f9238aa9dbc57c495 Cipher: 099149a5cfc01bf0faf5f20b9f1cf409ca5b29ee04743dc5db270d0bff27ff455a2c46e22d3c1321a7eab98fb67a17dc9461ba1f9b399e7863aad2 Salt: d57657896a98ab34148086ba Decrypted: The quick brown fox jumps over the lazy dog
For CBC we have a block cipher, and thus the ciphertext is a multiple of the block size (16 bytes):
Mode: CBC Key size: 128 bits Message: The quick brown fox jumps over the lazy dog Password: Qwerty123 Password Salt: bf283a43f50c3af4d201e9d8 Key: e9eeb0e6f3d4bcd707ed13788fdb5226 Cipher: aae9754f7a662c267338451be65bc021558e0e33e1ef9b9001dd98fff5a95963cf6f3ce0db4dd8da80392cc81f6443bd Salt: 29bf216555e7e58ba60ca29569bb618e Decrypted: The quick brown fox jumps over the lazy dog
For CFB we see that the ciphertext is the same length as the plaintext:
Mode: CFB Key size: 128 bits Message: The quick brown fox jumps over the lazy dog Password: Qwerty123 Password Salt: 8d993b9baed83ff3be546952 Key: afe68502e4587df339c6b6739ecbcda1 Cipher: 21215fdb73b086b70f9e25f8428c10b11ae64a0cf9accde173ab2043e439394fd7df28d06cf0049b8423f5 Salt: 2f8e3f87d3f271d61ed4c2d9388deef5 Decrypted: The quick brown fox jumps over the lazy dog