Hello everyone, I am currently working on a project that is going to become the foundation for some bigger research on Rust's crypto landscape and I am currently facing an issue with Rust-crypto's AES implementation when using CBC as block mode.
I wrote benchmarks using criterion and its cycles-per-byte plugin for ECB, CBC, CTR and GCM to measure how they perform in terms of Cycles-per-Byte and I am getting very reasonable results for ECB, CTR and GCM. In fact the results for these three modes are exactly as they are supposed to be.
CBC however does not produce expected results and I do not understand how or why.
Here's the premise: All benchmarks are executed on an I7-8700k with 3,7 GHz core frequency and disabled turbo boost. According to Intels specifications this CPU uses an AES pipeline length of 4.
The correct amount of Cycles-per-Byte should therefore be 2.5cpb for CBC-128 encryption. This I was able to verify with a basic benchmark in C.
Now the problem: My CBC-128 benchmarks in Rust produce a result of 4.5cpb, so 2cbp above the "normal".
Could somebody take a look at my code?
Here's the content of aes-cbc.rs:
use aes::cipher::{BlockEncryptMut, KeyIvInit};
use aes::{Aes128, Aes192, Aes256};
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use criterion_cycles_per_byte::CyclesPerByte;
use RustCrypto_AES_Benchmarks as benches;
type Aes128CbcEnc = cbc::Encryptor<Aes128>;
type Aes192CbcEnc = cbc::Encryptor<Aes192>;
type Aes256CbcEnc = cbc::Encryptor<Aes256>;
pub const KB: usize = 1024;
fn bench(c: &mut Criterion<CyclesPerByte>) {
let mut group = c.benchmark_group("aes-cbc");
let mut cipher128 = Aes128CbcEnc::new(&Default::default(), &Default::default());
let mut cipher192 = Aes192CbcEnc::new(&Default::default(), &Default::default());
let mut cipher256 = Aes256CbcEnc::new(&Default::default(), &Default::default());
for size in &[KB, 2 * KB, 4 * KB, 8 * KB, 16 * KB] {
let mut buf = vec![Default::default(); *size / 16];
group.throughput(Throughput::Bytes(*size as u64));
group.bench_function(BenchmarkId::new("encrypt-128", size), |b| {
b.iter(|| cipher128.encrypt_blocks_mut(&mut buf));
});
group.bench_function(BenchmarkId::new("encrypt-192", size), |b| {
b.iter(|| cipher192.encrypt_blocks_mut(&mut buf));
});
group.bench_function(BenchmarkId::new("encrypt-256", size), |b| {
b.iter(|| cipher256.encrypt_blocks_mut(&mut buf));
});
}
group.finish();
}
criterion_group!(
name = benches;
config = Criterion::default().with_measurement(CyclesPerByte);
targets = bench
);
criterion_main!(benches);
The entire project can be found here: