Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Argon2id #162

Merged
merged 2 commits into from
Apr 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 65 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,12 +192,24 @@ const KDF_ROUNDS: &str = "R";
pub enum KdfConfig {
/// Derive keys with repeated AES encryption
Aes { rounds: u64 },
/// Derive keys with Argon2
/// Derive keys with Argon2d
Argon2 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is actually the 2d variant, should we not rename the struct as well?
Doesn't have to happen in this PR, it could happen before 1.x since the API is changing quite a lot at the moment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I definitely can do it.

If we break the API, should i add support for Aigon2i as well? (In two seperate PRs?)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we break the API, should i add support for Aigon2i as well?

If it's offered in applications that use KDBX, then I feel like we will get the request for it at some point. Might as well break the API before we reach 1.0.

In two seperate PRs?

Yes please, this one is ready to merge.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tlercher FYI created #166 to track the Argon2i feature work.

iterations: u64,
memory: u64,
parallelism: u32,

#[cfg_attr(
feature = "serialization",
serde(serialize_with = "serialize_argon2_version")
)]
version: argon2::Version,
},
/// Derive keys with Argon2id
Argon2id {
iterations: u64,
memory: u64,
parallelism: u32,

#[cfg_attr(
feature = "serialization",
serde(serialize_with = "serialize_argon2_version")
Expand All @@ -219,6 +231,7 @@ impl KdfConfig {
match self {
KdfConfig::Aes { .. } => 32,
KdfConfig::Argon2 { .. } => 32,
KdfConfig::Argon2id { .. } => 32,
}
}

Expand Down Expand Up @@ -253,6 +266,20 @@ impl KdfConfig {
iterations: *iterations,
parallelism: *parallelism,
version: *version,
variant: argon2::Variant::Argon2d,
}),
KdfConfig::Argon2id {
memory,
iterations,
parallelism,
version,
} => Box::new(kdf::Argon2Kdf {
memory: *memory,
salt: seed.to_vec(),
iterations: *iterations,
parallelism: *parallelism,
version: *version,
variant: argon2::Variant::Argon2id,
}),
}
}
Expand All @@ -279,6 +306,19 @@ impl KdfConfig {
vd.set(KDF_PARALLELISM, *parallelism);
vd.set(KDF_VERSION, version.as_u32());
}
KdfConfig::Argon2id {
memory,
iterations,
parallelism,
version,
} => {
vd.set(KDF_ID, KDF_ARGON2ID.to_vec());
vd.set(KDF_MEMORY, *memory);
vd.set(KDF_SALT, seed.to_vec());
vd.set(KDF_ITERATIONS, *iterations);
vd.set(KDF_PARALLELISM, *parallelism);
vd.set(KDF_VERSION, version.as_u32());
}
}

