[joox] feat #1: add joox decipher implementation

This commit is contained in:
鲁树人
2024-09-17 21:45:51 +01:00
parent b3fc9f8318
commit 12199616c2
13 changed files with 293 additions and 1 deletions

15
um_crypto/joox/Cargo.toml Normal file
View File

@@ -0,0 +1,15 @@
[package]
name = "umc_joox"
version = "0.1.0"
edition = "2021"
[dependencies]
aes = "0.8.4"
byteorder = "1.5.0"
cipher = "0.4.4"
hmac = "0.12.1"
pbkdf2 = "0.12.2"
sha1 = "0.10.5"
thiserror = "1.0.63"
umc_qmc = { path = "../qmc" }
umc_utils = { path = "../utils" }

View File

@@ -0,0 +1,34 @@
use crate::header::Header;
use crate::JooxError;
use cipher::block_padding::Pkcs7;
use cipher::BlockDecryptMut;
use std::cmp::max;
pub trait JooxDecipher {
fn get_audio_block_size(&self) -> usize;
fn decrypt_audio_block<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a [u8], JooxError>;
}
impl JooxDecipher for Header {
fn get_audio_block_size(&self) -> usize {
max(
self.audio_encrypted_block_size,
self.audio_decrypted_block_size,
)
}
fn decrypt_audio_block<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a [u8], JooxError> {
let buffer_size = self.get_audio_block_size();
let (buffer, _) = buffer
.split_at_mut_checked(buffer_size)
.ok_or_else(|| JooxError::OutputBufferTooSmall(buffer_size))?;
let result = (&self.aes_engine)
.decrypt_padded_mut::<Pkcs7>(buffer)
.map_err(JooxError::AesUnpadError)?;
Ok(result)
}
}

View File

@@ -0,0 +1,75 @@
use crate::JooxError;
use aes::Aes128;
use byteorder::{ByteOrder, BE};
use cipher::generic_array::GenericArray;
use cipher::KeyInit;
use hmac::Hmac;
use pbkdf2::pbkdf2;
use sha1::Sha1;
pub struct Header {
pub version: u8,
pub original_file_len: u64,
pub audio_start_offset: usize,
pub audio_encrypted_block_size: usize,
pub audio_decrypted_block_size: usize,
pub aes_engine: Aes128,
}
const V4_PASSWORD_SALT: [u8; 0x10] = [
0xA4, 0x0B, 0xC8, 0x34, 0xD6, 0x95, 0xF3, 0x13, 0x23, 0x23, 0x43, 0x23, 0x54, 0x63, 0x83, 0xF3,
];
fn v4_generate_password<T: AsRef<[u8]>>(install_guid: T) -> [u8; 0x10] {
let mut derived_key = [0u8; 0x20];
pbkdf2::<Hmac<Sha1>>(
install_guid.as_ref(),
&V4_PASSWORD_SALT,
1000,
&mut derived_key,
)
.expect("buffer setup incorrect");
let mut result = [0u8; 0x10];
result.copy_from_slice(&derived_key[..0x10]);
result
}
impl Header {
pub fn from_buffer<T: AsRef<[u8]>>(buffer: &[u8], device_guid: T) -> Result<Self, JooxError> {
if buffer.len() < 0x0c {
Err(JooxError::HeaderTooSmall(0x0c))?;
}
let mut magic = [0u8; 4];
magic.copy_from_slice(&buffer[..4]);
let version = match magic {
[b'E', b'!', b'0', version] => version,
magic => Err(JooxError::NotJooxHeader(magic))?,
};
let result = match version {
b'4' => {
let original_file_len = BE::read_u64(&buffer[4..0x0c]);
let audio_start_offset = 0x0c;
let password = v4_generate_password(device_guid);
let aes_engine = Aes128::new(&GenericArray::from(password));
let audio_encrypted_block_size = 0x100010;
let audio_decrypted_block_size = 0x100000;
Self {
version,
original_file_len,
audio_start_offset,
audio_encrypted_block_size,
audio_decrypted_block_size,
aes_engine,
}
}
ver => Err(JooxError::UnsupportedJooxVersion(ver))?,
};
Ok(result)
}
}

28
um_crypto/joox/src/lib.rs Normal file
View File

@@ -0,0 +1,28 @@
use cipher::block_padding::UnpadError;
use cipher::inout::NotEqualError;
use thiserror::Error;
pub mod decrypt;
pub mod header;
#[derive(Error, Debug, Clone)]
pub enum JooxError {
#[error("Header too small, require at least {0} bytes.")]
HeaderTooSmall(usize),
#[error("Output buffer require at least {0} bytes.")]
OutputBufferTooSmall(usize),
#[error("Input buffer require at least {0} bytes.")]
InputBufferTooSmall(usize),
#[error("Not Joox encrypted header: {0:2x?}")]
NotJooxHeader([u8; 4]),
#[error("Unsupported Joox version: {0:2x}")]
UnsupportedJooxVersion(u8),
#[error("AES Decryption Unpad Error: {0}")]
AesUnpadError(UnpadError),
#[error("AES Buffer setup error: {0}")]
AesBufferSetupError(NotEqualError),
}