Encrypting Data With AES GCM in Rust Using Openssl
In the previous post
we discussed encrypting and decrypting data with AES using the chaining technique
called Cipher Block Chaining. In this post we will use AES with
GCM Galois/counter mode,
another block cipher mode of operation.
Just like the previous post, we will not get into the details and the
implementation of GCM itself, we will use the rust APIs for it from openssl
library.
Before we begin
-
What is GCM?
-
It’s a mode of operation for symmetric key block ciphers.
-
Introduced in 2004.
-
Combines counter mode (CTR) for encryption with Galois mode for authentication, ensuring both data confidentiality and integrity.
-
-
How is it different from CBC?
-
GCMis anAuthenticated Encryption(AE) mode, meaning it provides both encryption for confidentiality and an authentication tag to ensure data integrity and authenticity. You need both the encryption key and the tag to successfully decrypt and verify the data. -
Especially for encrypting large datasets, because of its counter mode operation, encryption and authentication can be parallelised in the case of
GCM, which is especially beneficial for hardware implementations. On the other handCBCis sequential and can not be parallelised.
-
Implementation
To use AES GCM (from openssl) in a Rust project, the steps are similar to
CBC mode except for a couple of things.
-
Encryption
- Generate the
AES key(128, 192 or 256 bits). - Generate
IV/Nonce. - Generate the
AADAdditional authentication data (in simple terms, a password). - Use the
GCMmode while initializing theCipher. - Encrypt the plain text and Obtain the
tag.
let mut key = vec![0; 24]; let _ = rand_bytes(&mut key)?; println!("key: {:?}", key); // Generate an IV with 12 bytes of randomness let mut iv = vec![0; 12]; let _ = rand_bytes(&mut iv)?; println!("iv: {:?}", iv); // additional authentication data let aad = b"$3CuRE-D4T4"; // Buffer for the authentication tag let mut tag = [0u8; 16]; // initialize the cipher AES 192 bits gcm mode let cipher = Cipher::aes_192_gcm(); // encrypt the plaintext let ciphertext = encrypt_aead( cipher, &key, Some(&iv), aad, plaintext_input.as_bytes(), &mut tag, )?; println!("ciphertext: {:?}", ciphertext); - Generate the
-
Decryption
- Use the same
AES keyandIV/Nonce. - Use the
GCMmode while initializing theCipher. - Using the
Cipher,key,IVand thetagthat was obtained, decrypt theCiphertext.
// decrypt the ciphertext let plaintext_decrypted = decrypt_aead( cipher, &key, Some(&iv), aad, &ciphertext, &mut tag)?; - Use the same
The complete code is as follows.
Cargo.toml
[package]
name = "openssl_demo_aes_gcm"
version = "0.1.0"
edition = "2021"
[dependencies]
openssl = "0.10.68"
main.rs
use openssl::rand::rand_bytes;
use openssl::symm::{decrypt_aead, encrypt_aead, Cipher};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let plaintext_input = "this is plain text";
println!("plaintext input: {}", plaintext_input);
println!("plaintext_input bytes: {:?}", plaintext_input.as_bytes());
// Generate a 192-bit (24-byte) AES key using CSPRNG
// which is openssl::rand::rand_bytes
let mut key = vec![0; 24];
let _ = rand_bytes(&mut key)?;
println!("key: {:?}", key);
// Generate an IV with 12 bytes of randomness
let mut iv = vec![0; 12];
let _ = rand_bytes(&mut iv)?;
println!("iv: {:?}", iv);
let aad = b"$3CuRE-D4T4";
// Buffer for the authentication tag
let mut tag = [0u8; 16];
// initialize the cipher AES 192 bits gcm mode
let cipher = Cipher::aes_192_gcm();
// encrypt the plaintext
let ciphertext = encrypt_aead(
cipher,
&key,
Some(&iv),
aad,
plaintext_input.as_bytes(),
&mut tag,
)?;
println!("ciphertext: {:?}", ciphertext);
// decrypt the ciphertext
let plaintext_decrypted = decrypt_aead(
cipher,
&key,
Some(&iv),
aad,
&ciphertext,
&mut tag)?;
// print the decrypted data
println!("plaintext decrypted bytes: {:?}", plaintext_decrypted);
let plaintext_decrypted_str = String::from_utf8(plaintext_decrypted)?;
println!("plaintext_decrypted string: {}", plaintext_decrypted_str);
Ok(())
}
Output
$ cargo run --release
Finished release [optimized] target(s) in 0.01s
Running `target/release/openssl_demo_aes_gcm`
plaintext input: this is plain text
plaintext_input bytes: [116, 104, 105, 115, 32, 105, 115, 32, 112, 108, 97, 105, 110, 32, 116, 101, 120, 116]
key: [42, 88, 63, 171, 3, 67, 247, 133, 72, 132, 163, 209, 95, 38, 32, 2, 209, 104, 250, 42, 159, 168, 0, 175]
iv: [160, 137, 110, 196, 97, 138, 164, 245, 6, 114, 155, 242]
ciphertext: [13, 51, 148, 136, 40, 84, 102, 61, 148, 109, 34, 179, 45, 29, 238, 184, 134, 218]
plaintext decrypted bytes: [116, 104, 105, 115, 32, 105, 115, 32, 112, 108, 97, 105, 110, 32, 116, 101, 120, 116]
plaintext_decrypted string: this is plain text
Points to Remember
-
There is a slight difference between
IVthat is used inCBCmode and theIV/Nonceused in theGCMmode- For
GCM, a nonce must never be repeated for the sameAES key. - For
CBC, the requirement is that the Initialization Vector must be unpredictable in advance to an adversary.
- For
-
IV/Noncesize:NIST's Recommendationstates:For IVs, it is recommended that implementations restrict support to the length of 96 bits, to promote interoperability, efficiency, and simplicity of design.- OpenSSL supports IVs of any length (defaulting to 96 bits), but non-standard lengths require additional processing.
- If the IV is not 96 bits, OpenSSL uses
GHASHto derive a 96-bit IV internally, which adds computational overhead.
-
In real-world scenarios, usually the
AADis passed as metadata such as header fields in anHTTPrequest. It is not the same thing as a password as mentioned above.