1
Fork 0
mirror of https://github.com/redstrate/Physis.git synced 2025-04-23 05:07:46 +00:00
physis/src/blowfish.rs

180 lines
5.2 KiB
Rust
Raw Normal View History

// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-or-later
2022-08-16 11:52:07 -04:00
use std::io::{Cursor, Write};
2022-07-19 19:29:41 -04:00
use crate::blowfish_constants::{BLOWFISH_P, BLOWFISH_S};
2022-07-19 19:29:41 -04:00
const ROUNDS: usize = 16;
const KEYBITS: u32 = 64u32 >> 3;
/// Implementation of the Blowfish block cipher, specialized for encrypting and decrypting SqexArg - the technique used to encrypt game arguments by the launcher.
///
/// # Example
///
/// ```
/// # use physis::blowfish::Blowfish;
/// let key = b"abcdefgh";
2023-09-22 18:46:58 -04:00
/// let data = b"foobar ";
///
/// let fish = Blowfish::new(key);
/// let encrypted = fish.encrypt(data).unwrap();
/// let decrypted = fish.decrypt(&encrypted).unwrap();
2023-09-22 18:46:58 -04:00
/// # assert_eq!(data, &decrypted[..])
/// ```
2022-07-19 19:29:41 -04:00
pub struct Blowfish {
p: [u32; 18],
s: [[u32; 256]; 4],
}
impl Blowfish {
/// Initializes a new Blowfish session with a key.
2022-07-19 19:29:41 -04:00
pub fn new(key: &[u8]) -> Blowfish {
let mut s = Self {
p: BLOWFISH_P,
s: BLOWFISH_S,
};
let mut j = 0usize;
for i in 0..ROUNDS + 2 {
let mut data = 0u32;
for _ in 0..4 {
data = (data << 8) | (key[j] as u32);
2022-07-19 19:29:41 -04:00
j += 1;
if j >= (KEYBITS as usize) {
j = 0;
}
}
s.p[i] ^= data;
}
let mut l = 0u32;
let mut r = 0u32;
for i in (0..18).step_by(2) {
let (l_new, r_new) = s.encrypt_pair(l, r);
s.p[i] = l_new;
s.p[i + 1] = r_new;
l = l_new;
r = r_new;
}
for i in 0..4 {
for j in (0..256).step_by(2) {
let (l_new, r_new) = s.encrypt_pair(l, r);
s.s[i][j] = l_new;
s.s[i][j + 1] = r_new;
l = l_new;
r = r_new;
}
}
s
}
/// Encrypts a block of data. If the encryption for any reason fails, returns None.
pub fn encrypt(&self, data: &[u8]) -> Option<Vec<u8>> {
let padded_data = Blowfish::pad_buffer(data);
let mut cursor = Cursor::new(Vec::with_capacity(padded_data.len()));
for i in (0..padded_data.len()).step_by(8) {
let l_bytes: [u8; 4] = padded_data[i..i + 4].try_into().ok()?;
let r_bytes: [u8; 4] = padded_data[i + 4..i + 8].try_into().ok()?;
2022-08-16 11:52:07 -04:00
let (l, r) =
self.encrypt_pair(u32::from_le_bytes(l_bytes), u32::from_le_bytes(r_bytes));
2022-07-19 19:29:41 -04:00
2022-08-16 11:50:18 -04:00
cursor.write_all(u32::to_le_bytes(l).as_slice()).ok()?;
cursor.write_all(u32::to_le_bytes(r).as_slice()).ok()?;
2022-07-19 19:29:41 -04:00
}
Some(cursor.into_inner())
}
fn pad_buffer(data: &[u8]) -> Vec<u8> {
let mut padded_length = data.len();
if data.len() % 8 != 0 {
padded_length = data.len() + (8 - (data.len() % 8));
}
2022-08-16 11:50:18 -04:00
let mut vec = vec![0; padded_length];
2022-07-19 19:29:41 -04:00
vec[..data.len()].clone_from_slice(data);
vec
}
/// Decrypts a block of data. If the decryption fails due to buffer overflow issues, will return
/// None - but this does not indicate that the wrong key was used.
2022-07-19 19:29:41 -04:00
pub fn decrypt(&self, data: &[u8]) -> Option<Vec<u8>> {
let padded_data = Blowfish::pad_buffer(data);
let mut buffer = Vec::with_capacity(padded_data.len());
let mut cursor = Cursor::new(&mut buffer);
for i in (0..padded_data.len()).step_by(8) {
let l_bytes: [u8; 4] = padded_data[i..i + 4].try_into().ok()?;
let r_bytes: [u8; 4] = padded_data[i + 4..i + 8].try_into().ok()?;
2022-08-16 11:52:07 -04:00
let (l, r) =
self.decrypt_pair(u32::from_le_bytes(l_bytes), u32::from_le_bytes(r_bytes));
2022-07-19 19:29:41 -04:00
2022-08-16 11:50:18 -04:00
cursor.write_all(u32::to_le_bytes(l).as_slice()).ok()?;
cursor.write_all(u32::to_le_bytes(r).as_slice()).ok()?;
2022-07-19 19:29:41 -04:00
}
Some(buffer)
}
/// Calculates the F-function for `x`.
fn f(&self, x: u32) -> u32 {
let a = self.s[0][(x >> 24) as usize];
let b = self.s[1][((x >> 16) & 0xFF) as usize];
let c = self.s[2][((x >> 8) & 0xFF) as usize];
let d = self.s[3][(x & 0xFF) as usize];
(a.wrapping_add(b) ^ c).wrapping_add(d)
}
fn encrypt_pair(&self, mut l: u32, mut r: u32) -> (u32, u32) {
for i in (0..ROUNDS).step_by(2) {
l ^= self.p[i];
r ^= self.f(l);
r ^= self.p[i + 1];
l ^= self.f(r);
}
2022-08-16 11:50:18 -04:00
(r ^ self.p[17], l ^ self.p[16])
2022-07-19 19:29:41 -04:00
}
fn decrypt_pair(&self, mut l: u32, mut r: u32) -> (u32, u32) {
for i in (2..ROUNDS + 1).step_by(2).rev() {
l ^= self.p[i + 1];
r ^= self.f(l);
r ^= self.p[i];
l ^= self.f(r);
}
2022-08-16 11:50:18 -04:00
(r ^ self.p[0], l ^ self.p[1])
2022-07-19 19:29:41 -04:00
}
2022-08-16 11:52:07 -04:00
}
2023-08-02 16:26:20 -04:00
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encrypt() {
let blowfish = Blowfish::new(b"test_case");
let expected_encrypted = [63, 149, 97, 229, 5, 35, 46, 128, 194, 107, 69, 132, 85, 202, 2, 126];
assert_eq!(blowfish.encrypt(b"hello, world!").unwrap(), expected_encrypted);
assert_eq!(String::from_utf8(blowfish.decrypt(&expected_encrypted).unwrap()).unwrap(), "hello, world!\0\0\0");
}
}