mirror of
https://git.um-react.app/um/lib_um_crypto_rust.git
synced 2026-03-08 04:29:54 +00:00
[kgm] feat #2: basic kgm support
This commit is contained in:
10
um_crypto/kgm/Cargo.toml
Normal file
10
um_crypto/kgm/Cargo.toml
Normal 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" }
|
||||
BIN
um_crypto/kgm/src/__fixtures__/kgm_v2_hdr.bin
Normal file
BIN
um_crypto/kgm/src/__fixtures__/kgm_v2_hdr.bin
Normal file
Binary file not shown.
59
um_crypto/kgm/src/header.rs
Normal file
59
um_crypto/kgm/src/header.rs
Normal 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
21
um_crypto/kgm/src/lib.rs
Normal 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
31
um_crypto/kgm/src/v2.rs
Normal 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
48
um_crypto/kgm/src/v3.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user