balloon_hash/
lib.rs

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//! # Usage (simple with default params)
11//!
12//! Note: this example requires the `rand_core` crate with the `std` feature
13//! enabled for `rand_core::OsRng` (embedded platforms can substitute their
14//! own RNG)
15//!
16//! Add the following to your crate's `Cargo.toml` to import it:
17//!
18//! ```toml
19//! [dependencies]
20//! balloon-hash = "0.2"
21//! rand_core = { version = "0.6", features = ["std"] }
22//! sha2 = "0.9"
23//! ```
24//!
25//! The `zeroize` crate feature will zeroize allocated memory created when
26//! using the [`Balloon::hash`] function. It will do nothing when the `alloc`
27//! crate feature is not active.
28//!
29//! The following example demonstrates the high-level password hashing API:
30//!
31//! ```
32//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
33//! # #[cfg(all(feature = "password-hash", feature = "std"))]
34//! # {
35//! use balloon_hash::{
36//!     password_hash::{
37//!         rand_core::OsRng,
38//!         PasswordHash, PasswordHasher, PasswordVerifier, SaltString
39//!     },
40//!     Balloon
41//! };
42//! use sha2::Sha256;
43//!
44//! let password = b"hunter42"; // Bad password; don't actually use!
45//! let salt = SaltString::generate(&mut OsRng);
46//!
47//! // Balloon with default params
48//! let balloon = Balloon::<Sha256>::default();
49//!
50//! // Hash password to PHC string ($balloon$v=1$...)
51//! let password_hash = balloon.hash_password(password, &salt)?.to_string();
52//!
53//! // Verify password against PHC string
54//! let parsed_hash = PasswordHash::new(&password_hash)?;
55//! assert!(balloon.verify_password(password, &parsed_hash).is_ok());
56//! # }
57//! # Ok(())
58//! # }
59//! ```
60//!
61//! [Balloon]: https://en.wikipedia.org/wiki/Balloon_hashing
62
63#[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/// Balloon context.
100///
101/// This is the primary type of this crate's API, and contains the following:
102///
103/// - Default set of [`Params`] to be used
104/// - (Optional) Secret key a.k.a. "pepper" to be used
105#[derive(Clone, Default)]
106pub struct Balloon<'key, D: Digest + FixedOutputReset>
107where
108    GenericArray<u8, D::OutputSize>: ArrayDecoding,
109{
110    /// Storing which hash function is used
111    pub digest: PhantomData<D>,
112    /// Algorithm to use
113    pub algorithm: Algorithm,
114    /// Algorithm parameters
115    pub params: Params,
116    /// Key array
117    pub secret: Option<&'key [u8]>,
118}
119
120impl<'key, D: Digest + FixedOutputReset> Balloon<'key, D>
121where
122    GenericArray<u8, D::OutputSize>: ArrayDecoding,
123{
124    /// Create a new Balloon context.
125    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    /// Hash a password and associated parameters.
135    #[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    /// Hash a password and associated parameters.
145    ///
146    /// The `output` has to have the same size as the hash output size: `D::OutputSize`.
147    #[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    /// Hash a password and associated parameters.
162    ///
163    /// This method takes an explicit `memory_blocks` parameter which allows
164    /// the caller to provide the backing storage for the algorithm's state:
165    ///
166    /// - Users with the `alloc` feature enabled can use [`Balloon::hash`]
167    ///   to have it allocated for them.
168    /// - `no_std` users on "heapless" targets can use an array of the [`GenericArray`] type
169    ///   to stack allocate this buffer. It needs a minimum size of `s_cost` or `s_cost * p_cost`
170    ///   with the `parallel` crate feature enabled.
171    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    /// Hash a password and associated parameters into the provided `output` buffer.
184    ///
185    /// The `output` has to have the same size as the hash output size: `D::OutputSize`.
186    ///
187    /// See [`Balloon::hash_with_memory`] for more details.
188    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}