physis/blowfish/
mod.rs

1// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4use std::io::{Cursor, Write};
5
6mod constants;
7use constants::{BLOWFISH_P, BLOWFISH_S};
8
9const ROUNDS: usize = 16;
10const KEYBITS: u32 = 64u32 >> 3;
11
12/// Implementation of the Blowfish block cipher, specialized for encrypting and decrypting SqexArg - the technique used to encrypt game arguments by the launcher.
13///
14/// # Example
15///
16/// ```
17/// # use physis::blowfish::Blowfish;
18/// let key = b"abcdefgh";
19/// let data = b"foobar  ";
20///
21/// let fish = Blowfish::new(key);
22/// let encrypted = fish.encrypt(data).unwrap();
23/// let decrypted = fish.decrypt(&encrypted).unwrap();
24/// # assert_eq!(data, &decrypted[..])
25/// ```
26pub struct Blowfish {
27    p: [u32; 18],
28    s: [[u32; 256]; 4],
29}
30
31impl Blowfish {
32    /// Initializes a new Blowfish session with a key.
33    pub fn new(key: &[u8]) -> Blowfish {
34        let mut s = Self {
35            p: BLOWFISH_P,
36            s: BLOWFISH_S,
37        };
38
39        let mut j = 0usize;
40        for i in 0..ROUNDS + 2 {
41            let mut data = 0u32;
42            for _ in 0..4 {
43                data = (data << 8) | (key[j] as u32);
44                j += 1;
45
46                if j >= (KEYBITS as usize) {
47                    j = 0;
48                }
49            }
50
51            s.p[i] ^= data;
52        }
53
54        let mut l = 0u32;
55        let mut r = 0u32;
56
57        for i in (0..18).step_by(2) {
58            let (l_new, r_new) = s.encrypt_pair(l, r);
59            s.p[i] = l_new;
60            s.p[i + 1] = r_new;
61
62            l = l_new;
63            r = r_new;
64        }
65
66        for i in 0..4 {
67            for j in (0..256).step_by(2) {
68                let (l_new, r_new) = s.encrypt_pair(l, r);
69                s.s[i][j] = l_new;
70                s.s[i][j + 1] = r_new;
71
72                l = l_new;
73                r = r_new;
74            }
75        }
76
77        s
78    }
79
80    /// Encrypts a block of data. If the encryption for any reason fails, returns None.
81    pub fn encrypt(&self, data: &[u8]) -> Option<Vec<u8>> {
82        let padded_data = Blowfish::pad_buffer(data);
83
84        let mut cursor = Cursor::new(Vec::with_capacity(padded_data.len()));
85
86        for i in (0..padded_data.len()).step_by(8) {
87            let l_bytes: [u8; 4] = padded_data[i..i + 4].try_into().ok()?;
88            let r_bytes: [u8; 4] = padded_data[i + 4..i + 8].try_into().ok()?;
89
90            let (l, r) =
91                self.encrypt_pair(u32::from_le_bytes(l_bytes), u32::from_le_bytes(r_bytes));
92
93            cursor.write_all(u32::to_le_bytes(l).as_slice()).ok()?;
94            cursor.write_all(u32::to_le_bytes(r).as_slice()).ok()?;
95        }
96
97        Some(cursor.into_inner())
98    }
99
100    fn pad_buffer(data: &[u8]) -> Vec<u8> {
101        let mut padded_length = data.len();
102        if data.len() % 8 != 0 {
103            padded_length = data.len() + (8 - (data.len() % 8));
104        }
105
106        let mut vec = vec![0; padded_length];
107        vec[..data.len()].clone_from_slice(data);
108
109        vec
110    }
111
112    /// Decrypts a block of data. If the decryption fails due to buffer overflow issues, will return
113    /// None - but this does not indicate that the wrong key was used.
114    pub fn decrypt(&self, data: &[u8]) -> Option<Vec<u8>> {
115        let padded_data = Blowfish::pad_buffer(data);
116
117        let mut buffer = Vec::with_capacity(padded_data.len());
118        let mut cursor = Cursor::new(&mut buffer);
119
120        for i in (0..padded_data.len()).step_by(8) {
121            let l_bytes: [u8; 4] = padded_data[i..i + 4].try_into().ok()?;
122            let r_bytes: [u8; 4] = padded_data[i + 4..i + 8].try_into().ok()?;
123
124            let (l, r) =
125                self.decrypt_pair(u32::from_le_bytes(l_bytes), u32::from_le_bytes(r_bytes));
126
127            cursor.write_all(u32::to_le_bytes(l).as_slice()).ok()?;
128            cursor.write_all(u32::to_le_bytes(r).as_slice()).ok()?;
129        }
130
131        Some(buffer)
132    }
133
134    /// Calculates the F-function for `x`.
135    fn f(&self, x: u32) -> u32 {
136        let a = self.s[0][(x >> 24) as usize];
137        let b = self.s[1][((x >> 16) & 0xFF) as usize];
138        let c = self.s[2][((x >> 8) & 0xFF) as usize];
139        let d = self.s[3][(x & 0xFF) as usize];
140
141        (a.wrapping_add(b) ^ c).wrapping_add(d)
142    }
143
144    fn encrypt_pair(&self, mut l: u32, mut r: u32) -> (u32, u32) {
145        for i in (0..ROUNDS).step_by(2) {
146            l ^= self.p[i];
147            r ^= self.f(l);
148            r ^= self.p[i + 1];
149            l ^= self.f(r);
150        }
151
152        (r ^ self.p[17], l ^ self.p[16])
153    }
154
155    fn decrypt_pair(&self, mut l: u32, mut r: u32) -> (u32, u32) {
156        for i in (2..ROUNDS + 1).step_by(2).rev() {
157            l ^= self.p[i + 1];
158            r ^= self.f(l);
159            r ^= self.p[i];
160            l ^= self.f(r);
161        }
162
163        (r ^ self.p[0], l ^ self.p[1])
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170
171    #[test]
172    fn test_encrypt_decrypt() {
173        let blowfish = Blowfish::new(b"test_case");
174
175        let expected_encrypted = [
176            63, 149, 97, 229, 5, 35, 46, 128, 194, 107, 69, 132, 85, 202, 2, 126,
177        ];
178
179        assert_eq!(
180            blowfish.encrypt(b"hello, world!").unwrap(),
181            expected_encrypted
182        );
183        assert_eq!(
184            String::from_utf8(blowfish.decrypt(&expected_encrypted).unwrap()).unwrap(),
185            "hello, world!\0\0\0"
186        );
187    }
188}