Compare commits

..

No commits in common. "75ebe1e63173939643e94cc9bf4927f6dc143426" and "92a52c1565d35c34fa899798b6c44657da8dd45a" have entirely different histories.

7 changed files with 59 additions and 68 deletions

View File

@ -4,7 +4,6 @@ use std::fs::File;
use std::io::{Read, Seek, SeekFrom, Write}; use std::io::{Read, Seek, SeekFrom, Write};
use std::path::PathBuf; use std::path::PathBuf;
use umc_kgm::header::Header; use umc_kgm::header::Header;
use umc_kgm::Decipher;
/// Decrypt a KGM/VPR file (Kugou Music) /// Decrypt a KGM/VPR file (Kugou Music)
#[derive(Args)] #[derive(Args)]
@ -24,7 +23,7 @@ impl ArgsKGM {
let mut header = [0u8; 0x40]; let mut header = [0u8; 0x40];
file_input.read_exact(&mut header)?; file_input.read_exact(&mut header)?;
let kgm_header = Header::from_buffer(&mut header)?; let kgm_header = Header::from_buffer(&mut header)?;
let decipher = Decipher::new(&kgm_header)?; let decipher = kgm_header.make_decipher()?;
file_input.seek(SeekFrom::Start(kgm_header.offset_to_data as u64))?; file_input.seek(SeekFrom::Start(kgm_header.offset_to_data as u64))?;
let mut offset = 0usize; let mut offset = 0usize;

View File

@ -1,4 +1,6 @@
use crate::KugouError; use crate::v2::DecipherV2;
use crate::v3::DecipherV3;
use crate::{Decipher, KugouError};
use byteorder::{ByteOrder, LE}; use byteorder::{ByteOrder, LE};
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -46,8 +48,25 @@ impl Header {
}) })
} }
pub fn get_challenge_data(&self) -> [u8; 0x10] { pub fn make_decipher(&self) -> Result<Box<dyn Decipher>, KugouError> {
self.challenge_data 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))?,
};
let mut test_data = self.decrypt_test_data;
decipher.decrypt(&mut test_data, 0);
if self.challenge_data != test_data {
Err(KugouError::SelfTestFailed)?;
}
Ok(decipher)
} }
} }

View File

@ -1,10 +1,7 @@
pub mod header; pub mod header;
pub mod v2; pub mod v2;
pub mod v3; mod v3;
use crate::header::Header;
use crate::v2::DecipherV2;
use crate::v3::DecipherV3;
use thiserror::Error; use thiserror::Error;
#[derive(Debug, Error)] #[derive(Debug, Error)]
@ -25,37 +22,6 @@ pub enum KugouError {
SelfTestFailed, SelfTestFailed,
} }
pub enum Decipher { pub trait Decipher {
V2(DecipherV2), fn decrypt(&self, buffer: &mut [u8], offset: usize);
V3(DecipherV3),
}
impl Decipher {
pub fn new(header: &Header) -> Result<Self, KugouError> {
let slot_key: &[u8] = match header.key_slot {
1 => b"l,/'",
slot => Err(KugouError::UnsupportedKeySlot(slot))?,
};
let decipher = match header.crypto_version {
2 => Decipher::V2(DecipherV2::new(header, slot_key)?),
3 => Decipher::V3(DecipherV3::new(header, slot_key)?),
version => Err(KugouError::UnsupportedCipherVersion(version))?,
};
let mut test_data = header.decrypt_test_data;
decipher.decrypt(&mut test_data, 0);
if test_data != header.get_challenge_data() {
Err(KugouError::SelfTestFailed)?;
}
Ok(decipher)
}
pub fn decrypt<T: AsMut<[u8]> + ?Sized>(&self, buffer: &mut T, offset: usize) {
match self {
Decipher::V2(decipher) => decipher.decrypt(buffer, offset),
Decipher::V3(decipher) => decipher.decrypt(buffer, offset),
}
}
} }

View File