vd
Expand All @@ -288,14 +328,37 @@ impl KdfConfig {
const KDF_AES_KDBX3: [u8; 16] = hex!("c9d9f39a628a4460bf740d08c18a4fea");
const KDF_AES_KDBX4: [u8; 16] = hex!("7c02bb8279a74ac0927d114a00648238");
const KDF_ARGON2: [u8; 16] = hex!("ef636ddf8c29444b91f7a9a403e30a0c");
const KDF_ARGON2ID: [u8; 16] = hex!("9e298b1956db4773b23dfc3ec6f0a1e6");

impl TryFrom<VariantDictionary> for (KdfConfig, Vec<u8>) {
type Error = KdfConfigError;

fn try_from(vd: VariantDictionary) -> Result<(KdfConfig, Vec<u8>), Self::Error> {
let uuid = vd.get::<Vec<u8>>(KDF_ID)?;

if uuid == &KDF_ARGON2 {
if uuid == &KDF_ARGON2ID {
let memory: u64 = *vd.get(KDF_MEMORY)?;
let salt: Vec<u8> = vd.get::<Vec<u8>>(KDF_SALT)?.clone();
let iterations: u64 = *vd.get(KDF_ITERATIONS)?;
let parallelism: u32 = *vd.get(KDF_PARALLELISM)?;
let version: u32 = *vd.get(KDF_VERSION)?;

let version = match version {
0x10 => argon2::Version::Version10,
0x13 => argon2::Version::Version13,
_ => return Err(KdfConfigError::InvalidKDFVersion { version }),
};

Ok((
KdfConfig::Argon2id {
memory,
iterations,
parallelism,
version,
},
salt,
))
} else if uuid == &KDF_ARGON2 {
let memory: u64 = *vd.get(KDF_MEMORY)?;
let salt: Vec<u8> = vd.get::<Vec<u8>>(KDF_SALT)?.clone();
let iterations: u64 = *vd.get(KDF_ITERATIONS)?;
Expand Down
3 changes: 2 additions & 1 deletion src/crypt/kdf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ pub struct Argon2Kdf {
pub iterations: u64,
pub parallelism: u32,
pub version: argon2::Version,
pub variant: argon2::Variant,
}

impl Kdf for Argon2Kdf {
Expand All @@ -62,7 +63,7 @@ impl Kdf for Argon2Kdf {
secret: &[],
thread_mode: argon2::ThreadMode::default(),
time_cost: self.iterations as u32,
variant: argon2::Variant::Argon2d,
variant: self.variant,
version: self.version,
};

Expand Down
6 changes: 6 additions & 0 deletions src/format/kdbx4/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ mod kdbx4_tests {
parallelism: 2,
version: argon2::Version::Version13,
},
KdfConfig::Argon2id {
iterations: 10,
memory: 65536,
parallelism: 2,
version: argon2::Version::Version13,
},
];

for outer_cipher_config in &outer_cipher_configs {
Expand Down
51 changes: 51 additions & 0 deletions tests/file_read_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,23 @@ mod file_read_tests {
Ok(())
}

#[test]
fn open_kdbx4_with_password_kdf_argon2id_cipher_aes() -> Result<(), DatabaseOpenError> {
let path = Path::new("tests/resources/test_db_kdbx4_with_password_argon2id.kdbx");

let db = Database::open(
&mut File::open(path)?,
DatabaseKey::with_password("demopass"),
)?;

println!("{:?} DB Opened", db);

assert_eq!(db.root.name, "Root");
assert_eq!(db.root.children.len(), 2);

Ok(())
}

#[test]
fn open_kdbx4_with_password_kdf_aes_cipher_aes() -> Result<(), DatabaseOpenError> {
let path = Path::new("tests/resources/test_db_kdbx4_with_password_aes.kdbx");
Expand Down Expand Up @@ -191,6 +208,40 @@ mod file_read_tests {
Ok(())
}

#[test]
fn open_kdbx4_with_password_kdf_argon2id_cipher_twofish() -> Result<(), DatabaseOpenError> {
let path = Path::new("tests/resources/test_db_kdbx4_with_password_argon2id_twofish.kdbx");

let db = Database::open(
&mut File::open(path)?,
DatabaseKey::with_password("demopass"),
)?;

println!("{:?} DB Opened", db);

assert_eq!(db.root.name, "Root");
assert_eq!(db.root.children.len(), 1);

Ok(())
}

#[test]
fn open_kdbx4_with_password_kdf_argon2id_cipher_chacha20() -> Result<(), DatabaseOpenError> {
let path = Path::new("tests/resources/test_db_kdbx4_with_password_argon2id_chacha20.kdbx");

let db = Database::open(
&mut File::open(path)?,
DatabaseKey::with_password("demopass"),
)?;

println!("{:?} DB Opened", db);

assert_eq!(db.root.name, "Root");
assert_eq!(db.root.children.len(), 1);

Ok(())
}

#[test]
fn open_kdbx4_with_keyfile() -> Result<(), DatabaseOpenError> {
let path = Path::new("tests/resources/test_db_kdbx4_with_keyfile.kdbx");
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.