Symmetric key encryption uses the same key to encrypt and decrypt. It is mainly a block cipher with a block size of 128 bits. In this case we will use the CFB (Cipher Feedback) mode. Within CFB, we use a salt value, so that the IV (Initialisation Vector) value is used in the encryption process. In this case we will use Rust to implement 128-bit AES with CFB mode.
AES CFB in Rust |
Method
Normally we think of the encryption process as taking plain text and using an encryption key to create the cipher text, and then applying the same key with a decryption process. The problem with this type of process is that an error in the cipher text will cause problems in decrypting.
So we apply Cipher Feedback (CFB), which basically converts our cipher block into a bit stream, and we can this encrypt and decrypt each bit at a time. This method is defined as self-synchronising. For this we do not encrypt and decrypt, but we encrypt the IV (Initialisation Vector) or a previous cipher block with the key, and EX-OR that with the data (on the sending side) or with the cipher stream. After the first block of data, we use encrypt the previous cipher block (Figure 2), and on the receiver we do the same. So the process on either side is almost identical, apart from the data which is used, where the sender uses data blocks (such as 128-bit blocks) and the receiver uses the cipher stream to build the data blocks.
If you are interested, here is the process for CFB (where P are the data blocks and C are the re-built cipher blocks):
Figure 1: Cipher feedback (CFB)
With ECB we do not get variation in the cipher blocks, so in CFB we use the IV, so, along with the shared key, we must pass the IV value in order to decipher the cipher stream.
Figure 2: CFB process
Code
First we create a project with:
cargo new cfb
We then go into the cfb folder, and add the following to the cargo.toml file:
[package] name = "my-project" version = "0.1.0" authors = ["runner"] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] aes="0.7.5" hex="0.4.3" block-modes="0.8.1" hex-literal="0.3.3"
The toml file defines the crates we will using in the project. In this case we will use AES CFB with a 128-bit key and a static IV 128-bit value ("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"):
use aes::Aes128; use block_modes::{BlockMode, Cfb}; use block_modes::block_padding::Pkcs7; use hex_literal::hex; use std::str; use std::env; type Aes128ECfb = Cfb<Aes128, Pkcs7>; fn main() { let iv = hex!("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"); let mut message = String::from("Hello world!"); let mut mykey =String::from("000102030405060708090A0B0C0D0E0F"); let args: Vec<String> = env::args().collect(); if args.len() >1 { message = args[1].clone(); } if args.len() >2 { mykey = args[2].clone(); } println!("==128-bit AES CFB Mode=="); println!("Message: {}",message); println!("Key: {}",mykey); let plaintext=message.as_bytes(); let key = hex::decode(mykey).expect("Decoding failed"); let cipher = Aes128ECfb::new_from_slices(&key, &iv).unwrap(); let pos = plaintext.len(); let mut buffer = [0u8; 128]; buffer[..pos].copy_from_slice(plaintext); let ciphertext = cipher.encrypt(&mut buffer, pos).unwrap(); println!("\nCiphertext: {:?}",hex::encode(ciphertext)); let cipher = Aes128ECfb::new_from_slices(&key,&iv).unwrap(); let mut buf = ciphertext.to_vec(); let decrypted_ciphertext = cipher.decrypt(&mut buf).unwrap(); println!("\nCiphertext: {:?}",str::from_utf8(decrypted_ciphertext).unwrap()); }
Finally we simply build with:
cargo build
A sample run is:
==128-bit AES CFB Mode== Message: Hello World! Key: 000102030405060708090A0B0C0D0E0F Ciphertext: "2ec2ab845b726627e53dba263712a9a9" Ciphertext: "Hello World!"