feat: encryption of SDGB 1.40/1.50

This commit is contained in:
mokurin000
2025-07-30 01:30:17 +08:00
parent b61a724698
commit d870dc7047
4 changed files with 116 additions and 24 deletions

View File

@@ -1,4 +0,0 @@
pub const SDGB_1_50_KEY: &[u8; 32] = b"a>32bVP7v<63BVLkY[xM>daZ1s9MBP<R";
pub const SDGB_1_50_IV: &[u8; 16] = b"d6xHIKq]1J]Dt^ue";
pub const SDGB_1_40_KEY: &[u8; 32] = b"n7bx6:@Fg_:2;5E89Phy7AyIcpxEQ:R@";
pub const SDGB_1_40_IV: &[u8; 16] = b";;KjR1C3hgB1ovXa";

View File

@@ -1,24 +1,78 @@
use std::io::Read; use std::io::{Read, Write as _};
use aes::cipher::{BlockDecryptMut, KeyIvInit, block_padding::Pkcs7}; use aes::cipher::{
BlockDecryptMut, BlockEncryptMut, BlockSizeUser, KeyIvInit, block_padding::Pkcs7,
};
use digest::generic_array::GenericArray; use digest::generic_array::GenericArray;
use flate2::read::ZlibDecoder; use flate2::write::ZlibEncoder;
use flate2::{Compression, read::ZlibDecoder};
use crate::title::error::ApiError; use crate::title::{MaiVersion, MaiVersionExt, Sdgb1_40, Sdgb1_50, error::ApiError};
pub mod constants; impl MaiVersionExt for Sdgb1_40 {
fn decode(mut data: impl AsMut<[u8]>) -> Result<Vec<u8>, 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<aes::Aes256>; fn encode(data: impl AsRef<[u8]>) -> Result<Vec<u8>, 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<Vec<u8>, 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<Vec<u8>, ApiError> {
let compressed = compress(data)?;
let enc = encrypt(compressed, Self::AES_KEY, Self::AES_IV)?;
Ok(enc)
}
}
type Aes256CbcEnc = cbc::Encryptor<aes::Aes256>;
type Aes256CbcDec = cbc::Decryptor<aes::Aes256>; type Aes256CbcDec = cbc::Decryptor<aes::Aes256>;
pub fn decompress(data: impl AsRef<[u8]>) -> Vec<u8> { fn compress(data: impl AsRef<[u8]>) -> Result<Vec<u8>, 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<u8> {
let mut buf = Vec::with_capacity(data.as_ref().len() * 2); let mut buf = Vec::with_capacity(data.as_ref().len() * 2);
let mut decode = ZlibDecoder::new(data.as_ref()); let mut decode = ZlibDecoder::new(data.as_ref());
_ = decode.read_to_end(&mut buf); _ = decode.read_to_end(&mut buf);
buf buf
} }
pub fn decrypt<'ct>( fn encrypt(data: impl AsRef<[u8]>, key: &[u8; 32], iv: &[u8; 16]) -> Result<Vec<u8>, 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::<Pkcs7>(&mut buf, data.len())?;
Ok(buf)
}
fn decrypt<'ct>(
data: &'ct mut impl AsMut<[u8]>, data: &'ct mut impl AsMut<[u8]>,
key: &[u8; 32], key: &[u8; 32],
iv: &[u8; 16], iv: &[u8; 16],
@@ -33,24 +87,26 @@ pub fn decrypt<'ct>(
#[cfg(test)] #[cfg(test)]
mod _tests { mod _tests {
use crate::title::encryption::{constants::*, *}; use crate::title::{Sdgb1_50, encryption::*};
#[test] #[test]
fn test_sdgb_140_dec() { fn test_sdgb_140_dec_enc() -> Result<(), ApiError> {
let data = [ let data = [
120_u8, 156, 171, 77, 91, 233, 184, 108, 2, 71, 125, 142, 118, 135, 112, 181, 85, 217, 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, 239, 243, 159, 153, 248, 98, 159, 185, 63, 43, 173, 106, 221, 115, 104, 105, 221, 107,
0, 241, 176, 16, 37, 0, 241, 176, 16, 37,
]; ];
let mut decompressed = decompress(data);
let data = let dec = Sdgb1_40::decode(data)?;
decrypt(&mut decompressed, SDGB_1_40_KEY, SDGB_1_40_IV).expect("decryption failed!"); assert_eq!(dec, br#"{"result":"Pong"}"#);
assert_eq!(&data, br#"{"result":"Pong"}"#); let enc = Sdgb1_40::encode(dec)?;
assert_eq!(enc, data);
Ok(())
} }
#[test] #[test]
fn test_sdgb_150_dec() { fn test_sdgb_150_dec_enc() -> Result<(), ApiError> {
let mut data = [ let data = [
161, 166, 3, 157, 202, 233, 151, 73, 40, 113, 186, 162, 177, 46, 118, 113, 98, 231, 67, 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, 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, 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, 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, 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 dec = Sdgb1_50::decode(data)?;
let decompressed = decompress(decrypted); 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());
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 enc = Sdgb1_50::encode(dec)?;
assert_eq!(enc, data);
Ok(())
} }
} }

View File

@@ -1,10 +1,15 @@
use aes::cipher::block_padding::UnpadError; use aes::cipher::{block_padding::UnpadError, inout::PadError};
use snafu::Snafu; use snafu::Snafu;
#[derive(Debug, Snafu)] #[derive(Debug, Snafu)]
pub enum ApiError { pub enum ApiError {
#[snafu(display("pad data: {error}"))]
PadError { error: PadError },
#[snafu(display("unpad data: {error}"))] #[snafu(display("unpad data: {error}"))]
UnpadError { error: UnpadError }, UnpadError { error: UnpadError },
#[snafu(display("io error: {source}"))]
#[snafu(context(false))]
IOError { source: std::io::Error },
} }
impl From<UnpadError> for ApiError { impl From<UnpadError> for ApiError {
@@ -12,3 +17,9 @@ impl From<UnpadError> for ApiError {
Self::UnpadError { error } Self::UnpadError { error }
} }
} }
impl From<PadError> for ApiError {
fn from(error: PadError) -> Self {
Self::PadError { error }
}
}

View File

@@ -1,2 +1,29 @@
use crate::title::error::ApiError;
pub mod encryption; pub mod encryption;
mod error; 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<Vec<u8>, ApiError>;
fn decode(data: impl AsMut<[u8]>) -> Result<Vec<u8>, 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<R";
const AES_IV: &[u8; 16] = b"d6xHIKq]1J]Dt^ue";
const OBFUSECATE_PARAM: &str = "B44df8yT";
}