Skip to content

Commit 76e826d

Browse files
authored
password-hash: extract phc submodule (#2103)
Extracts all functionality related to the Password Hashing Competition (PHC) string format into a `phc` submodule. The longer-term goal would be to extract this into its own crate, however that will need some trait redesign, which can be better accomplished by first adding a `phc` feature and feature-gating the relevant functionality. This is just an initial step to begin isolating the relevant code.
1 parent 5f07487 commit 76e826d

File tree

12 files changed

+348
-336
lines changed

12 files changed

+348
-336
lines changed

password-hash/src/lib.rs

Lines changed: 3 additions & 322 deletions
Original file line numberDiff line numberDiff line change
@@ -32,335 +32,16 @@ extern crate alloc;
3232
pub use rand_core;
3333

3434
pub mod errors;
35+
pub mod phc;
3536

36-
mod ident;
37-
mod output;
38-
mod params;
39-
mod salt;
4037
mod traits;
41-
mod value;
4238

4339
pub use crate::{
4440
errors::{Error, Result},
45-
ident::Ident,
46-
output::Output,
47-
params::ParamsString,
48-
salt::{Salt, SaltString},
4941
traits::{McfHasher, PasswordHasher, PasswordVerifier},
50-
value::{Decimal, Value},
5142
};
5243

53-
use core::fmt::{self, Debug};
44+
pub use phc::PasswordHash;
5445

5546
#[cfg(feature = "alloc")]
56-
use alloc::{
57-
str::FromStr,
58-
string::{String, ToString},
59-
};
60-
61-
/// Separator character used in password hashes (e.g. `$6$...`).
62-
const PASSWORD_HASH_SEPARATOR: char = '$';
63-
64-
/// Password hash.
65-
///
66-
/// This type corresponds to the parsed representation of a PHC string as
67-
/// described in the [PHC string format specification][1].
68-
///
69-
/// PHC strings have the following format:
70-
///
71-
/// ```text
72-
/// $<id>[$v=<version>][$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]]
73-
/// ```
74-
///
75-
/// where:
76-
///
77-
/// - `<id>` is the symbolic name for the function
78-
/// - `<version>` is the algorithm version
79-
/// - `<param>` is a parameter name
80-
/// - `<value>` is a parameter value
81-
/// - `<salt>` is an encoding of the salt
82-
/// - `<hash>` is an encoding of the hash output
83-
///
84-
/// The string is then the concatenation, in that order, of:
85-
///
86-
/// - a `$` sign;
87-
/// - the function symbolic name;
88-
/// - optionally, a `$` sign followed by the algorithm version with a `v=version` format;
89-
/// - optionally, a `$` sign followed by one or several parameters, each with a `name=value` format;
90-
/// the parameters are separated by commas;
91-
/// - optionally, a `$` sign followed by the (encoded) salt value;
92-
/// - optionally, a `$` sign followed by the (encoded) hash output (the hash output may be present
93-
/// only if the salt is present).
94-
///
95-
/// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#specification
96-
#[derive(Clone, Debug, Eq, PartialEq)]
97-
pub struct PasswordHash<'a> {
98-
/// Password hashing algorithm identifier.
99-
///
100-
/// This corresponds to the `<id>` field in a PHC string, a.k.a. the
101-
/// symbolic name for the function.
102-
pub algorithm: Ident<'a>,
103-
104-
/// Optional version field.
105-
///
106-
/// This corresponds to the `<version>` field in a PHC string.
107-
pub version: Option<Decimal>,
108-
109-
/// Algorithm-specific parameters.
110-
///
111-
/// This corresponds to the set of `$<param>=<value>(,<param>=<value>)*`
112-
/// name/value pairs in a PHC string.
113-
pub params: ParamsString,
114-
115-
/// [`Salt`] string for personalizing a password hash output.
116-
///
117-
/// This corresponds to the `<salt>` value in a PHC string.
118-
pub salt: Option<Salt<'a>>,
119-
120-
/// Password hashing function [`Output`], a.k.a. hash/digest.
121-
///
122-
/// This corresponds to the `<hash>` output in a PHC string.
123-
pub hash: Option<Output>,
124-
}
125-
126-
impl<'a> PasswordHash<'a> {
127-
/// Parse a password hash from a string in the PHC string format.
128-
pub fn new(s: &'a str) -> Result<Self> {
129-
if s.is_empty() {
130-
return Err(Error::PhcStringField);
131-
}
132-
133-
let mut fields = s.split(PASSWORD_HASH_SEPARATOR);
134-
let beginning = fields.next().expect("no first field");
135-
136-
if beginning.chars().next().is_some() {
137-
return Err(Error::PhcStringField);
138-
}
139-
140-
let algorithm = fields
141-
.next()
142-
.ok_or(Error::PhcStringField)
143-
.and_then(Ident::try_from)?;
144-
145-
let mut version = None;
146-
let mut params = ParamsString::new();
147-
let mut salt = None;
148-
let mut hash = None;
149-
150-
let mut next_field = fields.next();
151-
152-
if let Some(field) = next_field {
153-
// v=<version>
154-
if field.starts_with("v=") && !field.contains(params::PARAMS_DELIMITER) {
155-
version = Some(Value::new(&field[2..]).and_then(|value| value.decimal())?);
156-
next_field = None;
157-
}
158-
}
159-
160-
if next_field.is_none() {
161-
next_field = fields.next();
162-
}
163-
164-
if let Some(field) = next_field {
165-
// <param>=<value>
166-
if field.contains(params::PAIR_DELIMITER) {
167-
params = field.parse()?;
168-
next_field = None;
169-
}
170-
}
171-
172-
if next_field.is_none() {
173-
next_field = fields.next();
174-
}
175-
176-
if let Some(s) = next_field {
177-
salt = Some(s.try_into()?);
178-
}
179-
180-
if let Some(field) = fields.next() {
181-
hash = Some(Output::decode(field)?);
182-
}
183-
184-
if fields.next().is_some() {
185-
return Err(Error::PhcStringTrailingData);
186-
}
187-
188-
Ok(Self {
189-
algorithm,
190-
version,
191-
params,
192-
salt,
193-
hash,
194-
})
195-
}
196-
197-
/// Generate a password hash using the supplied algorithm.
198-
pub fn generate(
199-
phf: impl PasswordHasher,
200-
password: impl AsRef<[u8]>,
201-
salt: impl Into<Salt<'a>>,
202-
) -> Result<Self> {
203-
phf.hash_password(password.as_ref(), salt)
204-
}
205-
206-
/// Verify this password hash using the specified set of supported
207-
/// [`PasswordHasher`] trait objects.
208-
pub fn verify_password(
209-
&self,
210-
phfs: &[&dyn PasswordVerifier],
211-
password: impl AsRef<[u8]>,
212-
) -> Result<()> {
213-
for &phf in phfs {
214-
if phf.verify_password(password.as_ref(), self).is_ok() {
215-
return Ok(());
216-
}
217-
}
218-
219-
Err(Error::Password)
220-
}
221-
222-
/// Serialize this [`PasswordHash`] as a [`PasswordHashString`].
223-
#[cfg(feature = "alloc")]
224-
pub fn serialize(&self) -> PasswordHashString {
225-
self.into()
226-
}
227-
}
228-
229-
// Note: this uses `TryFrom` instead of `FromStr` to support a lifetime on
230-
// the `str` the value is being parsed from.
231-
impl<'a> TryFrom<&'a str> for PasswordHash<'a> {
232-
type Error = Error;
233-
234-
fn try_from(s: &'a str) -> Result<Self> {
235-
Self::new(s)
236-
}
237-
}
238-
239-
impl fmt::Display for PasswordHash<'_> {
240-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241-
write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, self.algorithm)?;
242-
243-
if let Some(version) = self.version {
244-
write!(f, "{PASSWORD_HASH_SEPARATOR}v={version}")?;
245-
}
246-
247-
if !self.params.is_empty() {
248-
write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, self.params)?;
249-
}
250-
251-
if let Some(salt) = &self.salt {
252-
write!(f, "{PASSWORD_HASH_SEPARATOR}{salt}")?;
253-
254-
if let Some(hash) = &self.hash {
255-
write!(f, "{PASSWORD_HASH_SEPARATOR}{hash}")?;
256-
}
257-
}
258-
259-
Ok(())
260-
}
261-
}
262-
263-
/// Serialized [`PasswordHash`].
264-
///
265-
/// This type contains a serialized password hash string which is ensured to
266-
/// parse successfully.
267-
// TODO(tarcieri): cached parsed representations? or at least structural data
268-
#[cfg(feature = "alloc")]
269-
#[derive(Clone, Debug, Eq, PartialEq)]
270-
pub struct PasswordHashString {
271-
/// String value
272-
string: String,
273-
}
274-
275-
#[cfg(feature = "alloc")]
276-
#[allow(clippy::len_without_is_empty)]
277-
impl PasswordHashString {
278-
/// Parse a password hash from a string in the PHC string format.
279-
pub fn new(s: &str) -> Result<Self> {
280-
PasswordHash::new(s).map(Into::into)
281-
}
282-
283-
/// Parse this owned string as a [`PasswordHash`].
284-
pub fn password_hash(&self) -> PasswordHash<'_> {
285-
PasswordHash::new(&self.string).expect("malformed password hash")
286-
}
287-
288-
/// Borrow this value as a `str`.
289-
pub fn as_str(&self) -> &str {
290-
self.string.as_str()
291-
}
292-
293-
/// Borrow this value as bytes.
294-
pub fn as_bytes(&self) -> &[u8] {
295-
self.as_str().as_bytes()
296-
}
297-
298-
/// Get the length of this value in ASCII characters.
299-
pub fn len(&self) -> usize {
300-
self.as_str().len()
301-
}
302-
303-
/// Password hashing algorithm identifier.
304-
pub fn algorithm(&self) -> Ident<'_> {
305-
self.password_hash().algorithm
306-
}
307-
308-
/// Optional version field.
309-
pub fn version(&self) -> Option<Decimal> {
310-
self.password_hash().version
311-
}
312-
313-
/// Algorithm-specific parameters.
314-
pub fn params(&self) -> ParamsString {
315-
self.password_hash().params
316-
}
317-
318-
/// [`Salt`] string for personalizing a password hash output.
319-
pub fn salt(&self) -> Option<Salt<'_>> {
320-
self.password_hash().salt
321-
}
322-
323-
/// Password hashing function [`Output`], a.k.a. hash/digest.
324-
pub fn hash(&self) -> Option<Output> {
325-
self.password_hash().hash
326-
}
327-
}
328-
329-
#[cfg(feature = "alloc")]
330-
impl AsRef<str> for PasswordHashString {
331-
fn as_ref(&self) -> &str {
332-
self.as_str()
333-
}
334-
}
335-
336-
#[cfg(feature = "alloc")]
337-
impl From<PasswordHash<'_>> for PasswordHashString {
338-
fn from(hash: PasswordHash<'_>) -> PasswordHashString {
339-
PasswordHashString::from(&hash)
340-
}
341-
}
342-
343-
#[cfg(feature = "alloc")]
344-
impl From<&PasswordHash<'_>> for PasswordHashString {
345-
fn from(hash: &PasswordHash<'_>) -> PasswordHashString {
346-
PasswordHashString {
347-
string: hash.to_string(),
348-
}
349-
}
350-
}
351-
352-
#[cfg(feature = "alloc")]
353-
impl FromStr for PasswordHashString {
354-
type Err = Error;
355-
356-
fn from_str(s: &str) -> Result<Self> {
357-
Self::new(s)
358-
}
359-
}
360-
361-
#[cfg(feature = "alloc")]
362-
impl fmt::Display for PasswordHashString {
363-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
364-
f.write_str(self.as_str())
365-
}
366-
}
47+
pub use phc::PasswordHashString;

0 commit comments

Comments
 (0)