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
17mod errors;
18
19pub use errors::Error;
20
21use blowfish::Blowfish;
22use sha2::{
23 Digest, Sha512,
24 digest::{
25 FixedOutput, MacMarker, Output, OutputSizeUser, Update,
26 crypto_common::{Key, KeyInit, KeySizeUser},
27 typenum::U32,
28 },
29};
30
31#[cfg(feature = "zeroize")]
32use zeroize::Zeroize;
33
34const BHASH_WORDS: usize = 8;
35const BHASH_OUTPUT_SIZE: usize = BHASH_WORDS * 4;
36const BHASH_SEED: &[u8; BHASH_OUTPUT_SIZE] = b"OxychromaticBlowfishSwatDynamite";
37
38#[cfg(feature = "alloc")]
52pub fn bcrypt_pbkdf(
53 passphrase: impl AsRef<[u8]>,
54 salt: &[u8],
55 rounds: u32,
56 output: &mut [u8],
57) -> Result<(), Error> {
58 const STACK_STRIDE: usize = 8;
60
61 let stride = output.len().div_ceil(BHASH_OUTPUT_SIZE);
63
64 let mut vec_buf;
65 let mut stack_buf = [0u8; STACK_STRIDE * BHASH_OUTPUT_SIZE];
66 let generated = if stride > STACK_STRIDE {
67 vec_buf = alloc::vec![0u8; stride * BHASH_OUTPUT_SIZE];
68 &mut vec_buf[..]
69 } else {
70 &mut stack_buf[..stride * BHASH_OUTPUT_SIZE]
71 };
72
73 bcrypt_pbkdf_with_memory(passphrase, salt, rounds, output, generated)
74}
75
76pub fn bcrypt_pbkdf_with_memory(
93 passphrase: impl AsRef<[u8]>,
94 salt: &[u8],
95 rounds: u32,
96 output: &mut [u8],
97 memory: &mut [u8],
98) -> Result<(), Error> {
99 let stride = output.len().div_ceil(BHASH_OUTPUT_SIZE);
100
101 let passphrase = passphrase.as_ref();
103 if passphrase.is_empty() || salt.is_empty() {
104 return Err(errors::Error::InvalidParamLen);
105 } else if rounds == 0 {
106 return Err(errors::Error::InvalidRounds);
107 } else if output.is_empty() || output.len() > BHASH_OUTPUT_SIZE * BHASH_OUTPUT_SIZE {
108 return Err(errors::Error::InvalidOutputLen);
109 } else if memory.len() < stride * BHASH_OUTPUT_SIZE {
110 return Err(errors::Error::InvalidMemoryLen);
111 }
112
113 pbkdf2::pbkdf2::<Bhash>(&Sha512::digest(passphrase), salt, rounds, memory)
115 .expect("Bhash can be initialized with any key length");
116
117 for (i, out_byte) in output.iter_mut().enumerate() {
119 let chunk_num = i % stride;
120 let chunk_index = i / stride;
121 *out_byte = memory[chunk_num * BHASH_OUTPUT_SIZE + chunk_index];
122 }
123
124 Ok(())
125}
126
127fn bhash(sha2_pass: &Output<Sha512>, sha2_salt: &Output<Sha512>) -> Output<Bhash> {
128 let mut blowfish = Blowfish::bc_init_state();
129
130 blowfish.salted_expand_key(sha2_salt, sha2_pass);
131 for _ in 0..64 {
132 blowfish.bc_expand_key(sha2_salt);
133 blowfish.bc_expand_key(sha2_pass);
134 }
135
136 let mut cdata = [0u32; BHASH_WORDS];
137 for i in 0..BHASH_WORDS {
138 cdata[i] = u32::from_be_bytes(BHASH_SEED[i * 4..(i + 1) * 4].try_into().unwrap());
139 }
140
141 for _ in 0..64 {
142 for i in (0..BHASH_WORDS).step_by(2) {
143 let [l, r] = blowfish.bc_encrypt([cdata[i], cdata[i + 1]]);
144 cdata[i] = l;
145 cdata[i + 1] = r;
146 }
147 }
148
149 let mut output = Output::<Bhash>::default();
150 for i in 0..BHASH_WORDS {
151 output[i * 4..(i + 1) * 4].copy_from_slice(&cdata[i].to_le_bytes());
152 }
153
154 output
155}
156
157#[derive(Clone)]
158struct Bhash {
159 sha2_pass: Output<Sha512>,
160 salt: Sha512,
161}
162
163impl MacMarker for Bhash {}
164
165impl KeySizeUser for Bhash {
166 type KeySize = <Sha512 as OutputSizeUser>::OutputSize;
167}
168
169impl KeyInit for Bhash {
170 fn new(key: &Key<Self>) -> Self {
171 Bhash {
172 sha2_pass: *key,
173 salt: Sha512::default(),
174 }
175 }
176}
177
178impl Update for Bhash {
179 fn update(&mut self, data: &[u8]) {
180 Update::update(&mut self.salt, data);
181 }
182}
183
184impl OutputSizeUser for Bhash {
185 type OutputSize = U32;
186}
187
188impl FixedOutput for Bhash {
189 fn finalize_into(mut self, out: &mut Output<Self>) {
190 *out = bhash(&self.sha2_pass, &self.salt.finalize_reset());
191 }
192}
193
194#[cfg(feature = "zeroize")]
195impl Drop for Bhash {
196 fn drop(&mut self) {
197 self.sha2_pass.zeroize();
198 }
199}
200
201#[cfg(test)]
202mod test {
203 use super::bhash;
204 use hex_literal::hex;
205 use sha2::digest::array::Array;
206
207 #[test]
208 fn test_bhash() {
209 struct Test {
210 hpass: [u8; 64],
211 hsalt: [u8; 64],
212 out: [u8; 32],
213 }
214
215 const TEST_VAL: [u8; 64] = hex!(
216 "000102030405060708090a0b0c0d0e0f"
217 "101112131415161718191a1b1c1d1e1f"
218 "202122232425262728292a2b2c2d2e2f"
219 "303132333435363738393a3b3c3d3e3f"
220 );
221
222 let tests = [
223 Test {
224 hpass: [0; 64],
225 hsalt: [0; 64],
226 out: hex!(
227 "460286e972fa833f8b1283ad8fa919fa"
228 "29bde20e23329e774d8422bac0a7926c"
229 ),
230 },
231 Test {
232 hpass: TEST_VAL,
233 hsalt: [0; 64],
234 out: hex!(
235 "b0b229dbc6badef0e1da2527474a8b28"
236 "888f8b061476fe80c32256e1142dd00d"
237 ),
238 },
239 Test {
240 hpass: [0; 64],
241 hsalt: TEST_VAL,
242 out: hex!(
243 "b62b4e367d3157f5c31e4d2cbafb2931"
244 "494d9d3bdd171d55cf799fa4416042e2"
245 ),
246 },
247 Test {
248 hpass: TEST_VAL,
249 hsalt: TEST_VAL,
250 out: hex!(
251 "c6a95fe6413115fb57e99f757498e85d"
252 "a3c6e1df0c3c93aa975c548a344326f8"
253 ),
254 },
255 ];
256
257 for t in tests.iter() {
258 let hpass = Array(t.hpass);
259 let hsalt = Array(t.hsalt);
260 let out = bhash(&hpass, &hsalt);
261 assert_eq!(out[..], t.out[..]);
262 }
263 }
264}