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?
-
GCM
is 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 handCBC
is 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
AAD
Additional authentication data (in simple terms, a password). - Use the
GCM
mode 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 key
andIV
/Nonce
. - Use the
GCM
mode while initializing theCipher
. - Using the
Cipher
,key
,IV
and thetag
that 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
IV
that is used inCBC
mode and theIV
/Nonce
used in theGCM
mode- 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
/Nonce
size:NIST's Recommendation
states: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
GHASH
to derive a 96-bit IV internally, which adds computational overhead.
-
In real-world scenarios, usually the
AAD
is passed as metadata such as header fields in anHTTP
request. It is not the same thing as a password as mentioned above.