Skip to content

Commit 5b6cc8e

Browse files
committed
feat: add utility for benchmarking kdf functions
1 parent 336ece7 commit 5b6cc8e

5 files changed

Lines changed: 112 additions & 14 deletions

File tree

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,5 +130,10 @@ required-features = ["utilities", "save_kdbx4", "challenge_response"]
130130
name = "kp-yk-recover"
131131
required-features = ["utilities", "save_kdbx4", "challenge_response"]
132132

133+
[[bin]]
134+
name = "kp-benchmark-kdf"
135+
required-features = ["utilities"]
136+
133137
[lints.rust]
134138
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin_include)'] }
139+

src/bin/kp-benchmark-kdf.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use std::time::Duration;
2+
3+
use clap::{Parser, ValueEnum};
4+
use keepass::crypt::kdf::{AesKdf, Argon2Kdf, Kdf};
5+
6+
#[derive(Parser, Debug)]
7+
#[command(version, about, long_about = None)]
8+
struct Args {
9+
/// KDF to benchmark
10+
#[arg(value_enum)]
11+
kdf: KdfChoice,
12+
13+
/// Duration for each KDF in milliseconds
14+
#[arg(short, long, default_value_t = 1000)]
15+
msecs: u64,
16+
}
17+
18+
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
19+
enum KdfChoice {
20+
Aes,
21+
Argon2,
22+
}
23+
24+
fn main() {
25+
let args = Args::parse();
26+
let duration = Duration::from_millis(args.msecs);
27+
28+
match args.kdf {
29+
KdfChoice::Aes => {
30+
let kdf = AesKdf {
31+
seed: vec![0; 32],
32+
rounds: 100_000,
33+
};
34+
println!("Benchmarking AES KDF for {} ms...", args.msecs);
35+
let rounds = kdf.benchmark(duration);
36+
println!("AES KDF: {} rounds in {} ms", rounds, args.msecs);
37+
}
38+
KdfChoice::Argon2 => {
39+
let kdf = Argon2Kdf {
40+
salt: vec![0; 32],
41+
parallelism: 1,
42+
memory: 1024 * 1024, // 1 MiB
43+
iterations: 1,
44+
variant: argon2::Variant::Argon2id,
45+
version: argon2::Version::Version13,
46+
};
47+
println!(
48+
"Benchmarking Argon2id KDF with {} KiB memory and parallelism {} for {} ms...",
49+
kdf.memory / 1024,
50+
kdf.parallelism,
51+
args.msecs
52+
);
53+
let iterations = kdf.benchmark(duration);
54+
println!("Argon2id KDF: {} iterations in {} ms", iterations, args.msecs);
55+
}
56+
}
57+
}

src/crypt/kdf.rs

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::time::Duration;
2+
13
use aes::Aes256;
24
use cipher::{
35
generic_array::{typenum::U32, GenericArray},
@@ -7,11 +9,13 @@ use sha2::{Digest, Sha256};
79

810
use super::CryptographyError;
911

10-
pub(crate) trait Kdf {
12+
pub trait Kdf {
1113
fn transform_key(
1214
&self,
1315
composite_key: &GenericArray<u8, U32>,
1416
) -> Result<GenericArray<u8, U32>, CryptographyError>;
17+
18+
fn benchmark(&self, duration: Duration) -> usize;
1519
}
1620

1721
pub struct AesKdf {
@@ -39,6 +43,23 @@ impl Kdf for AesKdf {
3943

4044
Ok(digest.finalize())
4145
}
46+
47+
fn benchmark(&self, duration: Duration) -> usize {
48+
let composite_key: GenericArray<u8, U32> = GenericArray::clone_from_slice(&[0; 32]);
49+
let mut rounds = 0;
50+
51+
let cipher = Aes256::new(&GenericArray::clone_from_slice(&self.seed));
52+
let mut block1 = GenericArray::clone_from_slice(&composite_key[..16]);
53+
let mut block2 = GenericArray::clone_from_slice(&composite_key[16..]);
54+
55+
let start_time = std::time::Instant::now();
56+
while start_time.elapsed() < duration {
57+
cipher.encrypt_block(&mut block1);
58+
cipher.encrypt_block(&mut block2);
59+
rounds = rounds + 1;
60+
}
61+
rounds
62+
}
4263
}
4364

4465
pub struct Argon2Kdf {
@@ -71,16 +92,31 @@ impl Kdf for Argon2Kdf {
7192

7293
Ok(*GenericArray::from_slice(&key))
7394
}
74-
}
7595

76-
/*
77-
pub(crate) fn transform_key_argon2(
78-
composite_key: &GenericArray<u8, U32>,
79-
) -> Result<GenericArray<u8, U32>> {
80-
let version = match version {
81-
0x10 => argon2::Version::Version10,
82-
0x13 => argon2::Version::Version13,
83-
_ => return Err(DatabaseIntegrityError::InvalidKDFVersion { version: version }.into()),
84-
};
96+
fn benchmark(&self, duration: Duration) -> usize {
97+
let composite_key: GenericArray<u8, U32> = GenericArray::clone_from_slice(&[0; 32]);
98+
99+
let config = argon2::Config {
100+
thread_mode: argon2::ThreadMode::Parallel,
101+
ad: &[],
102+
hash_length: 32,
103+
lanes: self.parallelism,
104+
mem_cost: (self.memory / 1024) as u32,
105+
secret: &[],
106+
time_cost: 1, // benchmark for one iteration
107+
variant: self.variant,
108+
version: self.version,
109+
};
110+
111+
let start_time = std::time::Instant::now();
112+
let _ = argon2::hash_raw(&composite_key, &self.salt, &config);
113+
let elapsed = start_time.elapsed();
114+
115+
if elapsed.is_zero() {
116+
// Should not happen, but to be safe
117+
return 0;
118+
}
119+
120+
(duration.as_nanos() / elapsed.as_nanos()) as usize
121+
}
85122
}
86-
*/

src/crypt/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use sha2::{Digest, Sha256, Sha512};
1313
use crate::error::CryptographyError;
1414

1515
pub(crate) mod ciphers;
16-
pub(crate) mod kdf;
16+
pub mod kdf;
1717

1818
pub(crate) fn calculate_hmac(
1919
elements: &[&[u8]],

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
mod compression;
55
pub mod config;
6-
pub(crate) mod crypt;
6+
pub mod crypt;
77
pub mod db;
88
pub mod error;
99
pub(crate) mod format;

0 commit comments

Comments
 (0)