1#![no_std]
8#![doc = include_str!("../README.md")]
9#![doc(
10 html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg",
11 html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg"
12)]
13
14#[cfg(feature = "alloc")]
15extern crate alloc;
16#[cfg(feature = "std")]
17extern crate std;
18
19mod errors;
20
21pub use errors::Error;
22
23use blowfish::Blowfish;
24use sha2::{
25 digest::{
26 crypto_common::{Key, KeyInit, KeySizeUser},
27 generic_array::typenum::U32,
28 FixedOutput, MacMarker, Output, OutputSizeUser, Update,
29 },
30 Digest, Sha512,
31};
32
33#[cfg(feature = "zeroize")]
34use zeroize::Zeroize;
35
36const BHASH_WORDS: usize = 8;
37const BHASH_OUTPUT_SIZE: usize = BHASH_WORDS * 4;
38const BHASH_SEED: &[u8; BHASH_OUTPUT_SIZE] = b"OxychromaticBlowfishSwatDynamite";
39
40fn bhash(sha2_pass: &Output<Sha512>, sha2_salt: &Output<Sha512>) -> Output<Bhash> {
41 let mut blowfish = Blowfish::bc_init_state();
42
43 blowfish.salted_expand_key(sha2_salt, sha2_pass);
44 for _ in 0..64 {
45 blowfish.bc_expand_key(sha2_salt);
46 blowfish.bc_expand_key(sha2_pass);
47 }
48
49 let mut cdata = [0u32; BHASH_WORDS];
50 for i in 0..BHASH_WORDS {
51 cdata[i] = u32::from_be_bytes(BHASH_SEED[i * 4..(i + 1) * 4].try_into().unwrap());
52 }
53
54 for _ in 0..64 {
55 for i in (0..BHASH_WORDS).step_by(2) {
56 let [l, r] = blowfish.bc_encrypt([cdata[i], cdata[i + 1]]);
57 cdata[i] = l;
58 cdata[i + 1] = r;
59 }
60 }
61
62 let mut output = Output::<Bhash>::default();
63 for i in 0..BHASH_WORDS {
64 output[i * 4..(i + 1) * 4].copy_from_slice(&cdata[i].to_le_bytes());
65 }
66
67 output
68}
69
70#[derive(Clone)]
71struct Bhash {
72 sha2_pass: Output<Sha512>,
73 salt: Sha512,
74}
75
76impl MacMarker for Bhash {}
77
78impl KeySizeUser for Bhash {
79 type KeySize = <Sha512 as OutputSizeUser>::OutputSize;
80}
81
82impl KeyInit for Bhash {
83 fn new(key: &Key<Self>) -> Self {
84 Bhash {
85 sha2_pass: *key,
86 salt: Sha512::default(),
87 }
88 }
89}
90
91impl Update for Bhash {
92 fn update(&mut self, data: &[u8]) {
93 Update::update(&mut self.salt, data);
94 }
95}
96
97impl OutputSizeUser for Bhash {
98 type OutputSize = U32;
99}
100
101impl FixedOutput for Bhash {
102 fn finalize_into(mut self, out: &mut Output<Self>) {
103 *out = bhash(&self.sha2_pass, &self.salt.finalize_reset());
104 }
105}
106
107#[cfg(feature = "zeroize")]
108impl Drop for Bhash {
109 fn drop(&mut self) {
110 self.sha2_pass.zeroize();
111 }
112}
113
114#[cfg(feature = "alloc")]
128pub fn bcrypt_pbkdf(
129 passphrase: impl AsRef<[u8]>,
130 salt: &[u8],
131 rounds: u32,
132 output: &mut [u8],
133) -> Result<(), Error> {
134 const STACK_STRIDE: usize = 8;
136
137 let stride = (output.len() + BHASH_OUTPUT_SIZE - 1) / BHASH_OUTPUT_SIZE;
139
140 let mut vec_buf;
141 let mut stack_buf = [0u8; STACK_STRIDE * BHASH_OUTPUT_SIZE];
142 let generated = if stride > STACK_STRIDE {
143 vec_buf = alloc::vec![0u8; stride * BHASH_OUTPUT_SIZE];
144 &mut vec_buf[..]
145 } else {
146 &mut stack_buf[..stride * BHASH_OUTPUT_SIZE]
147 };
148
149 bcrypt_pbkdf_with_memory(passphrase, salt, rounds, output, generated)
150}
151
152pub fn bcrypt_pbkdf_with_memory(
169 passphrase: impl AsRef<[u8]>,
170 salt: &[u8],
171 rounds: u32,
172 output: &mut [u8],
173 memory: &mut [u8],
174) -> Result<(), Error> {
175 let stride = (output.len() + BHASH_OUTPUT_SIZE - 1) / BHASH_OUTPUT_SIZE;
176
177 let passphrase = passphrase.as_ref();
179 if passphrase.is_empty() || salt.is_empty() {
180 return Err(errors::Error::InvalidParamLen);
181 } else if rounds == 0 {
182 return Err(errors::Error::InvalidRounds);
183 } else if output.is_empty() || output.len() > BHASH_OUTPUT_SIZE * BHASH_OUTPUT_SIZE {
184 return Err(errors::Error::InvalidOutputLen);
185 } else if memory.len() < stride * BHASH_OUTPUT_SIZE {
186 return Err(errors::Error::InvalidMemoryLen);
187 }
188
189 pbkdf2::pbkdf2::<Bhash>(&Sha512::digest(passphrase), salt, rounds, memory)
191 .expect("Bhash can be initialized with any key length");
192
193 for (i, out_byte) in output.iter_mut().enumerate() {
195 let chunk_num = i % stride;
196 let chunk_index = i / stride;
197 *out_byte = memory[chunk_num * BHASH_OUTPUT_SIZE + chunk_index];
198 }
199
200 Ok(())
201}
202
203#[cfg(test)]
204mod test {
205 use super::bhash;
206 use hex_literal::hex;
207 use sha2::digest::generic_array::GenericArray;
208
209 #[test]
210 fn test_bhash() {
211 struct Test {
212 hpass: [u8; 64],
213 hsalt: [u8; 64],
214 out: [u8; 32],
215 }
216
217 const TEST_VAL: [u8; 64] = hex!(
218 "000102030405060708090a0b0c0d0e0f"
219 "101112131415161718191a1b1c1d1e1f"
220 "202122232425262728292a2b2c2d2e2f"
221 "303132333435363738393a3b3c3d3e3f"
222 );
223
224 let tests = [
225 Test {
226 hpass: [0; 64],
227 hsalt: [0; 64],
228 out: hex!(
229 "460286e972fa833f8b1283ad8fa919fa"
230 "29bde20e23329e774d8422bac0a7926c"
231 ),
232 },
233 Test {
234 hpass: TEST_VAL,
235 hsalt: [0; 64],
236 out: hex!(
237 "b0b229dbc6badef0e1da2527474a8b28"
238 "888f8b061476fe80c32256e1142dd00d"
239 ),
240 },
241 Test {
242 hpass: [0; 64],
243 hsalt: TEST_VAL,
244 out: hex!(
245 "b62b4e367d3157f5c31e4d2cbafb2931"
246 "494d9d3bdd171d55cf799fa4416042e2"
247 ),
248 },
249 Test {
250 hpass: TEST_VAL,
251 hsalt: TEST_VAL,
252 out: hex!(
253 "c6a95fe6413115fb57e99f757498e85d"
254 "a3c6e1df0c3c93aa975c548a344326f8"
255 ),
256 },
257 ];
258
259 for t in tests.iter() {
260 let hpass = GenericArray::from_slice(&t.hpass);
261 let hsalt = GenericArray::from_slice(&t.hsalt);
262 let out = bhash(hpass, hsalt);
263 assert_eq!(out[..], t.out[..]);
264 }
265 }
266}