Skip to main content

dcrypt_algorithms/kdf/
params.rs

1//! Common parameter structures and traits for key derivation functions
2
3#[cfg(feature = "alloc")]
4use crate::alloc_prelude::*;
5
6// Conditional imports based on available features
7#[cfg(feature = "std")]
8use std::collections::BTreeMap;
9#[cfg(feature = "std")]
10use std::string::String;
11#[cfg(feature = "std")]
12use std::vec::Vec;
13
14#[cfg(all(feature = "alloc", not(feature = "std")))]
15use alloc::collections::BTreeMap;
16#[cfg(all(feature = "alloc", not(feature = "std")))]
17use alloc::string::String;
18#[cfg(all(feature = "alloc", not(feature = "std")))]
19use alloc::vec::Vec;
20
21use base64::{engine::general_purpose::STANDARD_NO_PAD, Engine as _};
22use core::fmt;
23use core::str::FromStr;
24use dcrypt_internal::zeroing::Zeroize;
25
26use crate::error::{Error, Result};
27
28/// Trait for algorithms with configurable parameters
29pub trait ParamProvider {
30    /// The parameter type associated with this algorithm
31    type Params: Clone;
32
33    /// Creates a new instance with the specified parameters
34    fn with_params(params: Self::Params) -> Self;
35
36    /// Returns the current parameters
37    fn params(&self) -> &Self::Params;
38
39    /// Updates the parameters
40    fn set_params(&mut self, params: Self::Params);
41}
42
43/// A complete password hash with algorithm, parameters, salt, and hash
44#[derive(Clone, PartialEq, Eq)]
45pub struct PasswordHash {
46    /// The algorithm identifier
47    pub algorithm: String,
48
49    /// Algorithm-specific parameters
50    pub params: BTreeMap<String, String>,
51
52    /// The public salt used for hashing.
53    pub salt: Vec<u8>,
54
55    /// The public password verifier encoded in the PHC string.
56    pub hash: Vec<u8>,
57}
58
59// Manual implementation of Zeroize for PasswordHash
60impl Zeroize for PasswordHash {
61    fn zeroize(&mut self) {
62        self.algorithm.zeroize();
63        for (mut key, mut value) in core::mem::take(&mut self.params) {
64            key.zeroize();
65            value.zeroize();
66        }
67        self.salt.zeroize();
68        self.hash.zeroize();
69    }
70}
71
72impl PasswordHash {
73    /// Creates a new password hash
74    pub fn new(
75        algorithm: String,
76        params: BTreeMap<String, String>,
77        salt: Vec<u8>,
78        hash: Vec<u8>,
79    ) -> Self {
80        Self {
81            algorithm,
82            params,
83            salt,
84            hash,
85        }
86    }
87
88    /// Extracts a parameter value by key
89    pub fn param(&self, key: &str) -> Option<&String> {
90        self.params.get(key)
91    }
92
93    /// Parses a parameter as an integer
94    pub fn param_as_u32(&self, key: &str) -> Result<u32> {
95        match self.param(key) {
96            Some(value) => value.parse::<u32>().map_err(|_| {
97                Error::param(
98                    key.to_string(), // Convert to owned String for dynamic lifetime
99                    "Invalid parameter value - not a valid u32",
100                )
101            }),
102            None => Err(Error::param(
103                key.to_string(), // Convert to owned String for dynamic lifetime
104                "Missing required parameter",
105            )),
106        }
107    }
108}
109
110// String encoding for PasswordHash in PHC format
111// $algorithm$param=value,param=value$salt$hash
112impl fmt::Display for PasswordHash {
113    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114        write!(f, "${}", self.algorithm)?;
115
116        if !self.params.is_empty() {
117            write!(f, "$")?;
118            let mut first = true;
119            for (key, value) in &self.params {
120                if !first {
121                    write!(f, ",")?;
122                }
123                write!(f, "{}={}", key, value)?;
124                first = false;
125            }
126        }
127
128        // Encode salt and hash in base64
129        let salt_b64 = STANDARD_NO_PAD.encode(self.salt.as_slice());
130        let hash_b64 = STANDARD_NO_PAD.encode(self.hash.as_slice());
131
132        write!(f, "${}${}", salt_b64, hash_b64)
133    }
134}
135
136impl FromStr for PasswordHash {
137    type Err = Error;
138
139    fn from_str(s: &str) -> Result<Self> {
140        if !s.starts_with('$') {
141            return Err(Error::param(
142                "password_hash",
143                "Invalid password hash format - must start with '$'",
144            ));
145        }
146
147        let parts: Vec<&str> = s.split('$').skip(1).collect();
148        if parts.len() < 3 {
149            return Err(Error::param(
150                "password_hash",
151                "Invalid password hash format - insufficient components",
152            ));
153        }
154
155        let algorithm = parts[0].to_string();
156
157        // Parse parameters if present
158        let mut params = BTreeMap::new();
159        if parts.len() > 3 {
160            for param_str in parts[1].split(',') {
161                if param_str.is_empty() {
162                    continue;
163                }
164
165                let param_parts: Vec<&str> = param_str.split('=').collect();
166                if param_parts.len() != 2 {
167                    return Err(Error::param(
168                        "param",
169                        "Invalid parameter format - must be key=value",
170                    ));
171                }
172
173                params.insert(param_parts[0].to_string(), param_parts[1].to_string());
174            }
175        }
176
177        // Parse salt and hash
178        let salt_idx = if parts.len() > 3 { 2 } else { 1 };
179        let hash_idx = if parts.len() > 3 { 3 } else { 2 };
180
181        let salt = STANDARD_NO_PAD
182            .decode(parts[salt_idx])
183            .map_err(|_| Error::param("salt", "Invalid salt encoding - not valid base64"))?;
184
185        let hash = STANDARD_NO_PAD
186            .decode(parts[hash_idx])
187            .map_err(|_| Error::param("hash", "Invalid hash encoding - not valid base64"))?;
188
189        Ok(PasswordHash {
190            algorithm,
191            params,
192            salt,
193            hash,
194        })
195    }
196}