mirror of
https://git.um-react.app/um/lib_um_crypto_rust.git
synced 2026-03-07 20:19:51 +00:00
[joox] feat #1: add joox decipher implementation
This commit is contained in:
15
um_crypto/joox/Cargo.toml
Normal file
15
um_crypto/joox/Cargo.toml
Normal 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" }
|
||||
34
um_crypto/joox/src/decrypt.rs
Normal file
34
um_crypto/joox/src/decrypt.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
75
um_crypto/joox/src/header.rs
Normal file
75
um_crypto/joox/src/header.rs
Normal 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
28
um_crypto/joox/src/lib.rs
Normal 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),
|
||||
}
|
||||
Reference in New Issue
Block a user