commit 366cc3b759c1e4b6705bb01d940d37c10a6efc73 Author: Maple Nebel Date: Fri Jul 3 22:15:58 2026 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..567e6d1 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,287 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aes" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" +dependencies = [ + "cipher", + "cpubits", + "cpufeatures", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core", +] + +[[package]] +name = "cipher" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "ctutils", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "rand_core", +] + +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest", +] + +[[package]] +name = "hybrid-array" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" +dependencies = [ + "bytemuck", + "typenum", +] + +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "md-5" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "spbr-common" +version = "0.1.0" +dependencies = [ + "aes", + "base64", + "bytemuck", + "hmac", + "hybrid-array", + "md-5", + "rand", + "thiserror", +] + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..527f400 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "spbr-common" +version = "0.1.0" +edition = "2024" + +[dependencies] +aes = "0.9.1" +base64 = { version = "0.22.1", + optional = true +} +bytemuck = { version = "1.25.0", features = ["derive"] } +hmac = {version = "0.13.0", optional = true} +hybrid-array = { version = "0.4.13", features = ["bytemuck"], optional = true } +md-5 = {version = "0.11.0", optional = true} +thiserror = "2.0.18" +rand = "0.10.2" + +[features] +crypto = ["dep:base64", "dep:hmac", "dep:md-5", "dep:hybrid-array"] diff --git a/src/crypto/encsign.rs b/src/crypto/encsign.rs new file mode 100644 index 0000000..bd53bcc --- /dev/null +++ b/src/crypto/encsign.rs @@ -0,0 +1,112 @@ +use aes::{ + Aes128, + cipher::{Block, BlockCipherDecrypt, BlockCipherEncrypt, BlockSizeUser, block}, +}; +use bytemuck::{Pod, PodCastError, Zeroable}; +use hmac::{Hmac, KeyInit, Mac}; +use hybrid_array::Array; +use md5::Md5; +use rand::random; +use thiserror::Error; + +use crate::crypto::encsign::Error::{BlockCastError, InvalidSignature, MessageTooSmall}; + +// first is aes second is hmac +pub struct EncSignKeypair(pub [u8; 0x10], pub [u8; 0x40]); + +type HmacMd5 = Hmac; + +impl EncSignKeypair { + fn new_random() -> Self { + Self(random(), random()) + } +} + +pub fn blob_calc_signature(data: &[u8], key: &[u8; 0x40]) -> [u8; 0x10] { + let mut hmac = HmacMd5::new(Array::cast_from_core(key)); + + hmac.update(data); + + hmac.finalize().into_bytes().0 +} + +pub fn encrypt_and_gen_signature_blocks( + data: &mut [Block], + pair: &EncSignKeypair, +) -> [u8; 0x10] { + let signature = blob_calc_signature(bytemuck::cast_slice(&data), &pair.1); + Aes128::new(Array::cast_from_core(&pair.0)).encrypt_blocks(data); + signature +} +pub fn decrypt_and_gen_signature_blocks<'a, 'b>( + data: &'a mut [Block], + pair: &'b EncSignKeypair, +) -> [u8; 0x10] { + Aes128::new(Array::cast_from_core(&pair.0)).decrypt_blocks(data); + blob_calc_signature(bytemuck::cast_slice(&data), &pair.1) +} + +#[derive(Error, Debug, PartialEq)] +pub enum Error { + #[error("an error occurred whilest casting data to blocks: {0}")] + BlockCastError(PodCastError), + #[error("invalid signature")] + InvalidSignature, + #[error("message too small, it must be at least 0x10 in size")] + MessageTooSmall, +} + +pub type Result = std::result::Result; + +pub fn encrypt_and_sign(mut data: Vec, keypair: &EncSignKeypair) -> Result> { + let block_data = bytemuck::try_cast_slice_mut(&mut data).map_err(BlockCastError)?; + let signature = encrypt_and_gen_signature_blocks(block_data, keypair); + data.extend_from_slice(&signature[..]); + Ok(data) +} + +pub fn decrypt_and_check<'a, 'b>( + data: &'a mut [u8], + keypair: &'b EncSignKeypair, +) -> Result<&'a [u8]> { + let signature_delim_point = data.len().checked_sub(0x10).ok_or(MessageTooSmall)?; + let (crypt_data, signature_data) = data + .split_at_mut_checked(signature_delim_point) + .ok_or(MessageTooSmall)?; + let block_data = bytemuck::try_cast_slice_mut(crypt_data).map_err(BlockCastError)?; + let signature = decrypt_and_gen_signature_blocks(block_data, keypair); + if signature_data != &signature[..] { + return Err(InvalidSignature); + } + Ok(crypt_data) +} +#[cfg(test)] +mod test { + use super::{EncSignKeypair, Error, decrypt_and_check, encrypt_and_sign}; + const FUZZ_ROUNDS: usize = 10000; + #[test] + fn fuzz_test_succeed() { + for _ in 0..FUZZ_ROUNDS { + let keypair = EncSignKeypair::new_random(); + let random_data: [u8; 0x20] = rand::random(); + let random_data_vec: Vec = random_data.into(); + let mut message = encrypt_and_sign(random_data_vec, &keypair).unwrap(); + let result = decrypt_and_check(&mut message, &keypair).unwrap(); + assert_eq!(&random_data[..], result); + } + } + #[test] + fn fuzz_test_fail() { + for _ in 0..FUZZ_ROUNDS { + let keypair = EncSignKeypair::new_random(); + let random_data: [u8; 0x20] = rand::random(); + let random_data_vec: Vec = random_data.into(); + let mut message = encrypt_and_sign(random_data_vec, &keypair).unwrap(); + message.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + assert_eq!( + decrypt_and_check(&mut message, &keypair), + Err(Error::InvalidSignature) + ); + } + } +} diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs new file mode 100644 index 0000000..9b1aab8 --- /dev/null +++ b/src/crypto/mod.rs @@ -0,0 +1 @@ +pub mod encsign; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..f6c81d1 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,2 @@ +#[cfg(feature = "crypto")] +pub mod crypto;