bcrypt_only/
lib.rs

1#![no_std]
2
3#[cfg(feature = "std")]
4extern crate std;
5
6use core::fmt;
7
8#[cfg(test)]
9mod tests;
10
11/// The maximum number of bytes in a bcrypt key.
12pub const KEY_SIZE_MAX: usize = 72;
13
14/// The number of bytes in a bcrypt salt.
15pub const SALT_SIZE: usize = 16;
16
17/// The number of bytes in a bcrypt hash.
18pub const HASH_SIZE: usize = 23;
19
20/// A bcrypt work factor.
21#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
22pub struct WorkFactor(u32);
23
24/// A bcrypt hashing error.
25#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
26pub enum BcryptError {
27	/// The key was longer than the limit of 72 bytes.
28	Length,
29
30	/// The key contained a 0 byte.
31	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/// A bcrypt salt.
47#[derive(Clone, Debug)]
48pub struct Salt {
49	be: [u32; 4],
50}
51
52impl Salt {
53	/// Creates a bcrypt salt from any 16 bytes.
54	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	/// Gets the bytes making up a bcrypt salt.
70	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	/// Creates a bcrypt work factor from a typical base-2 exponent between 4 and 31 (inclusive). The number of rounds is 2\*\*`log_rounds`.
112	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	/// The base-2 logarithm of the number of rounds represented by this work factor.
121	pub const fn log_rounds(self) -> u32 {
122		self.0
123	}
124
125	/// The number of rounds represented by this work factor.
126	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],  // S-Boxes
165	p: [u32; BLF_N + 2], // subkeys
166}
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
195/// An iterator yielding the bytes of a key, then 0, forever.
196struct 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
279/// Hashes a key and salt with bcrypt according to a work factor. The key can’t be longer than 72 bytes and can’t contain a 0 byte.
280pub 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}