/rust/registry/src/index.crates.io-1949cf8c6b5b557f/password-hash-0.4.2/src/salt.rs
Line | Count | Source |
1 | | //! Salt string support. |
2 | | |
3 | | use crate::{Encoding, Error, Result, Value}; |
4 | | use core::{fmt, str}; |
5 | | |
6 | | use crate::errors::InvalidValue; |
7 | | #[cfg(feature = "rand_core")] |
8 | | use rand_core::{CryptoRng, RngCore}; |
9 | | |
10 | | /// Error message used with `expect` for when internal invariants are violated |
11 | | /// (i.e. the contents of a [`Salt`] should always be valid) |
12 | | const INVARIANT_VIOLATED_MSG: &str = "salt string invariant violated"; |
13 | | |
14 | | /// Salt string. |
15 | | /// |
16 | | /// In password hashing, a "salt" is an additional value used to |
17 | | /// personalize/tweak the output of a password hashing function for a given |
18 | | /// input password. |
19 | | /// |
20 | | /// Salts help defend against attacks based on precomputed tables of hashed |
21 | | /// passwords, i.e. "[rainbow tables][1]". |
22 | | /// |
23 | | /// The [`Salt`] type implements the RECOMMENDED best practices for salts |
24 | | /// described in the [PHC string format specification][2], namely: |
25 | | /// |
26 | | /// > - Maximum lengths for salt, output and parameter values are meant to help |
27 | | /// > consumer implementations, in particular written in C and using |
28 | | /// > stack-allocated buffers. These buffers must account for the worst case, |
29 | | /// > i.e. the maximum defined length. Therefore, keep these lengths low. |
30 | | /// > - The role of salts is to achieve uniqueness. A random salt is fine for |
31 | | /// > that as long as its length is sufficient; a 16-byte salt would work well |
32 | | /// > (by definition, UUID are very good salts, and they encode over exactly |
33 | | /// > 16 bytes). 16 bytes encode as 22 characters in B64. Functions should |
34 | | /// > disallow salt values that are too small for security (4 bytes should be |
35 | | /// > viewed as an absolute minimum). |
36 | | /// |
37 | | /// # Recommended length |
38 | | /// The recommended default length for a salt string is **16-bytes** (128-bits). |
39 | | /// |
40 | | /// See [`Salt::RECOMMENDED_LENGTH`] for more information. |
41 | | /// |
42 | | /// # Constraints |
43 | | /// Salt strings are constrained to the following set of characters per the |
44 | | /// PHC spec: |
45 | | /// |
46 | | /// > The salt consists in a sequence of characters in: `[a-zA-Z0-9/+.-]` |
47 | | /// > (lowercase letters, uppercase letters, digits, /, +, . and -). |
48 | | /// |
49 | | /// Additionally the following length restrictions are enforced based on the |
50 | | /// guidelines from the spec: |
51 | | /// |
52 | | /// - Minimum length: **4**-bytes |
53 | | /// - Maximum length: **64**-bytes |
54 | | /// |
55 | | /// A maximum length is enforced based on the above recommendation for |
56 | | /// supporting stack-allocated buffers (which this library uses), and the |
57 | | /// specific determination of 64-bytes is taken as a best practice from the |
58 | | /// [Argon2 Encoding][3] specification in the same document: |
59 | | /// |
60 | | /// > The length in bytes of the salt is between 8 and 64 bytes<sup>†</sup>, thus |
61 | | /// > yielding a length in characters between 11 and 64 characters (and that |
62 | | /// > length is never equal to 1 modulo 4). The default byte length of the salt |
63 | | /// > is 16 bytes (22 characters in B64 encoding). An encoded UUID, or a |
64 | | /// > sequence of 16 bytes produced with a cryptographically strong PRNG, are |
65 | | /// > appropriate salt values. |
66 | | /// > |
67 | | /// > <sup>†</sup>The Argon2 specification states that the salt can be much longer, up |
68 | | /// > to 2^32-1 bytes, but this makes little sense for password hashing. |
69 | | /// > Specifying a relatively small maximum length allows for parsing with a |
70 | | /// > stack allocated buffer.) |
71 | | /// |
72 | | /// Based on this guidance, this type enforces an upper bound of 64-bytes |
73 | | /// as a reasonable maximum, and recommends using 16-bytes. |
74 | | /// |
75 | | /// [1]: https://en.wikipedia.org/wiki/Rainbow_table |
76 | | /// [2]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#function-duties |
77 | | /// [3]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#argon2-encoding |
78 | | #[derive(Copy, Clone, Eq, PartialEq)] |
79 | | pub struct Salt<'a>(Value<'a>); |
80 | | |
81 | | #[allow(clippy::len_without_is_empty)] |
82 | | impl<'a> Salt<'a> { |
83 | | /// Minimum length of a [`Salt`] string: 4-bytes. |
84 | | pub const MIN_LENGTH: usize = 4; |
85 | | |
86 | | /// Maximum length of a [`Salt`] string: 64-bytes. |
87 | | /// |
88 | | /// See type-level documentation about [`Salt`] for more information. |
89 | | pub const MAX_LENGTH: usize = 64; |
90 | | |
91 | | /// Recommended length of a salt: 16-bytes. |
92 | | /// |
93 | | /// This recommendation comes from the [PHC string format specification]: |
94 | | /// |
95 | | /// > The role of salts is to achieve uniqueness. A *random* salt is fine |
96 | | /// > for that as long as its length is sufficient; a 16-byte salt would |
97 | | /// > work well (by definition, UUID are very good salts, and they encode |
98 | | /// > over exactly 16 bytes). 16 bytes encode as 22 characters in B64. |
99 | | /// |
100 | | /// [PHC string format specification]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#function-duties |
101 | | pub const RECOMMENDED_LENGTH: usize = 16; |
102 | | |
103 | | /// Create a [`Salt`] from the given `str`, validating it according to |
104 | | /// [`Salt::MIN_LENGTH`] and [`Salt::MAX_LENGTH`] length restrictions. |
105 | 0 | pub fn new(input: &'a str) -> Result<Self> { |
106 | 0 | let length = input.as_bytes().len(); |
107 | | |
108 | 0 | if length < Self::MIN_LENGTH { |
109 | 0 | return Err(Error::SaltInvalid(InvalidValue::TooShort)); |
110 | 0 | } |
111 | | |
112 | 0 | if length > Self::MAX_LENGTH { |
113 | 0 | return Err(Error::SaltInvalid(InvalidValue::TooLong)); |
114 | 0 | } |
115 | | |
116 | 0 | input.try_into().map(Self).map_err(|e| match e { |
117 | 0 | Error::ParamValueInvalid(value_err) => Error::SaltInvalid(value_err), |
118 | 0 | err => err, |
119 | 0 | }) |
120 | 0 | } |
121 | | |
122 | | /// Attempt to decode a B64-encoded [`Salt`], writing the decoded result |
123 | | /// into the provided buffer, and returning a slice of the buffer |
124 | | /// containing the decoded result on success. |
125 | | /// |
126 | | /// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#argon2-encoding |
127 | 0 | pub fn b64_decode<'b>(&self, buf: &'b mut [u8]) -> Result<&'b [u8]> { |
128 | 0 | self.0.b64_decode(buf) |
129 | 0 | } |
130 | | |
131 | | /// Borrow this value as a `str`. |
132 | 0 | pub fn as_str(&self) -> &'a str { |
133 | 0 | self.0.as_str() |
134 | 0 | } |
135 | | |
136 | | /// Borrow this value as bytes. |
137 | 0 | pub fn as_bytes(&self) -> &'a [u8] { |
138 | 0 | self.as_str().as_bytes() |
139 | 0 | } |
140 | | |
141 | | /// Get the length of this value in ASCII characters. |
142 | 0 | pub fn len(&self) -> usize { |
143 | 0 | self.as_str().len() |
144 | 0 | } |
145 | | } |
146 | | |
147 | | impl<'a> AsRef<str> for Salt<'a> { |
148 | 0 | fn as_ref(&self) -> &str { |
149 | 0 | self.as_str() |
150 | 0 | } |
151 | | } |
152 | | |
153 | | impl<'a> TryFrom<&'a str> for Salt<'a> { |
154 | | type Error = Error; |
155 | | |
156 | 0 | fn try_from(input: &'a str) -> Result<Self> { |
157 | 0 | Self::new(input) |
158 | 0 | } |
159 | | } |
160 | | |
161 | | impl<'a> fmt::Display for Salt<'a> { |
162 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
163 | 0 | f.write_str(self.as_str()) |
164 | 0 | } |
165 | | } |
166 | | |
167 | | impl<'a> fmt::Debug for Salt<'a> { |
168 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
169 | 0 | write!(f, "Salt({:?})", self.as_str()) |
170 | 0 | } |
171 | | } |
172 | | |
173 | | /// Owned stack-allocated equivalent of [`Salt`]. |
174 | | #[derive(Clone, Eq)] |
175 | | pub struct SaltString { |
176 | | /// Byte array containing an ASCiI-encoded string. |
177 | | bytes: [u8; Salt::MAX_LENGTH], |
178 | | |
179 | | /// Length of the string in ASCII characters (i.e. bytes). |
180 | | length: u8, |
181 | | } |
182 | | |
183 | | #[allow(clippy::len_without_is_empty)] |
184 | | impl SaltString { |
185 | | /// Generate a random B64-encoded [`SaltString`]. |
186 | | #[cfg(feature = "rand_core")] |
187 | | #[cfg_attr(docsrs, doc(cfg(feature = "rand_core")))] |
188 | 0 | pub fn generate(mut rng: impl CryptoRng + RngCore) -> Self { |
189 | 0 | let mut bytes = [0u8; Salt::RECOMMENDED_LENGTH]; |
190 | 0 | rng.fill_bytes(&mut bytes); |
191 | 0 | Self::b64_encode(&bytes).expect(INVARIANT_VIOLATED_MSG) |
192 | 0 | } |
193 | | |
194 | | /// Create a new [`SaltString`]. |
195 | 0 | pub fn new(s: &str) -> Result<Self> { |
196 | | // Assert `s` parses successfully as a `Salt` |
197 | 0 | Salt::new(s)?; |
198 | | |
199 | 0 | let length = s.as_bytes().len(); |
200 | | |
201 | 0 | if length < Salt::MAX_LENGTH { |
202 | 0 | let mut bytes = [0u8; Salt::MAX_LENGTH]; |
203 | 0 | bytes[..length].copy_from_slice(s.as_bytes()); |
204 | 0 | Ok(SaltString { |
205 | 0 | bytes, |
206 | 0 | length: length as u8, |
207 | 0 | }) |
208 | | } else { |
209 | 0 | Err(Error::SaltInvalid(InvalidValue::TooLong)) |
210 | | } |
211 | 0 | } |
212 | | |
213 | | /// Encode the given byte slice as B64 into a new [`SaltString`]. |
214 | | /// |
215 | | /// Returns `None` if the slice is too long. |
216 | 0 | pub fn b64_encode(input: &[u8]) -> Result<Self> { |
217 | 0 | let mut bytes = [0u8; Salt::MAX_LENGTH]; |
218 | 0 | let length = Encoding::B64.encode(input, &mut bytes)?.len() as u8; |
219 | 0 | Ok(Self { bytes, length }) |
220 | 0 | } |
221 | | |
222 | | /// Decode this [`SaltString`] from B64 into the provided output buffer. |
223 | 0 | pub fn b64_decode<'a>(&self, buf: &'a mut [u8]) -> Result<&'a [u8]> { |
224 | 0 | self.as_salt().b64_decode(buf) |
225 | 0 | } |
226 | | |
227 | | /// Borrow the contents of a [`SaltString`] as a [`Salt`]. |
228 | 0 | pub fn as_salt(&self) -> Salt<'_> { |
229 | 0 | Salt::new(self.as_str()).expect(INVARIANT_VIOLATED_MSG) |
230 | 0 | } |
231 | | |
232 | | /// Borrow the contents of a [`SaltString`] as a `str`. |
233 | 0 | pub fn as_str(&self) -> &str { |
234 | 0 | str::from_utf8(&self.bytes[..(self.length as usize)]).expect(INVARIANT_VIOLATED_MSG) |
235 | 0 | } |
236 | | |
237 | | /// Borrow this value as bytes. |
238 | 0 | pub fn as_bytes(&self) -> &[u8] { |
239 | 0 | self.as_str().as_bytes() |
240 | 0 | } |
241 | | |
242 | | /// Get the length of this value in ASCII characters. |
243 | 0 | pub fn len(&self) -> usize { |
244 | 0 | self.as_str().len() |
245 | 0 | } |
246 | | } |
247 | | |
248 | | impl AsRef<str> for SaltString { |
249 | 0 | fn as_ref(&self) -> &str { |
250 | 0 | self.as_str() |
251 | 0 | } |
252 | | } |
253 | | |
254 | | impl PartialEq for SaltString { |
255 | 0 | fn eq(&self, other: &Self) -> bool { |
256 | | // Ensure comparisons always honor the initialized portion of the buffer |
257 | 0 | self.as_ref().eq(other.as_ref()) |
258 | 0 | } |
259 | | } |
260 | | |
261 | | impl<'a> From<&'a SaltString> for Salt<'a> { |
262 | 0 | fn from(salt_string: &'a SaltString) -> Salt<'a> { |
263 | 0 | salt_string.as_salt() |
264 | 0 | } |
265 | | } |
266 | | |
267 | | impl fmt::Display for SaltString { |
268 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
269 | 0 | f.write_str(self.as_str()) |
270 | 0 | } |
271 | | } |
272 | | |
273 | | impl fmt::Debug for SaltString { |
274 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
275 | 0 | write!(f, "SaltString({:?})", self.as_str()) |
276 | 0 | } |
277 | | } |
278 | | |
279 | | #[cfg(test)] |
280 | | mod tests { |
281 | | use super::{Error, Salt}; |
282 | | use crate::errors::InvalidValue; |
283 | | |
284 | | #[test] |
285 | | fn new_with_valid_min_length_input() { |
286 | | let s = "abcd"; |
287 | | let salt = Salt::new(s).unwrap(); |
288 | | assert_eq!(salt.as_ref(), s); |
289 | | } |
290 | | |
291 | | #[test] |
292 | | fn new_with_valid_max_length_input() { |
293 | | let s = "012345678911234567892123456789312345678941234567"; |
294 | | let salt = Salt::new(s).unwrap(); |
295 | | assert_eq!(salt.as_ref(), s); |
296 | | } |
297 | | |
298 | | #[test] |
299 | | fn reject_new_too_short() { |
300 | | for &too_short in &["", "a", "ab", "abc"] { |
301 | | let err = Salt::new(too_short).err().unwrap(); |
302 | | assert_eq!(err, Error::SaltInvalid(InvalidValue::TooShort)); |
303 | | } |
304 | | } |
305 | | |
306 | | #[test] |
307 | | fn reject_new_too_long() { |
308 | | let s = "01234567891123456789212345678931234567894123456785234567896234567"; |
309 | | let err = Salt::new(s).err().unwrap(); |
310 | | assert_eq!(err, Error::SaltInvalid(InvalidValue::TooLong)); |
311 | | } |
312 | | |
313 | | #[test] |
314 | | fn reject_new_invalid_char() { |
315 | | let s = "01234_abcd"; |
316 | | let err = Salt::new(s).err().unwrap(); |
317 | | assert_eq!(err, Error::SaltInvalid(InvalidValue::InvalidChar('_'))); |
318 | | } |
319 | | } |