Skip to main content

sha_crypt/
lib.rs

1#![no_std]
2#![doc = include_str!("../README.md")]
3#![cfg_attr(docsrs, feature(doc_cfg))]
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#![deny(unsafe_code)]
9#![warn(missing_docs, rust_2018_idioms)]
10
11//! # Usage
12//!
13#![cfg_attr(feature = "getrandom", doc = "```")]
14#![cfg_attr(not(feature = "getrandom"), doc = "```ignore")]
15//! # fn main() -> password_hash::Result<()> {
16//! // NOTE: example requires `getrandom` feature is enabled
17//!
18//! use sha_crypt::{PasswordHasher, PasswordVerifier, ShaCrypt};
19//!
20//! let sha_crypt = ShaCrypt::default(); // default is SHA-512-crypt
21//! let password = b"pleaseletmein"; // don't actually use this as a password!
22//! let password_hash = sha_crypt.hash_password(password)?;
23//! assert!(password_hash.as_str().starts_with("$6$"));
24//!
25//! // verify password is correct for the given hash
26//! sha_crypt.verify_password(password, &password_hash)?;
27//! # Ok(())
28//! # }
29//! ```
30
31mod errors;
32mod params;
33
34#[cfg(feature = "password-hash")]
35mod algorithm;
36#[cfg(feature = "password-hash")]
37mod mcf;
38
39pub use crate::{
40    errors::{Error, Result},
41    params::Params,
42};
43
44#[cfg(feature = "password-hash")]
45pub use {
46    crate::{
47        algorithm::Algorithm,
48        mcf::{PasswordHashRef, ShaCrypt},
49    },
50    password_hash::{self, CustomizedPasswordHasher, PasswordHasher, PasswordVerifier},
51};
52
53#[cfg(all(feature = "password-hash", feature = "alloc"))]
54pub use ::mcf::PasswordHash;
55
56use sha2::{Digest, Sha256, Sha512};
57
58/// Block size for SHA-256-crypt.
59pub const BLOCK_SIZE_SHA256: usize = 32;
60
61/// Block size for SHA-512-crypt.
62pub const BLOCK_SIZE_SHA512: usize = 64;
63
64/// The SHA-256-crypt function which outputs a uniformly random byte array.
65///
66/// # Arguments
67/// - `password`: the password to process as a byte vector
68/// - `salt`: the salt value to use as a byte vector
69/// - `params`: the parameters to use
70///
71///   **WARNING: Make sure to compare this value in constant time!**
72#[must_use]
73pub fn sha256_crypt(password: &[u8], salt: &[u8], params: Params) -> [u8; BLOCK_SIZE_SHA256] {
74    let pw_len = password.len();
75
76    let salt_len = salt.len();
77    let salt = match salt_len {
78        0..=15 => &salt[0..salt_len],
79        _ => &salt[0..16],
80    };
81    let salt_len = salt.len();
82
83    let digest_a = sha256_crypt_intermediate(password, salt);
84
85    // 13.
86    let mut hasher_alt = Sha256::default();
87
88    // 14.
89    for _ in 0..pw_len {
90        hasher_alt.update(password);
91    }
92
93    // 15.
94    let dp = hasher_alt.finalize();
95
96    // 16.
97    // Create byte sequence P.
98    // NOTE: deferred until below and computed on-the-fly. See `hash_byte_seq(pw_len...`)
99
100    // 17.
101    hasher_alt = Sha256::default();
102
103    // 18.
104    // For every character in the password add the entire password.
105    for _ in 0..(16 + digest_a[0] as usize) {
106        hasher_alt.update(salt);
107    }
108
109    // 19.
110    // Finish the digest.
111    let ds = hasher_alt.finalize();
112
113    // 20.
114    // Create byte sequence S.
115    // NOTE: deferred until below and computed on-the-fly. See `hash_byte_seq(salt_len...`)
116
117    let mut digest_c = digest_a;
118    // Repeatedly run the collected hash value through SHA256 to burn
119    // CPU cycles
120    for i in 0..params.rounds {
121        // new hasher
122        let mut hasher = Sha256::default();
123
124        // Add key or last result
125        if (i & 1) != 0 {
126            hash_byte_seq(pw_len, &dp, &mut hasher);
127        } else {
128            hasher.update(digest_c);
129        }
130
131        // Add salt for numbers not divisible by 3
132        if i % 3 != 0 {
133            hash_byte_seq(salt_len, &ds, &mut hasher);
134        }
135
136        // Add key for numbers not divisible by 7
137        if i % 7 != 0 {
138            hash_byte_seq(pw_len, &dp, &mut hasher);
139        }
140
141        // Add key or last result
142        if (i & 1) != 0 {
143            hasher.update(digest_c);
144        } else {
145            hash_byte_seq(pw_len, &dp, &mut hasher);
146        }
147
148        digest_c.clone_from_slice(&hasher.finalize());
149    }
150
151    digest_c
152}
153
154/// The SHA-512-crypt function which outputs a uniformly random byte array.
155///
156/// # Arguments
157/// - `password`The password to process as a byte vector
158/// - `salt` - The salt value to use as a byte vector
159/// - `params` - The parameters to use
160///
161///   **WARNING: Make sure to compare this value in constant time!**
162#[must_use]
163pub fn sha512_crypt(password: &[u8], salt: &[u8], params: Params) -> [u8; BLOCK_SIZE_SHA512] {
164    let pw_len = password.len();
165
166    let salt_len = salt.len();
167    let salt = match salt_len {
168        0..=15 => &salt[0..salt_len],
169        _ => &salt[0..16],
170    };
171    let salt_len = salt.len();
172
173    let digest_a = sha512_crypt_intermediate(password, salt);
174
175    // 13.
176    let mut hasher_alt = Sha512::default();
177
178    // 14.
179    for _ in 0..pw_len {
180        hasher_alt.update(password);
181    }
182
183    // 15.
184    let dp = hasher_alt.finalize();
185
186    // 16.
187    // Create byte sequence P.
188    // NOTE: deferred until below and computed on-the-fly. See `hash_byte_seq(pw_len...`)
189
190    // 17.
191    hasher_alt = Sha512::default();
192
193    // 18.
194    // For every character in the password add the entire password.
195    for _ in 0..(16 + digest_a[0] as usize) {
196        hasher_alt.update(salt);
197    }
198
199    // 19.
200    // Finish the digest.
201    let ds = hasher_alt.finalize();
202
203    // 20.
204    // Create byte sequence S.
205    // NOTE: deferred until below and computed on-the-fly. See `hash_byte_seq(salt_len...`)
206
207    let mut digest_c = digest_a;
208    // Repeatedly run the collected hash value through SHA512 to burn
209    // CPU cycles
210    for i in 0..params.rounds {
211        // new hasher
212        let mut hasher = Sha512::default();
213
214        // Add key or last result
215        if (i & 1) != 0 {
216            hash_byte_seq(pw_len, &dp, &mut hasher);
217        } else {
218            hasher.update(digest_c);
219        }
220
221        // Add salt for numbers not divisible by 3
222        if i % 3 != 0 {
223            hash_byte_seq(salt_len, &ds, &mut hasher);
224        }
225
226        // Add key for numbers not divisible by 7
227        if i % 7 != 0 {
228            hash_byte_seq(pw_len, &dp, &mut hasher);
229        }
230
231        // Add key or last result
232        if (i & 1) != 0 {
233            hasher.update(digest_c);
234        } else {
235            hash_byte_seq(pw_len, &dp, &mut hasher);
236        }
237
238        digest_c.clone_from_slice(&hasher.finalize());
239    }
240
241    digest_c
242}
243
244fn sha256_crypt_intermediate(password: &[u8], salt: &[u8]) -> [u8; BLOCK_SIZE_SHA256] {
245    let pw_len = password.len();
246
247    let mut hasher = Sha256::default();
248    hasher.update(password);
249    hasher.update(salt);
250
251    // 4.
252    let mut hasher_alt = Sha256::default();
253    // 5.
254    hasher_alt.update(password);
255    // 6.
256    hasher_alt.update(salt);
257    // 7.
258    hasher_alt.update(password);
259    // 8.
260    let digest_b = hasher_alt.finalize();
261
262    // 9.
263    for _ in 0..(pw_len / BLOCK_SIZE_SHA256) {
264        hasher.update(digest_b);
265    }
266    // 10.
267    hasher.update(&digest_b[..(pw_len % BLOCK_SIZE_SHA256)]);
268
269    // 11
270    let mut n = pw_len;
271    for _ in 0..pw_len {
272        if n == 0 {
273            break;
274        }
275        if (n & 1) != 0 {
276            hasher.update(digest_b);
277        } else {
278            hasher.update(password);
279        }
280        n >>= 1;
281    }
282
283    // 12.
284    hasher.finalize().into()
285}
286
287fn sha512_crypt_intermediate(password: &[u8], salt: &[u8]) -> [u8; BLOCK_SIZE_SHA512] {
288    let pw_len = password.len();
289
290    let mut hasher = Sha512::default();
291    hasher.update(password);
292    hasher.update(salt);
293
294    // 4.
295    let mut hasher_alt = Sha512::default();
296    // 5.
297    hasher_alt.update(password);
298    // 6.
299    hasher_alt.update(salt);
300    // 7.
301    hasher_alt.update(password);
302    // 8.
303    let digest_b = hasher_alt.finalize();
304
305    // 9.
306    for _ in 0..(pw_len / BLOCK_SIZE_SHA512) {
307        hasher.update(digest_b);
308    }
309    // 10.
310    hasher.update(&digest_b[..(pw_len % BLOCK_SIZE_SHA512)]);
311
312    // 11
313    let mut n = pw_len;
314    for _ in 0..pw_len {
315        if n == 0 {
316            break;
317        }
318        if (n & 1) != 0 {
319            hasher.update(digest_b);
320        } else {
321            hasher.update(password);
322        }
323        n >>= 1;
324    }
325
326    // 12.
327    hasher.finalize().into()
328}
329
330fn hash_byte_seq<D: Digest>(len: usize, fill_from: &[u8], digest: &mut D) {
331    let bs = fill_from.len();
332    for _ in 0..(len / bs) {
333        digest.update(fill_from);
334    }
335    digest.update(&fill_from[..(len % bs)]);
336}