@ -1,5 +1,5 @@
use crate::header::Header; use crate::header::Header;
use crate::KugouError; use crate::{Decipher, KugouError};
pub struct DecipherV2 { pub struct DecipherV2 {
key: [u8; 4], key: [u8; 4],
@ -11,9 +11,11 @@ impl DecipherV2 {
key.copy_from_slice(slot_key); key.copy_from_slice(slot_key);
Ok(Self { key }) Ok(Self { key })
} }
}
pub fn decrypt<T: AsMut<[u8]> + ?Sized>(&self, buffer: &mut T, offset: usize) { impl Decipher for DecipherV2 {
for (datum, offset) in buffer.as_mut().iter_mut().zip(offset..) { fn decrypt(&self, buffer: &mut [u8], offset: usize) {
for (datum, offset) in buffer.iter_mut().zip(offset..) {
*datum ^= self.key[offset % self.key.len()]; *datum ^= self.key[offset % self.key.len()];
} }
} }

View File

@ -1,5 +1,5 @@
use crate::header::Header; use crate::header::Header;
use crate::KugouError; use crate::{Decipher, KugouError};
pub struct DecipherV3 { pub struct DecipherV3 {
slot_key: [u8; 16], slot_key: [u8; 16],
@ -30,9 +30,11 @@ impl DecipherV3 {
let offset_key = (offset as u32).to_ne_bytes(); let offset_key = (offset as u32).to_ne_bytes();
offset_key[0] ^ offset_key[1] ^ offset_key[2] ^ offset_key[3] offset_key[0] ^ offset_key[1] ^ offset_key[2] ^ offset_key[3]
} }
}
pub fn decrypt<T: AsMut<[u8]> + ?Sized>(&self, buffer: &mut T, offset: usize) { impl Decipher for DecipherV3 {
for (datum, offset) in buffer.as_mut().iter_mut().zip(offset..) { fn decrypt(&self, buffer: &mut [u8], offset: usize) {
for (datum, offset) in buffer.iter_mut().zip(offset..) {
let offset_key = Self::offset_key(offset); let offset_key = Self::offset_key(offset);
let file_key = self.file_key[offset % self.file_key.len()]; let file_key = self.file_key[offset % self.file_key.len()];

View File

@ -43,32 +43,19 @@ impl fmt::Display for HeaderMagicBytes {
pub const DATA_START_OFFSET: usize = 0x400; pub const DATA_START_OFFSET: usize = 0x400;
pub enum Decipher { pub enum Cipher {
V1(CipherV1), V1(CipherV1),
V2(CipherV2), V2(CipherV2),
} }
impl Decipher { impl Cipher {
pub fn new<T: AsRef<[u8]>>(header: &Header, ekey: Option<T>) -> Result<Decipher> {
let cipher = match header.version {
1 => Decipher::V1(CipherV1::new(header.resource_id)),
2 => match ekey {
Some(ekey) => Decipher::V2(CipherV2::new_from_ekey(ekey)?),
None => Err(KuwoCryptoError::V2EKeyRequired)?,
},
version => Err(KuwoCryptoError::UnsupportedVersion(version as usize))?,
};
Ok(cipher)
}
pub fn decrypt<T>(&self, data: &mut T, offset: usize) pub fn decrypt<T>(&self, data: &mut T, offset: usize)
where where
T: AsMut<[u8]> + ?Sized, T: AsMut<[u8]> + ?Sized,
{ {
match self { match self {
Decipher::V1(cipher) => cipher.decrypt(data, offset), Cipher::V1(cipher) => cipher.decrypt(data, offset),
Decipher::V2(cipher) => cipher.decrypt(data, offset), Cipher::V2(cipher) => cipher.decrypt(data, offset),
} }
} }
} }
@ -118,6 +105,22 @@ impl Header {
}) })
} }
pub fn get_decipher<T>(&self, ekey: Option<T>) -> Result<Cipher>
where
T: AsRef<[u8]>,
{
let cipher = match self.version {
1 => Cipher::V1(CipherV1::new(self.resource_id)),
2 => match ekey {
Some(ekey) => Cipher::V2(CipherV2::new_from_ekey(ekey)?),
None => Err(KuwoCryptoError::V2EKeyRequired)?,
},
version => Err(KuwoCryptoError::UnsupportedVersion(version as usize))?,
};
Ok(cipher)
}
/// Get the quality id /// Get the quality id
/// Used for matching Android MMKV id. /// Used for matching Android MMKV id.
pub fn get_quality_id(&self) -> u32 { pub fn get_quality_id(&self) -> u32 {

View File

@ -1,7 +1,7 @@
use crate::errors::map_js_error; use crate::errors::map_js_error;
use crate::exports::qmc::JsQMC2; use crate::exports::qmc::JsQMC2;
use umc_kuwo::kwm_v1::CipherV1; use umc_kuwo::kwm_v1::CipherV1;
use umc_kuwo::{Decipher, Header}; use umc_kuwo::{Cipher, Header};
use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsError; use wasm_bindgen::JsError;
@ -27,7 +27,7 @@ impl JsKuwoHeader {
/// Create an instance of cipher (decipher) for decryption /// Create an instance of cipher (decipher) for decryption
#[wasm_bindgen(js_name=makeDecipher)] #[wasm_bindgen(js_name=makeDecipher)]
pub fn make_decipher(&self, ekey: Option<String>) -> Result<JsCipher, JsError> { pub fn make_decipher(&self, ekey: Option<String>) -> Result<JsCipher, JsError> {
let cipher = Decipher::new(&self.0, ekey).map_err(map_js_error)?; let cipher = self.0.get_decipher(ekey).map_err(map_js_error)?;
Ok(JsCipher(cipher)) Ok(JsCipher(cipher))
} }
} }
@ -59,7 +59,7 @@ pub fn js_kuwo_v2_cipher_factory(ekey: &str) -> Result<JsQMC2, JsError> {
/// Common V1/V2 wrapper interface, derived from `KuwoHeader.makeCipher` /// Common V1/V2 wrapper interface, derived from `KuwoHeader.makeCipher`
#[wasm_bindgen(js_name=KWMCipher)] #[wasm_bindgen(js_name=KWMCipher)]
pub struct JsCipher(Decipher); pub struct JsCipher(Cipher);
#[wasm_bindgen(js_class=KWMCipher)] #[wasm_bindgen(js_class=KWMCipher)]
impl JsCipher { impl JsCipher {