From d870dc70472bd02b3429823f298d87f75bcd4f5e Mon Sep 17 00:00:00 2001 From: mokurin000 <1348292515a@gmail.com> Date: Wed, 30 Jul 2025 01:30:17 +0800 Subject: [PATCH] feat: encryption of SDGB 1.40/1.50 --- .../src/title/encryption/constants/mod.rs | 4 - sdgb-api/src/title/encryption/mod.rs | 96 +++++++++++++++---- sdgb-api/src/title/error/mod.rs | 13 ++- sdgb-api/src/title/mod.rs | 27 ++++++ 4 files changed, 116 insertions(+), 24 deletions(-) delete mode 100644 sdgb-api/src/title/encryption/constants/mod.rs diff --git a/sdgb-api/src/title/encryption/constants/mod.rs b/sdgb-api/src/title/encryption/constants/mod.rs deleted file mode 100644 index c08fa49..0000000 --- a/sdgb-api/src/title/encryption/constants/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub const SDGB_1_50_KEY: &[u8; 32] = b"a>32bVP7v<63BVLkY[xM>daZ1s9MBP) -> Result, ApiError> { + let mut decompressed = decompress(data.as_mut()); + let unpad_size = decrypt(&mut decompressed, Self::AES_KEY, Self::AES_IV)?.len(); + decompressed.truncate(unpad_size); + Ok(decompressed) + } -// type Aes256CbcEnc = cbc::Encryptor; + fn encode(data: impl AsRef<[u8]>) -> Result, ApiError> { + let enc = encrypt(data, Self::AES_KEY, Self::AES_IV)?; + let compressed = compress(enc)?; + Ok(compressed) + } +} + +impl MaiVersionExt for Sdgb1_50 { + fn decode(mut data: impl AsMut<[u8]>) -> Result, ApiError> { + let decrypted = decrypt(&mut data, Self::AES_KEY, Self::AES_IV)?; + let decompressed = decompress(decrypted); + Ok(decompressed) + } + + fn encode(data: impl AsRef<[u8]>) -> Result, ApiError> { + let compressed = compress(data)?; + let enc = encrypt(compressed, Self::AES_KEY, Self::AES_IV)?; + Ok(enc) + } +} + +type Aes256CbcEnc = cbc::Encryptor; type Aes256CbcDec = cbc::Decryptor; -pub fn decompress(data: impl AsRef<[u8]>) -> Vec { +fn compress(data: impl AsRef<[u8]>) -> Result, ApiError> { + let mut buf = Vec::with_capacity(data.as_ref().len()); + // 6 is the default compression level of zlib + let mut encoder = ZlibEncoder::new(&mut buf, Compression::new(6)); + encoder.write_all(data.as_ref())?; + encoder.finish()?; + Ok(buf) +} + +fn decompress(data: impl AsRef<[u8]>) -> Vec { let mut buf = Vec::with_capacity(data.as_ref().len() * 2); let mut decode = ZlibDecoder::new(data.as_ref()); _ = decode.read_to_end(&mut buf); buf } -pub fn decrypt<'ct>( +fn encrypt(data: impl AsRef<[u8]>, key: &[u8; 32], iv: &[u8; 16]) -> Result, ApiError> { + let key = GenericArray::from_slice(key); + let iv = GenericArray::from_slice(iv); + let encryptor = Aes256CbcEnc::new(key, iv); + let data = data.as_ref(); + let bs = aes::Aes256::block_size(); + let pad_len = bs - data.len() % bs; + + let mut buf = vec![0; pad_len + data.len()]; + buf[..data.len()].copy_from_slice(&data); + + encryptor.encrypt_padded_mut::(&mut buf, data.len())?; + Ok(buf) +} + +fn decrypt<'ct>( data: &'ct mut impl AsMut<[u8]>, key: &[u8; 32], iv: &[u8; 16], @@ -33,24 +87,26 @@ pub fn decrypt<'ct>( #[cfg(test)] mod _tests { - use crate::title::encryption::{constants::*, *}; + use crate::title::{Sdgb1_50, encryption::*}; #[test] - fn test_sdgb_140_dec() { + fn test_sdgb_140_dec_enc() -> Result<(), ApiError> { let data = [ 120_u8, 156, 171, 77, 91, 233, 184, 108, 2, 71, 125, 142, 118, 135, 112, 181, 85, 217, 239, 243, 159, 153, 248, 98, 159, 185, 63, 43, 173, 106, 221, 115, 104, 105, 221, 107, 0, 241, 176, 16, 37, ]; - let mut decompressed = decompress(data); - let data = - decrypt(&mut decompressed, SDGB_1_40_KEY, SDGB_1_40_IV).expect("decryption failed!"); - assert_eq!(&data, br#"{"result":"Pong"}"#); + + let dec = Sdgb1_40::decode(data)?; + assert_eq!(dec, br#"{"result":"Pong"}"#); + let enc = Sdgb1_40::encode(dec)?; + assert_eq!(enc, data); + Ok(()) } #[test] - fn test_sdgb_150_dec() { - let mut data = [ + fn test_sdgb_150_dec_enc() -> Result<(), ApiError> { + let data = [ 161, 166, 3, 157, 202, 233, 151, 73, 40, 113, 186, 162, 177, 46, 118, 113, 98, 231, 67, 185, 246, 180, 109, 253, 1, 152, 0, 31, 81, 211, 28, 137, 95, 12, 110, 105, 181, 246, 177, 1, 45, 59, 182, 113, 56, 97, 56, 100, 34, 168, 27, 51, 228, 77, 192, 194, 248, 45, @@ -66,8 +122,10 @@ mod _tests { 177, 84, 85, 152, 183, 77, 67, 163, 61, 165, 144, 125, 255, 89, 108, 58, 137, 142, 9, 8, 54, 228, 34, 55, 124, 158, 83, 36, ]; - let decrypted = decrypt(&mut data, SDGB_1_50_KEY, SDGB_1_50_IV).expect("decryption failed"); - let decompressed = decompress(decrypted); - assert_eq!(decompressed, r#"{"userId":10103750,"userName":"舞萌","isLogin":false,"lastGameId":null,"lastRomVersion":"1.01.00","lastDataVersion":"1.05.03","lastLoginDate":"1970-01-01 00:00:00","lastPlayDate":"1970-01-01 00:00:00","playerRating":1024,"nameplateId":0,"iconId":11,"trophyId":0,"isNetMember":1,"isInherit":false,"totalAwake":5,"dispRate":0,"dailyBonusDate":"1970-01-01 09:00:00","headPhoneVolume":null,"banState":0}"#.as_bytes()) + let dec = Sdgb1_50::decode(data)?; + assert_eq!(dec, r#"{"userId":10103750,"userName":"舞萌","isLogin":false,"lastGameId":null,"lastRomVersion":"1.01.00","lastDataVersion":"1.05.03","lastLoginDate":"1970-01-01 00:00:00","lastPlayDate":"1970-01-01 00:00:00","playerRating":1024,"nameplateId":0,"iconId":11,"trophyId":0,"isNetMember":1,"isInherit":false,"totalAwake":5,"dispRate":0,"dailyBonusDate":"1970-01-01 09:00:00","headPhoneVolume":null,"banState":0}"#.as_bytes()); + let enc = Sdgb1_50::encode(dec)?; + assert_eq!(enc, data); + Ok(()) } } diff --git a/sdgb-api/src/title/error/mod.rs b/sdgb-api/src/title/error/mod.rs index 44898f6..edc6120 100644 --- a/sdgb-api/src/title/error/mod.rs +++ b/sdgb-api/src/title/error/mod.rs @@ -1,10 +1,15 @@ -use aes::cipher::block_padding::UnpadError; +use aes::cipher::{block_padding::UnpadError, inout::PadError}; use snafu::Snafu; #[derive(Debug, Snafu)] pub enum ApiError { + #[snafu(display("pad data: {error}"))] + PadError { error: PadError }, #[snafu(display("unpad data: {error}"))] UnpadError { error: UnpadError }, + #[snafu(display("io error: {source}"))] + #[snafu(context(false))] + IOError { source: std::io::Error }, } impl From for ApiError { @@ -12,3 +17,9 @@ impl From for ApiError { Self::UnpadError { error } } } + +impl From for ApiError { + fn from(error: PadError) -> Self { + Self::PadError { error } + } +} diff --git a/sdgb-api/src/title/mod.rs b/sdgb-api/src/title/mod.rs index ec203f8..267229a 100644 --- a/sdgb-api/src/title/mod.rs +++ b/sdgb-api/src/title/mod.rs @@ -1,2 +1,29 @@ +use crate::title::error::ApiError; + pub mod encryption; mod error; + +pub trait MaiVersion { + const AES_KEY: &[u8; 32]; + const AES_IV: &[u8; 16]; + const OBFUSECATE_PARAM: &str; +} + +pub trait MaiVersionExt: MaiVersion { + fn encode(data: impl AsRef<[u8]>) -> Result, ApiError>; + fn decode(data: impl AsMut<[u8]>) -> Result, ApiError>; +} + +pub struct Sdgb1_40; +pub struct Sdgb1_50; + +impl MaiVersion for Sdgb1_40 { + const AES_KEY: &[u8; 32] = b"n7bx6:@Fg_:2;5E89Phy7AyIcpxEQ:R@"; + const AES_IV: &[u8; 16] = b";;KjR1C3hgB1ovXa"; + const OBFUSECATE_PARAM: &str = "BEs2D5vW"; +} +impl MaiVersion for Sdgb1_50 { + const AES_KEY: &[u8; 32] = b"a>32bVP7v<63BVLkY[xM>daZ1s9MBP