1#![no_std]
2
3#[cfg(feature = "std")]
4extern crate std;
5
6use core::fmt;
7
8#[cfg(test)]
9mod tests;
10
11pub const KEY_SIZE_MAX: usize = 72;
13
14pub const SALT_SIZE: usize = 16;
16
17pub const HASH_SIZE: usize = 23;
19
20#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
22pub struct WorkFactor(u32);
23
24#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
26pub enum BcryptError {
27 Length,
29
30 ZeroByte,
32}
33
34impl fmt::Display for BcryptError {
35 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
36 write!(f, "{}", match self {
37 BcryptError::Length => "password too long",
38 BcryptError::ZeroByte => "password contains a NUL character",
39 })
40 }
41}
42
43#[cfg(feature = "std")]
44impl std::error::Error for BcryptError {}
45
46#[derive(Clone, Debug)]
48pub struct Salt {
49 be: [u32; 4],
50}
51
52impl Salt {
53 pub fn from_bytes(bytes: &[u8; SALT_SIZE]) -> Self {
55 let mut be = [0_u32; 4];
56
57 for i in 0..4 {
58 be[i] = u32::from_be_bytes([
59 bytes[4 * i],
60 bytes[4 * i + 1],
61 bytes[4 * i + 2],
62 bytes[4 * i + 3],
63 ]);
64 }
65
66 Self { be }
67 }
68
69 pub fn to_bytes(&self) -> [u8; SALT_SIZE] {
71 let mut bytes = [0_u8; 16];
72
73 for (b, w) in bytes.chunks_exact_mut(4).zip(self.be.iter().copied()) {
74 b.copy_from_slice(&w.to_be_bytes());
75 }
76
77 bytes
78 }
79}
80
81impl WorkFactor {
82 pub const EXP4: Self = Self(4);
83 pub const EXP5: Self = Self(5);
84 pub const EXP6: Self = Self(6);
85 pub const EXP7: Self = Self(7);
86 pub const EXP8: Self = Self(8);
87 pub const EXP9: Self = Self(9);
88 pub const EXP10: Self = Self(10);
89 pub const EXP11: Self = Self(11);
90 pub const EXP12: Self = Self(12);
91 pub const EXP13: Self = Self(13);
92 pub const EXP14: Self = Self(14);
93 pub const EXP15: Self = Self(15);
94 pub const EXP16: Self = Self(16);
95 pub const EXP17: Self = Self(17);
96 pub const EXP18: Self = Self(18);
97 pub const EXP19: Self = Self(19);
98 pub const EXP20: Self = Self(20);
99 pub const EXP21: Self = Self(21);
100 pub const EXP22: Self = Self(22);
101 pub const EXP23: Self = Self(23);
102 pub const EXP24: Self = Self(24);
103 pub const EXP25: Self = Self(25);
104 pub const EXP26: Self = Self(26);
105 pub const EXP27: Self = Self(27);
106 pub const EXP28: Self = Self(28);
107 pub const EXP29: Self = Self(29);
108 pub const EXP30: Self = Self(30);
109 pub const EXP31: Self = Self(31);
110
111 pub fn exp(log_rounds: u32) -> Option<Self> {
113 if log_rounds >= 4 && log_rounds <= 31 {
114 Some(Self(log_rounds))
115 } else {
116 None
117 }
118 }
119
120 pub const fn log_rounds(self) -> u32 {
122 self.0
123 }
124
125 pub const fn linear_rounds(self) -> u32 {
127 1 << self.0
128 }
129}
130
131const BLF_N: usize = 16;
132
133const BLOWFISH_INITIAL: BlowfishContext = BlowfishContext {
134 s: include!("sbox-init.in"),
135 p: [
136 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
137 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
138 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
139 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
140 0x9216d5d9, 0x8979fb1b,
141 ],
142};
143
144const BCRYPT_MESSAGE: [u32; 6] = {
145 const fn u32_from_be_bytes(bytes: [u8; 4]) -> u32 {
146 (bytes[0] as u32) << 24
147 | (bytes[1] as u32) << 16
148 | (bytes[2] as u32) << 8
149 | (bytes[3] as u32)
150 }
151
152 [
153 u32_from_be_bytes(*b"Orph"),
154 u32_from_be_bytes(*b"eanB"),
155 u32_from_be_bytes(*b"ehol"),
156 u32_from_be_bytes(*b"derS"),
157 u32_from_be_bytes(*b"cryD"),
158 u32_from_be_bytes(*b"oubt"),
159 ]
160};
161
162#[derive(Clone)]
163struct BlowfishContext {
164 s: [[u32; 256]; 4], p: [u32; BLF_N + 2], }
167
168fn read_u32_be<T: Iterator<Item = u8>>(bytes: &mut T) -> u32 {
169 u32::from(bytes.next().unwrap()) << 24
170 | u32::from(bytes.next().unwrap()) << 16
171 | u32::from(bytes.next().unwrap()) << 8
172 | u32::from(bytes.next().unwrap())
173}
174
175fn f(c: &BlowfishContext, x: u32) -> u32 {
176 let [b0, b1, b2, b3] = x.to_be_bytes();
177 let h = c.s[0][usize::from(b0)].wrapping_add(c.s[1][usize::from(b1)]);
178 (h ^ c.s[2][usize::from(b2)]).wrapping_add(c.s[3][usize::from(b3)])
179}
180
181fn blowfish_encipher(c: &BlowfishContext, mut l: u32, mut r: u32) -> (u32, u32) {
182 for i in (0..16).step_by(2) {
183 l ^= c.p[i];
184 r ^= f(c, l);
185 r ^= c.p[i + 1];
186 l ^= f(c, r);
187 }
188
189 l ^= c.p[16];
190 r ^= c.p[17];
191
192 (r, l)
193}
194
195struct KeyCycle<'a> {
197 key: &'a [u8],
198 index: usize,
199}
200
201impl<'a> Iterator for KeyCycle<'a> {
202 type Item = u8;
203
204 fn next(&mut self) -> Option<u8> {
205 if self.index == self.key.len() {
206 self.index = 0;
207 return Some(0);
208 }
209
210 let result = self.key[self.index];
211 self.index += 1;
212 Some(result)
213 }
214}
215
216fn blowfish_expandstate_key(c: &mut BlowfishContext, key: &[u8]) {
217 let mut key_cycle = KeyCycle { key, index: 0 };
218
219 for pi in &mut c.p {
220 let temp = read_u32_be(&mut key_cycle);
221 *pi ^= temp;
222 }
223}
224
225fn blowfish_expandstate_data(c: &mut BlowfishContext, data: &[u32; 4]) {
226 let mut datal = 0_u32;
227 let mut datar = 0_u32;
228
229 for i in (0..BLF_N + 2).step_by(2) {
230 datal ^= data[i % 4];
231 datar ^= data[i % 4 + 1];
232 let (nextl, nextr) = blowfish_encipher(c, datal, datar);
233 datal = nextl;
234 datar = nextr;
235
236 c.p[i] = datal;
237 c.p[i + 1] = datar;
238 }
239
240 for i in 0..4 {
241 for k in (0..256).step_by(2) {
242 datal ^= data[(k + 2) % 4];
243 datar ^= data[(k + 2) % 4 + 1];
244 let (nextl, nextr) = blowfish_encipher(c, datal, datar);
245 datal = nextl;
246 datar = nextr;
247
248 c.s[i][k] = datal;
249 c.s[i][k + 1] = datar;
250 }
251 }
252}
253
254fn blowfish_expandstate_data0(c: &mut BlowfishContext) {
255 let mut datal = 0_u32;
256 let mut datar = 0_u32;
257
258 for i in (0..BLF_N + 2).step_by(2) {
259 let (nextl, nextr) = blowfish_encipher(c, datal, datar);
260 datal = nextl;
261 datar = nextr;
262
263 c.p[i] = datal;
264 c.p[i + 1] = datar;
265 }
266
267 for i in 0..4 {
268 for k in (0..256).step_by(2) {
269 let (nextl, nextr) = blowfish_encipher(c, datal, datar);
270 datal = nextl;
271 datar = nextr;
272
273 c.s[i][k] = datal;
274 c.s[i][k + 1] = datar;
275 }
276 }
277}
278
279pub fn bcrypt(key: &[u8], salt: &Salt, work_factor: WorkFactor) -> Result<[u8; HASH_SIZE], BcryptError> {
281 if key.len() > KEY_SIZE_MAX {
282 return Err(BcryptError::Length);
283 }
284
285 if key.contains(&b'\0') {
286 return Err(BcryptError::ZeroByte);
287 }
288
289 let mut state = BLOWFISH_INITIAL;
290
291 blowfish_expandstate_key(&mut state, key);
292 blowfish_expandstate_data(&mut state, &salt.be);
293
294 for _ in 0..work_factor.linear_rounds() {
295 blowfish_expandstate_key(&mut state, key);
296 blowfish_expandstate_data0(&mut state);
297
298 for i in 0..(BLF_N + 2) {
299 state.p[i] ^= salt.be[i % 4];
300 }
301
302 blowfish_expandstate_data0(&mut state);
303 }
304
305 let mut cdata = BCRYPT_MESSAGE;
306
307 for _ in 0..64 {
308 for i in (0..BCRYPT_MESSAGE.len()).step_by(2) {
309 let (l, r) = blowfish_encipher(&state, cdata[i], cdata[i + 1]);
310 cdata[i] = l;
311 cdata[i + 1] = r;
312 }
313 }
314
315 let mut result = [0_u8; 23];
316
317 for (b, w) in result.chunks_exact_mut(4).zip(cdata.iter().copied()) {
318 b.copy_from_slice(&w.to_be_bytes());
319 }
320
321 result[20..].copy_from_slice(&cdata[5].to_be_bytes()[0..3]);
322
323 Ok(result)
324}