dcrypt_algorithms/kdf/
params.rs1#![cfg_attr(not(feature = "std"), no_std)]
4
5#[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
26pub trait ParamProvider {
28 type Params: Clone;
30
31 fn with_params(params: Self::Params) -> Self;
33
34 fn params(&self) -> &Self::Params;
36
37 fn set_params(&mut self, params: Self::Params);
39}
40
41pub trait StringEncodable {
43 fn to_string(&self) -> String;
45
46 fn from_string(s: &str) -> Result<Self>
48 where
49 Self: Sized;
50}
51
52#[derive(Clone, PartialEq, Eq)]
54pub struct PasswordHash {
55 pub algorithm: String,
57
58 pub params: BTreeMap<String, String>,
60
61 pub salt: Zeroizing<Vec<u8>>,
63
64 pub hash: Zeroizing<Vec<u8>>,
66}
67
68impl Zeroize for PasswordHash {
70 fn zeroize(&mut self) {
71 self.algorithm.zeroize();
72 }
76}
77
78impl PasswordHash {
79 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 pub fn param(&self, key: &str) -> Option<&String> {
96 self.params.get(key)
97 }
98
99 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(), "Invalid parameter value - not a valid u32",
106 )
107 }),
108 None => Err(Error::param(
109 key.to_string(), "Missing required parameter",
111 )),
112 }
113 }
114}
115
116impl 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 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 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 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
202fn base64_encode(data: &[u8]) -> String {
206 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 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}