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
31// TODO(tarcieri): heapless support
32#[macro_use]
33extern crate alloc;
34
35mod errors;
36mod params;
37
38#[cfg(feature = "password-hash")]
39mod algorithm;
40#[cfg(feature = "password-hash")]
41mod mcf;
42
43pub use crate::{
44    errors::{Error, Result},
45    params::Params,
46};
47
48#[cfg(feature = "password-hash")]
49pub use {
50    crate::{
51        algorithm::Algorithm,
52        mcf::{PasswordHash, PasswordHashRef, ShaCrypt},
53    },
54    password_hash::{self, CustomizedPasswordHasher, PasswordHasher, PasswordVerifier},
55};
56
57use alloc::vec::Vec;
58use sha2::{Digest, Sha256, Sha512};
59
60/// Block size for SHA-256-crypt.
61pub const BLOCK_SIZE_SHA256: usize = 32;
62
63/// Block size for SHA-512-crypt.
64pub const BLOCK_SIZE_SHA512: usize = 64;
65
66/// The SHA-256-crypt function which outputs a uniformly random byte array.
67///
68/// # Arguments
69/// - `password`: the password to process as a byte vector
70/// - `salt`: the salt value to use as a byte vector
71/// - `params`: the parameters to use
72///
73///   **WARNING: Make sure to compare this value in constant time!**
74pub fn sha256_crypt(password: &[u8], salt: &[u8], params: Params) -> [u8; BLOCK_SIZE_SHA256] {
75    let pw_len = password.len();
76
77    let salt_len = salt.len();
78    let salt = match salt_len {
79        0..=15 => &salt[0..salt_len],
80        _ => &salt[0..16],
81    };
82    let salt_len = salt.len();
83
84    let digest_a = sha256_crypt_intermediate(password, salt);
85
86    // 13.
87    let mut hasher_alt = Sha256::default();
88
89    // 14.
90    for _ in 0..pw_len {
91        hasher_alt.update(password);
92    }
93
94    // 15.
95    let dp = hasher_alt.finalize();
96
97    // 16.
98    // Create byte sequence P.
99    let p_vec = produce_byte_seq(pw_len, &dp);
100
101    // 17.
102    hasher_alt = Sha256::default();
103
104    // 18.
105    // For every character in the password add the entire password.
106    for _ in 0..(16 + digest_a[0] as usize) {
107        hasher_alt.update(salt);
108    }
109
110    // 19.
111    // Finish the digest.
112    let ds = hasher_alt.finalize();
113
114    // 20.
115    // Create byte sequence S.
116    let s_vec = produce_byte_seq(salt_len, &ds);
117
118    let mut digest_c = digest_a;
119    // Repeatedly run the collected hash value through SHA256 to burn
120    // CPU cycles
121    for i in 0..params.rounds {
122        // new hasher
123        let mut hasher = Sha256::default();
124
125        // Add key or last result
126        if (i & 1) != 0 {
127            hasher.update(&p_vec);
128        } else {
129            hasher.update(digest_c);
130        }
131
132        // Add salt for numbers not divisible by 3
133        if i % 3 != 0 {
134            hasher.update(&s_vec);
135        }
136
137        // Add key for numbers not divisible by 7
138        if i % 7 != 0 {
139            hasher.update(&p_vec);
140        }
141
142        // Add key or last result
143        if (i & 1) != 0 {
144            hasher.update(digest_c);
145        } else {
146            hasher.update(&p_vec);
147        }
148
149        digest_c.clone_from_slice(&hasher.finalize());
150    }
151
152    digest_c
153}
154
155/// The SHA-512-crypt function which outputs a uniformly random byte array.
156///
157/// # Arguments
158/// - `password`The password to process as a byte vector
159/// - `salt` - The salt value to use as a byte vector
160/// - `params` - The parameters to use
161///
162///   **WARNING: Make sure to compare this value in constant time!**
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    let p_vec = produce_byte_seq(pw_len, &dp);
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    let s_vec = produce_byte_seq(salt_len, &ds);
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            hasher.update(&p_vec);
217        } else {
218            hasher.update(digest_c);
219        }
220
221        // Add salt for numbers not divisible by 3
222        if i % 3 != 0 {
223            hasher.update(&s_vec);
224        }
225
226        // Add key for numbers not divisible by 7
227        if i % 7 != 0 {
228            hasher.update(&p_vec);
229        }
230
231        // Add key or last result
232        if (i & 1) != 0 {
233            hasher.update(digest_c);
234        } else {
235            hasher.update(&p_vec);
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().as_slice().try_into().unwrap()
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().as_slice().try_into().unwrap()
328}
329
330fn produce_byte_seq(len: usize, fill_from: &[u8]) -> Vec<u8> {
331    let bs = fill_from.len();
332    let mut seq: Vec<u8> = vec![0; len];
333    let mut offset: usize = 0;
334    for _ in 0..(len / bs) {
335        seq[offset..offset + bs].clone_from_slice(fill_from);
336        offset += bs;
337    }
338    let from_slice = &fill_from[..(len % bs)];
339    seq[offset..offset + (len % bs)].clone_from_slice(from_slice);
340    seq
341}