mini_rcrypt/
bcrypt.rs

1use crate::{
2    base64::{decode_base64, encode_base64},
3    utils::{generate_random_numbers, get_byte_from_char, get_byte_from_number},
4    BCRYPT_SALT_LEN, BF_CRYPT_CIPHERTEXT, BLOWFISH_NUM_ROUNDS, P_ORIG, S_ORIG,
5};
6
7#[allow(non_snake_case)]
8pub struct BCrypt {
9    pub P: Vec<isize>,
10    pub S: Vec<isize>,
11}
12
13impl BCrypt {
14    fn new() -> Self {
15        Self {
16            P: vec![],
17            S: vec![],
18        }
19    }
20
21    fn init_key(&mut self) -> () {
22        self.P = P_ORIG.clone().to_vec();
23        self.S = S_ORIG.clone().to_vec();
24    }
25
26    fn encipher(&mut self, mut lr: Vec<isize>, off: usize) -> Vec<isize> {
27        let mut i;
28        let mut n;
29        let mut l = lr[off];
30        let mut r = lr[off + 1];
31
32        l ^= self.P[0];
33
34        i = 0;
35
36        while i <= BLOWFISH_NUM_ROUNDS - 2 {
37            // Feistel substitution on left word
38            n = self.S[(l >> 24) as usize & 0xff];
39            n = n
40                .checked_add(self.S[0x100 | ((l >> 16) as usize & 0xff)])
41                .unwrap_or(0);
42            n ^= self.S[0x200 | ((l >> 8) as usize & 0xff)];
43            n = n
44                .checked_add(self.S[0x300 | (l & 0xff) as usize])
45                .unwrap_or(0);
46
47            i = i + 1;
48
49            r ^= n ^ self.P[i];
50
51            // Feistel substitution on right word
52            n = self.S[(r >> 24) as usize & 0xff];
53            n = n
54                .checked_add(self.S[0x100 | ((r >> 16) as usize & 0xff)])
55                .unwrap_or(0);
56            n ^= self.S[0x200 | ((r >> 8) as usize & 0xff)];
57            n = n
58                .checked_add(self.S[0x300 | (r & 0xff) as usize])
59                .unwrap_or(0);
60
61            i = i + 1;
62
63            l ^= n ^ self.P[i];
64        }
65
66        lr[off] = r ^ self.P[BLOWFISH_NUM_ROUNDS + 1];
67        lr[off + 1] = l;
68
69        lr
70    }
71
72    fn streamtoword(data: Vec<isize>, mut offp: Vec<usize>) -> (isize, Vec<usize>) {
73        let mut i = 0;
74        let mut word = 0;
75        let mut off = offp[0];
76
77        while i < 4 {
78            word = (word << 8) | (data[off] & 0xff);
79            off = (off + 1) % data.len();
80
81            i = i + 1;
82        }
83
84        offp[0] = off;
85
86        return (word, offp);
87    }
88
89    fn key(&mut self, key: Vec<isize>) {
90        let mut i;
91        let mut koffp = vec![0];
92        let mut lr = vec![0, 0];
93        let plen = self.P.len();
94        let slen = self.S.len();
95
96        i = 0;
97
98        while i < plen {
99            let (word, offp) = BCrypt::streamtoword(key.clone(), koffp);
100
101            self.P[i] = self.P[i] ^ word;
102            koffp = offp;
103
104            i = i + 1;
105        }
106
107        i = 0;
108
109        while i < plen {
110            lr = self.encipher(lr.clone(), 0);
111            self.P[i] = lr[0];
112            self.P[i + 1] = lr[1];
113
114            i = i + 2;
115        }
116
117        i = 0;
118
119        while i < slen {
120            lr = self.encipher(lr, 0);
121            self.S[i] = lr[0];
122            self.S[i + 1] = lr[1];
123
124            i = i + 2;
125        }
126    }
127
128    fn ekskey(&mut self, data: Vec<isize>, key: Vec<isize>) {
129        let mut i;
130        let mut koffp = vec![0];
131        let mut doffp = vec![0];
132        let mut lr = vec![0, 0];
133        let plen = self.P.len();
134        let slen = self.S.len();
135
136        i = 0;
137
138        while i < plen {
139            let (word, offp) = BCrypt::streamtoword(key.clone(), koffp);
140
141            self.P[i] = self.P[i] ^ word;
142            koffp = offp;
143
144            i = i + 1;
145        }
146
147        i = 0;
148
149        while i < plen {
150            let (word, offp) = BCrypt::streamtoword(data.clone(), doffp.clone());
151
152            lr[0] ^= word;
153            doffp = offp;
154
155            let (word, offp) = BCrypt::streamtoword(data.clone(), doffp.clone());
156
157            lr[1] ^= word;
158            doffp = offp;
159
160            lr = self.encipher(lr, 0);
161
162            self.P[i] = lr[0];
163            self.P[i + 1] = lr[1];
164
165            i = i + 2;
166        }
167
168        i = 0;
169
170        while i < slen {
171            let (word, offp) = BCrypt::streamtoword(data.clone(), doffp.clone());
172
173            lr[0] ^= word;
174            doffp = offp;
175
176            let (word, offp) = BCrypt::streamtoword(data.clone(), doffp.clone());
177
178            lr[1] ^= word;
179            doffp = offp;
180
181            lr = self.encipher(lr, 0);
182
183            self.S[i] = lr[0];
184            self.S[i + 1] = lr[1];
185
186            i = i + 2;
187        }
188    }
189
190    fn crypt_raw<'a>(
191        &mut self,
192        password: Vec<isize>,
193        salt: Vec<isize>,
194        log_rounds: usize,
195        data: Vec<isize>,
196    ) -> Result<Vec<isize>, &'a str> {
197        let mut i;
198        let mut j;
199        let mut cdata = data;
200        let clen = cdata.len();
201        let mut ret: Vec<isize> = vec![];
202
203        let rounds = 1 << log_rounds;
204
205        if log_rounds < 4 || log_rounds > 30 {
206            return Err("Bad number of rounds");
207        }
208
209        if salt.len() != BCRYPT_SALT_LEN as usize {
210            return Err("Bad salt length");
211        }
212
213        self.init_key();
214        self.ekskey(salt.clone(), password.clone());
215
216        i = 0;
217
218        while i != rounds {
219            self.key(password.clone());
220            self.key(salt.clone());
221
222            i = i + 1;
223        }
224
225        i = 0;
226        j = 0;
227
228        while i < 64 {
229            while j < (clen >> 1) {
230                cdata = self.encipher(cdata.clone(), j << 1);
231
232                j = j + 1;
233            }
234
235            i = i + 1;
236        }
237
238        i = 0;
239
240        while i < clen {
241            ret.push(get_byte_from_number((cdata[i] >> 24) & 0xff));
242            ret.push(get_byte_from_number((cdata[i] >> 16) & 0xff));
243            ret.push(get_byte_from_number((cdata[i] >> 8) & 0xff));
244            ret.push(get_byte_from_number(cdata[i] & 0xff));
245
246            i = i + 1;
247        }
248
249        Ok(ret)
250    }
251
252    fn password_to_bytes<'a>(password: String) -> Result<Vec<isize>, &'a str> {
253        let mut passwordb: Vec<isize> = vec![];
254
255        for c in password.chars() {
256            let code = c as isize;
257
258            if code < 128 {
259                passwordb.push(code);
260            } else if code > 127 && code < 2048 {
261                passwordb.push((code >> 6) | 192);
262                passwordb.push((code & 63) | 128);
263            } else if code >= 55296 && code <= 56319 {
264                let next_code = password.chars().nth(1).unwrap_or('\0') as isize;
265
266                if next_code < 56320 || next_code > 57343 {
267                    return Err("utf-16 Decoding error: trail surrogate not in the range of 0xdc00 through 0xdfff");
268                }
269
270                let decoded = ((code - 55296) << 10) + (next_code - 56320) + 65536;
271                passwordb.push((decoded >> 18) | 240);
272                passwordb.push(((decoded >> 12) & 63) | 128);
273                passwordb.push(((decoded >> 6) & 63) | 128);
274                passwordb.push((decoded & 63) | 128);
275            } else {
276                passwordb.push((code >> 12) | 224);
277                passwordb.push(((code >> 6) & 63) | 128);
278                passwordb.push((code & 63) | 128);
279            }
280        }
281
282        Ok(passwordb)
283    }
284
285    pub fn hashpw<'a>(password: String, salt: String) -> Result<String, &'a str> {
286        let mut bcrypt = BCrypt::new();
287        let real_salt: String;
288        let passwordb: Vec<isize>;
289        let saltb: Vec<isize>;
290        let hashed: Vec<isize>;
291        let mut minor: char = '0';
292        let rounds: usize;
293        let off;
294        let mut result: String = "".into();
295
296        if salt.chars().nth(0) != Some('$') || salt.chars().nth(1) != Some('2') {
297            return Err("Invalid salt version");
298        }
299
300        if salt.chars().nth(2) == Some('$') {
301            off = 3;
302        } else {
303            match salt.chars().nth(2) {
304                Some(c) => {
305                    minor = c;
306
307                    if minor != 'a' || salt.chars().nth(3) != Some('$') {
308                        return Err("Invalid salt revision");
309                    }
310
311                    off = 4;
312                }
313                None => return Err("Invalid salt revision"),
314            }
315        }
316
317        if salt.chars().nth(off + 2) > Some('$') {
318            return Err("Missing salt rounds");
319        }
320
321        match salt[off..off + 2].parse::<usize>() {
322            Ok(x) => rounds = x,
323            Err(_) => return Err("Missing salt rounds"),
324        }
325
326        real_salt = salt[off + 3..off + 25].to_owned();
327        let password_ = format!("{}{}", password, if minor >= 'a' { "\0" } else { "" });
328
329        match BCrypt::password_to_bytes(password_) {
330            Ok(x) => passwordb = x,
331            Err(err) => return Err(err),
332        }
333
334        match decode_base64(real_salt, BCRYPT_SALT_LEN as usize) {
335            Ok(x) => saltb = x,
336            Err(err) => return Err(err),
337        }
338
339        match bcrypt.crypt_raw(
340            passwordb,
341            saltb.clone(),
342            rounds,
343            BF_CRYPT_CIPHERTEXT.to_vec().clone(),
344        ) {
345            Ok(x) => hashed = x,
346            Err(err) => return Err(err),
347        }
348
349        result.push_str("$2");
350
351        if minor >= 'a' {
352            result.push(minor);
353        }
354
355        result.push('$');
356
357        if rounds < 10 {
358            result.push('0');
359        }
360
361        if rounds > 30 {
362            return Err("Rounds exceeds maximum (30)");
363        }
364
365        result.push_str(rounds.to_string().as_str());
366        result.push('$');
367
368        match encode_base64(saltb.clone(), saltb.len()) {
369            Ok(x) => result.push_str(x.as_str()),
370            Err(err) => return Err(err),
371        }
372
373        match encode_base64(hashed, BF_CRYPT_CIPHERTEXT.to_vec().clone().len() * 4 - 1) {
374            Ok(x) => result.push_str(x.as_str()),
375            Err(err) => return Err(err),
376        }
377
378        Ok(result)
379    }
380
381    pub fn gensalt<'a>(rounds: u32) -> Result<String, &'a str> {
382        if rounds < 4 || rounds > 30 {
383            return Err("Rounds excededs maximum (30)!");
384        }
385
386        let mut output: String = "".into();
387
388        output.push_str("$2a$");
389
390        if rounds < 10 {
391            output.push('0');
392        }
393
394        output.push_str(rounds.to_string().as_str());
395        output.push('$');
396
397        match encode_base64(generate_random_numbers(), BCRYPT_SALT_LEN as usize) {
398            Ok(x) => output.push_str(x.as_str()),
399            Err(err) => return Err(err),
400        }
401
402        Ok(output)
403    }
404
405    pub fn checkpw(plaintext: String, hashed: String) -> bool {
406        let off;
407
408        if hashed.chars().nth(0) != Some('$') || hashed.chars().nth(1) != Some('2') {
409            return false;
410        }
411
412        if hashed.chars().nth(2) == Some('$') {
413            off = 3;
414        } else {
415            match hashed.chars().nth(2) {
416                Some(minor) => {
417                    if (minor != 'a' && minor != 'b') || hashed.chars().nth(3) != Some('$') {
418                        return false;
419                    }
420
421                    off = 4;
422                }
423                None => return false,
424            }
425        }
426
427        let salt = hashed[..off + 25].to_owned();
428
429        match BCrypt::hashpw(plaintext, salt) {
430            Ok(try_pass) => {
431                let mut ret = 0;
432
433                let mut i = 0;
434
435                while i < hashed.len() {
436                    match (hashed.chars().nth(i), try_pass.chars().nth(i)) {
437                        (Some(x), Some(y)) => ret |= get_byte_from_char(x) ^ get_byte_from_char(y),
438                        _ => return false,
439                    }
440
441                    i = i + 1;
442                }
443
444                return ret == 0;
445            }
446            Err(_) => return false,
447        }
448    }
449}