[kgm] feat #2: basic kgm support

This commit is contained in:
鲁树人
2024-09-15 22:15:02 +01:00
parent 2222e7bc50
commit 7b4464bacd
16 changed files with 273 additions and 3 deletions

10
um_crypto/kgm/Cargo.toml Normal file
View File

@@ -0,0 +1,10 @@
[package]
name = "umc_kgm"
version = "0.1.0"
edition = "2021"
[dependencies]
byteorder = "1.5.0"
itertools = "0.13.0"
thiserror = "1.0.63"
umc_utils = { path = "../utils" }

Binary file not shown.

View File

@@ -0,0 +1,59 @@
use crate::v2::DecipherV2;
use crate::v3::DecipherV3;
use crate::{Decipher, KugouError};
use byteorder::{ByteOrder, LE};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Header {
pub magic: [u8; 0x10],
pub offset_to_data: usize,
pub crypto_version: u32,
pub key_slot: u32,
pub decrypt_test_data: [u8; 0x10],
pub file_key: [u8; 0x10],
}
impl Header {
pub fn from_buffer<T>(buffer: T) -> Result<Self, KugouError>
where
T: AsRef<[u8]>,
{
let buffer = buffer.as_ref();
if buffer.len() < 0x3c {
Err(KugouError::HeaderTooSmall(0x3c))?;
}
let mut magic = [0u8; 0x10];
magic.copy_from_slice(&buffer[..0x10]);
let offset_to_data = LE::read_u32(&buffer[0x10..0x14]) as usize;
let crypto_version = LE::read_u32(&buffer[0x14..0x18]);
let key_slot = LE::read_u32(&buffer[0x18..0x1C]);
let mut decrypt_test_data = [0u8; 0x10];
decrypt_test_data.copy_from_slice(&buffer[0x1c..0x2c]);
let mut file_key = [0u8; 0x10];
file_key.copy_from_slice(&buffer[0x2c..0x3c]);
Ok(Self {
magic,
offset_to_data,
crypto_version,
key_slot,
decrypt_test_data,
file_key,
})
}
pub fn make_decipher(&self) -> Result<Box<dyn Decipher>, KugouError> {
let slot_key: &[u8] = match self.key_slot {
1 => b"l,/'",
slot => Err(KugouError::UnsupportedKeySlot(slot))?,
};
let decipher: Box<dyn Decipher> = match self.crypto_version {
2 => Box::from(DecipherV2::new(self, slot_key)?),
3 => Box::from(DecipherV3::new(self, slot_key)?),
version => Err(KugouError::UnsupportedCipherVersion(version))?,
};
Ok(decipher)
}
}

21
um_crypto/kgm/src/lib.rs Normal file
View File

@@ -0,0 +1,21 @@
pub mod header;
pub mod v2;
mod v3;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum KugouError {
#[error("Header too small, need at least {0} bytes.")]
HeaderTooSmall(usize),
#[error("Unsupported key slot: {0}")]
UnsupportedKeySlot(u32),
#[error("Unsupported cipher version: {0}")]
UnsupportedCipherVersion(u32),
}
pub trait Decipher {
fn decrypt(&self, buffer: &mut [u8], offset: usize);
}

31
um_crypto/kgm/src/v2.rs Normal file
View File

@@ -0,0 +1,31 @@
use crate::header::Header;
use crate::{Decipher, KugouError};
pub struct DecipherV2 {
key: [u8; 4],
}
impl DecipherV2 {
pub fn new(_header: &Header, slot_key: &[u8]) -> Result<Self, KugouError> {
let mut key = [0u8; 4];
key.copy_from_slice(slot_key);
Ok(Self { key })
}
}
impl Decipher for DecipherV2 {
fn decrypt(&self, buffer: &mut [u8], offset: usize) {
let key_stream = self.key.iter().cycle().skip(offset % self.key.len());
for (datum, &k) in buffer.iter_mut().zip(key_stream) {
*datum ^= k;
}
}
}
#[test]
fn test_v2_init() -> Result<(), KugouError> {
let hdr_v2 = Header::from_buffer(include_bytes!("__fixtures__/kgm_v2_hdr.bin"))?;
DecipherV2::new(&hdr_v2, b"1234")?;
Ok(())
}

48
um_crypto/kgm/src/v3.rs Normal file
View File

@@ -0,0 +1,48 @@
use crate::header::Header;
use crate::{Decipher, KugouError};
pub struct DecipherV3 {
slot_key: [u8; 16],
file_key: [u8; 17],
}
impl DecipherV3 {
fn hash_key<T: AsRef<[u8]>>(data: T) -> [u8; 16] {
let digest = umc_utils::md5(data);
let mut result = [0u8; 16];
for (result, digest) in result.rchunks_exact_mut(2).zip(digest.chunks_exact(2)) {
result[0] = digest[0];
result[1] = digest[1];
}
result
}
pub fn new(header: &Header, slot_key: &[u8]) -> Result<Self, KugouError> {
let slot_key = Self::hash_key(slot_key);
let mut file_key = [0x6b; 17];
file_key[..16].copy_from_slice(&Self::hash_key(header.file_key));
Ok(Self { slot_key, file_key })
}
}
impl Decipher for DecipherV3 {
fn decrypt(&self, buffer: &mut [u8], offset: usize) {
let slot_key_stream = self.slot_key.iter().cycle().skip(offset);
let file_key_stream = self.file_key.iter().cycle().skip(offset);
let mut offset = offset as u32;
let key_stream = slot_key_stream.zip(file_key_stream);
for (datum, (&slot_key, &file_key)) in buffer.iter_mut().zip(key_stream) {
let mut temp = *datum;
temp ^= file_key;
temp ^= temp.wrapping_shl(4);
temp ^= slot_key;
temp ^= offset.to_ne_bytes().iter().fold(0, |acc, &x| acc ^ x);
*datum = temp;
offset = offset.wrapping_add(1);
}
}
}

View File

@@ -5,3 +5,5 @@ edition = "2021"
[dependencies]
base64 = "0.22.1"
itertools = "0.13.0"
md-5 = "0.10.6"

View File

@@ -1 +1,3 @@
pub mod base64;
mod md5;
pub use md5::md5;

View File

@@ -0,0 +1,5 @@
use md5::{Digest, Md5};
pub fn md5<T: AsRef<[u8]>>(buffer: T) -> [u8; 16] {
Md5::digest(buffer).into()
}