Skip to main content

crypto_argon2id/
derive.rs

1// SPDX-FileCopyrightText: Copyright © 2026 ReallyMe LLC. All rights reserved
2//
3// SPDX-License-Identifier: Apache-2.0
4
5use argon2::{Algorithm, Argon2, Block, Params, Version};
6use crypto_core::{CryptoError, KdfAlgorithm, KdfFailureKind};
7use zeroize::{Zeroize, Zeroizing};
8
9use crate::constants::ARGON2ID_DERIVED_KEY_LENGTH;
10use crate::material::{Argon2Salt, Argon2Secret, Argon2idDerivedKey};
11use crate::profile::{Argon2KdfVersion, Argon2Profile};
12
13/// Inputs for a single Argon2id key-derivation operation.
14pub struct DeriveKeyRequest<'a> {
15    /// Parameter profile selecting the cost tuple.
16    pub profile: Argon2Profile,
17    /// Secret input (e.g. password) to hash.
18    pub secret: &'a Argon2Secret,
19    /// Salt to bind the derivation to.
20    pub salt: &'a Argon2Salt,
21}
22
23/// Derives a 32-byte key with Argon2id using the request's profile, secret, and
24/// salt. Returns an error on invalid parameters or derivation failure. The
25/// intermediate output buffer is zeroized before returning.
26pub fn derive_key(request: &DeriveKeyRequest<'_>) -> Result<Argon2idDerivedKey, CryptoError> {
27    let profile = request.profile;
28    let kdf_profile = profile.to_kdf_profile();
29    let (m_cost, t_cost, p_cost) = profile.params_tuple();
30
31    let params =
32        Params::new(m_cost, t_cost, p_cost, Some(ARGON2ID_DERIVED_KEY_LENGTH)).map_err(|_| {
33            CryptoError::Kdf {
34                algorithm: KdfAlgorithm::Argon2id,
35                profile: kdf_profile,
36                kind: KdfFailureKind::InvalidParams,
37            }
38        })?;
39
40    let block_count = params.block_count();
41    let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
42
43    // The upstream convenience API allocates its memory matrix in an ordinary
44    // Vec, which releases password-derived blocks without wiping them. Own the
45    // matrix here so every initialized block is zeroized on success, failure,
46    // or unwind before the allocation is returned to the allocator.
47    let mut blocks = Vec::new();
48    blocks
49        .try_reserve_exact(block_count)
50        .map_err(|_| CryptoError::Kdf {
51            algorithm: KdfAlgorithm::Argon2id,
52            profile: kdf_profile,
53            kind: KdfFailureKind::DerivationFailed,
54        })?;
55    blocks.resize(block_count, Block::default());
56    let mut blocks = Zeroizing::new(blocks);
57
58    let mut output = Zeroizing::new(vec![0u8; ARGON2ID_DERIVED_KEY_LENGTH]);
59    argon2
60        .hash_password_into_with_memory(
61            request.secret.as_bytes(),
62            request.salt.as_bytes(),
63            &mut output,
64            blocks.as_mut_slice(),
65        )
66        .map_err(|_| CryptoError::Kdf {
67            algorithm: KdfAlgorithm::Argon2id,
68            profile: kdf_profile,
69            kind: KdfFailureKind::DerivationFailed,
70        })?;
71
72    let mut bytes = [0u8; ARGON2ID_DERIVED_KEY_LENGTH];
73    bytes.copy_from_slice(&output);
74    output.zeroize();
75
76    Ok(Argon2idDerivedKey::from_array(bytes))
77}
78
79/// Derives a 32-byte key from raw secret and salt bytes, selecting the profile
80/// from the KDF version integer. Returns an error if the version is unrecognized,
81/// the secret or salt length is invalid, or derivation fails.
82pub fn derive_key_for_version(
83    kdf_version: u32,
84    secret: &[u8],
85    salt: &[u8],
86) -> Result<Argon2idDerivedKey, CryptoError> {
87    let version = Argon2KdfVersion::try_from(kdf_version)?;
88    let profile = Argon2Profile::from(version);
89
90    let secret = Argon2Secret::from_slice(secret, profile)?;
91    let salt = Argon2Salt::from_slice(salt, profile)?;
92
93    derive_key(&DeriveKeyRequest {
94        profile,
95        secret: &secret,
96        salt: &salt,
97    })
98}