1#![no_std]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![doc = include_str!("../README.md")]
4#![doc(
5 html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg",
6 html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg"
7)]
8#![warn(rust_2018_idioms, missing_docs)]
9
10#[cfg(feature = "alloc")]
64extern crate alloc;
65
66#[cfg(feature = "std")]
67extern crate std;
68
69mod algorithm;
70mod balloon;
71mod error;
72mod params;
73
74pub use crate::{
75 algorithm::Algorithm,
76 error::{Error, Result},
77 params::Params,
78};
79
80#[cfg(feature = "password-hash")]
81#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))]
82pub use password_hash::{self, PasswordHash, PasswordHasher, PasswordVerifier};
83
84use core::marker::PhantomData;
85use crypto_bigint::ArrayDecoding;
86use digest::generic_array::GenericArray;
87use digest::typenum::Unsigned;
88use digest::{Digest, FixedOutputReset};
89
90#[cfg(all(feature = "alloc", feature = "password-hash"))]
91pub use password_hash::Salt;
92
93#[cfg(all(feature = "alloc", feature = "password-hash"))]
94use password_hash::{Decimal, Ident, ParamsString};
95
96#[cfg(feature = "zeroize")]
97use zeroize::Zeroize;
98
99#[derive(Clone, Default)]
106pub struct Balloon<'key, D: Digest + FixedOutputReset>
107where
108 GenericArray<u8, D::OutputSize>: ArrayDecoding,
109{
110 pub digest: PhantomData<D>,
112 pub algorithm: Algorithm,
114 pub params: Params,
116 pub secret: Option<&'key [u8]>,
118}
119
120impl<'key, D: Digest + FixedOutputReset> Balloon<'key, D>
121where
122 GenericArray<u8, D::OutputSize>: ArrayDecoding,
123{
124 pub fn new(algorithm: Algorithm, params: Params, secret: Option<&'key [u8]>) -> Self {
126 Self {
127 digest: PhantomData,
128 algorithm,
129 params,
130 secret,
131 }
132 }
133
134 #[cfg(feature = "alloc")]
136 #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
137 pub fn hash(&self, pwd: &[u8], salt: &[u8]) -> Result<GenericArray<u8, D::OutputSize>> {
138 let mut output = GenericArray::default();
139 self.hash_into(pwd, salt, &mut output)?;
140
141 Ok(output)
142 }
143
144 #[cfg(feature = "alloc")]
148 #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
149 pub fn hash_into(&self, pwd: &[u8], salt: &[u8], output: &mut [u8]) -> Result<()> {
150 #[cfg(not(feature = "parallel"))]
151 let mut memory = alloc::vec![GenericArray::default(); self.params.s_cost.get() as usize];
152 #[cfg(feature = "parallel")]
153 let mut memory = alloc::vec![GenericArray::default(); (self.params.s_cost.get() * self.params.p_cost.get()) as usize];
154
155 self.hash_into_with_memory(pwd, salt, &mut memory, output)?;
156 #[cfg(feature = "zeroize")]
157 memory.iter_mut().for_each(|block| block.zeroize());
158 Ok(())
159 }
160
161 pub fn hash_with_memory(
172 &self,
173 pwd: &[u8],
174 salt: &[u8],
175 memory_blocks: &mut [GenericArray<u8, D::OutputSize>],
176 ) -> Result<GenericArray<u8, D::OutputSize>> {
177 let mut output = GenericArray::default();
178 self.hash_into_with_memory(pwd, salt, memory_blocks, &mut output)?;
179
180 Ok(output)
181 }
182
183 pub fn hash_into_with_memory(
189 &self,
190 pwd: &[u8],
191 salt: &[u8],
192 memory_blocks: &mut [GenericArray<u8, D::OutputSize>],
193 output: &mut [u8],
194 ) -> Result<()> {
195 let output = if output.len() == D::OutputSize::USIZE {
196 GenericArray::from_mut_slice(output)
197 } else {
198 return Err(Error::OutputSize {
199 actual: output.len(),
200 expected: D::OutputSize::USIZE,
201 });
202 };
203
204 match self.algorithm {
205 Algorithm::Balloon => {
206 balloon::balloon::<D>(pwd, salt, self.secret, self.params, memory_blocks).map(
207 |hash| {
208 output.copy_from_slice(&hash);
209 },
210 )
211 }
212 Algorithm::BalloonM => {
213 balloon::balloon_m::<D>(pwd, salt, self.secret, self.params, memory_blocks, output)
214 }
215 }
216 }
217}
218
219#[cfg(all(feature = "alloc", feature = "password-hash"))]
220#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
221#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))]
222impl<D: Digest + FixedOutputReset> PasswordHasher for Balloon<'_, D>
223where
224 GenericArray<u8, D::OutputSize>: ArrayDecoding,
225{
226 type Params = Params;
227
228 fn hash_password<'a>(
229 &self,
230 password: &[u8],
231 salt: impl Into<Salt<'a>>,
232 ) -> password_hash::Result<PasswordHash<'a>> {
233 let salt = salt.into();
234 let mut salt_arr = [0u8; 64];
235 let salt_bytes = salt.decode_b64(&mut salt_arr)?;
236 let output = password_hash::Output::new(&self.hash(password, salt_bytes)?)?;
237
238 Ok(PasswordHash {
239 algorithm: self.algorithm.ident(),
240 version: Some(1),
241 params: ParamsString::try_from(&self.params)?,
242 salt: Some(salt),
243 hash: Some(output),
244 })
245 }
246
247 fn hash_password_customized<'a>(
248 &self,
249 password: &[u8],
250 alg_id: Option<Ident<'a>>,
251 version: Option<Decimal>,
252 params: Params,
253 salt: impl Into<Salt<'a>>,
254 ) -> password_hash::Result<PasswordHash<'a>> {
255 let algorithm = alg_id
256 .map(Algorithm::try_from)
257 .transpose()?
258 .unwrap_or_default();
259
260 if let Some(version) = version {
261 if version != 1 {
262 return Err(password_hash::Error::Version);
263 }
264 }
265
266 let salt = salt.into();
267
268 Self::new(algorithm, params, self.secret).hash_password(password, salt)
269 }
270}
271
272impl<'key, D: Digest + FixedOutputReset> From<Params> for Balloon<'key, D>
273where
274 GenericArray<u8, D::OutputSize>: ArrayDecoding,
275{
276 fn from(params: Params) -> Self {
277 Self::new(Algorithm::default(), params, None)
278 }
279}