dcrypt_algorithms/kdf/
params.rs1#[cfg(feature = "alloc")]
4use crate::alloc_prelude::*;
5
6#[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
28pub trait ParamProvider {
30 type Params: Clone;
32
33 fn with_params(params: Self::Params) -> Self;
35
36 fn params(&self) -> &Self::Params;
38
39 fn set_params(&mut self, params: Self::Params);
41}
42
43#[derive(Clone, PartialEq, Eq)]
45pub struct PasswordHash {
46 pub algorithm: String,
48
49 pub params: BTreeMap<String, String>,
51
52 pub salt: Vec<u8>,
54
55 pub hash: Vec<u8>,
57}
58
59impl 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 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 pub fn param(&self, key: &str) -> Option<&String> {
90 self.params.get(key)
91 }
92
93 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(), "Invalid parameter value - not a valid u32",
100 )
101 }),
102 None => Err(Error::param(
103 key.to_string(), "Missing required parameter",
105 )),
106 }
107 }
108}
109
110impl 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 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 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 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}