dcrypt_algorithms/kdf/
params.rs

1//! Common parameter structures and traits for key derivation functions
2
3#![cfg_attr(not(feature = "std"), no_std)]
4
5// Conditional imports based on available features
6#[cfg(feature = "std")]
7use std::collections::BTreeMap;
8#[cfg(feature = "std")]
9use std::string::String;
10#[cfg(feature = "std")]
11use std::vec::Vec;
12
13#[cfg(all(feature = "alloc", not(feature = "std")))]
14use alloc::collections::BTreeMap;
15#[cfg(all(feature = "alloc", not(feature = "std")))]
16use alloc::string::String;
17#[cfg(all(feature = "alloc", not(feature = "std")))]
18use alloc::vec::Vec;
19
20use core::fmt;
21use core::str::FromStr;
22use zeroize::{Zeroize, Zeroizing};
23
24use crate::error::{Error, Result};
25
26/// Trait for algorithms with configurable parameters
27pub trait ParamProvider {
28    /// The parameter type associated with this algorithm
29    type Params: Clone;
30
31    /// Creates a new instance with the specified parameters
32    fn with_params(params: Self::Params) -> Self;
33
34    /// Returns the current parameters
35    fn params(&self) -> &Self::Params;
36
37    /// Updates the parameters
38    fn set_params(&mut self, params: Self::Params);
39}
40
41/// Trait for parameter types that can be serialized to a string
42pub trait StringEncodable {
43    /// Converts the parameters to a string representation
44    fn to_string(&self) -> String;
45
46    /// Converts from a string representation
47    fn from_string(s: &str) -> Result<Self>
48    where
49        Self: Sized;
50}
51
52/// A complete password hash with algorithm, parameters, salt, and hash
53#[derive(Clone, PartialEq, Eq)]
54pub struct PasswordHash {
55    /// The algorithm identifier
56    pub algorithm: String,
57
58    /// Algorithm-specific parameters
59    pub params: BTreeMap<String, String>,
60
61    /// The salt used for hashing
62    pub salt: Zeroizing<Vec<u8>>,
63
64    /// The password hash
65    pub hash: Zeroizing<Vec<u8>>,
66}
67
68// Manual implementation of Zeroize for PasswordHash
69impl Zeroize for PasswordHash {
70    fn zeroize(&mut self) {
71        self.algorithm.zeroize();
72        // BTreeMap doesn't implement Zeroize, so we can't zeroize it directly
73        // The sensitive data is in salt and hash which are Zeroizing<Vec<u8>>
74        // and will be zeroized automatically
75    }
76}
77
78impl PasswordHash {
79    /// Creates a new password hash
80    pub fn new(
81        algorithm: String,
82        params: BTreeMap<String, String>,
83        salt: Vec<u8>,
84        hash: Vec<u8>,
85    ) -> Self {
86        Self {
87            algorithm,
88            params,
89            salt: Zeroizing::new(salt),
90            hash: Zeroizing::new(hash),
91        }
92    }
93
94    /// Extracts a parameter value by key
95    pub fn param(&self, key: &str) -> Option<&String> {
96        self.params.get(key)
97    }
98
99    /// Parses a parameter as an integer
100    pub fn param_as_u32(&self, key: &str) -> Result<u32> {
101        match self.param(key) {
102            Some(value) => value.parse::<u32>().map_err(|_| {
103                Error::param(
104                    key.to_string(), // Convert to owned String for dynamic lifetime
105                    "Invalid parameter value - not a valid u32",
106                )
107            }),
108            None => Err(Error::param(
109                key.to_string(), // Convert to owned String for dynamic lifetime
110                "Missing required parameter",
111            )),
112        }
113    }
114}
115
116// String encoding for PasswordHash in PHC format
117// $algorithm$param=value,param=value$salt$hash
118impl fmt::Display for PasswordHash {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        write!(f, "${}", self.algorithm)?;
121
122        if !self.params.is_empty() {
123            write!(f, "$")?;
124            let mut first = true;
125            for (key, value) in &self.params {
126                if !first {
127                    write!(f, ",")?;
128                }
129                write!(f, "{}={}", key, value)?;
130                first = false;
131            }
132        }
133
134        // Encode salt and hash in base64
135        let salt_b64 = base64_encode(&self.salt);
136        let hash_b64 = base64_encode(&self.hash);
137
138        write!(f, "${}${}", salt_b64, hash_b64)
139    }
140}
141
142impl FromStr for PasswordHash {
143    type Err = Error;
144
145    fn from_str(s: &str) -> Result<Self> {
146        if !s.starts_with('$') {
147            return Err(Error::param(
148                "password_hash",
149                "Invalid password hash format - must start with '$'",
150            ));
151        }
152
153        let parts: Vec<&str> = s.split('$').skip(1).collect();
154        if parts.len() < 3 {
155            return Err(Error::param(
156                "password_hash",
157                "Invalid password hash format - insufficient components",
158            ));
159        }
160
161        let algorithm = parts[0].to_string();
162
163        // Parse parameters if present
164        let mut params = BTreeMap::new();
165        if parts.len() > 3 {
166            for param_str in parts[1].split(',') {
167                if param_str.is_empty() {
168                    continue;
169                }
170
171                let param_parts: Vec<&str> = param_str.split('=').collect();
172                if param_parts.len() != 2 {
173                    return Err(Error::param(
174                        "param",
175                        "Invalid parameter format - must be key=value",
176                    ));
177                }
178
179                params.insert(param_parts[0].to_string(), param_parts[1].to_string());
180            }
181        }
182
183        // Parse salt and hash
184        let salt_idx = if parts.len() > 3 { 2 } else { 1 };
185        let hash_idx = if parts.len() > 3 { 3 } else { 2 };
186
187        let salt = base64_decode(parts[salt_idx])
188            .map_err(|_| Error::param("salt", "Invalid salt encoding - not valid base64"))?;
189
190        let hash = base64_decode(parts[hash_idx])
191            .map_err(|_| Error::param("hash", "Invalid hash encoding - not valid base64"))?;
192
193        Ok(PasswordHash {
194            algorithm,
195            params,
196            salt: Zeroizing::new(salt),
197            hash: Zeroizing::new(hash),
198        })
199    }
200}
201
202// Simple Base64 encoding/decoding functions
203// Note: In a real implementation, you'd use a proper base64 library
204
205fn base64_encode(data: &[u8]) -> String {
206    // This is a stub - in a real implementation, use a proper base64 library
207    let encoded = data
208        .iter()
209        .map(|b| format!("{:02x}", b))
210        .collect::<String>();
211    encoded
212}
213
214fn base64_decode(s: &str) -> Result<Vec<u8>> {
215    // This is a stub - in a real implementation, use a proper base64 library
216    let mut result = Vec::new();
217    let mut chars = s.chars().peekable();
218
219    while chars.peek().is_some() {
220        let high = chars.next().ok_or_else(|| {
221            Error::param(
222                "hex_string",
223                "Invalid hex encoding - unexpected end of string",
224            )
225        })?;
226        let low = chars
227            .next()
228            .ok_or_else(|| Error::param("hex_string", "Invalid hex encoding - odd length"))?;
229
230        let byte = u8::from_str_radix(&format!("{}{}", high, low), 16)
231            .map_err(|_| Error::param("hex_string", "Invalid hex encoding - non-hex character"))?;
232
233        result.push(byte);
234    }
235
236    Ok(result)
237}