Compare commits
No commits in common. "c8af0b12113b13f6c07f1b9e576024dd6e13c28e" and "1e1d6421493e8f861e34787762c7ec9acfc92b63" have entirely different histories.
c8af0b1211
...
1e1d642149
@ -10,7 +10,6 @@
|
|||||||
<sourceFolder url="file://$MODULE_DIR$/um_crypto/utils/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/um_crypto/utils/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/um_crypto/ncm/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/um_crypto/ncm/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/um_audio/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/um_audio/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/um_crypto/kgm/src" isTestSource="false" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/um_wasm_loader/dist" />
|
<excludeFolder url="file://$MODULE_DIR$/um_wasm_loader/dist" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/um_wasm_loader/pkg" />
|
<excludeFolder url="file://$MODULE_DIR$/um_wasm_loader/pkg" />
|
||||||
|
42
Cargo.lock
generated
42
Cargo.lock
generated
@ -74,15 +74,6 @@ version = "0.22.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "block-buffer"
|
|
||||||
version = "0.10.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
|
||||||
dependencies = [
|
|
||||||
"generic-array",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-padding"
|
name = "block-padding"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
@ -225,16 +216,6 @@ version = "0.1.13"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "digest"
|
|
||||||
version = "0.10.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
|
||||||
dependencies = [
|
|
||||||
"block-buffer",
|
|
||||||
"crypto-common",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.13.0"
|
version = "1.13.0"
|
||||||
@ -322,16 +303,6 @@ version = "0.4.22"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "md-5"
|
|
||||||
version = "0.10.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"digest",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "minicov"
|
name = "minicov"
|
||||||
version = "0.3.5"
|
version = "0.3.5"
|
||||||
@ -470,7 +441,6 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
"umc_kgm",
|
|
||||||
"umc_kuwo",
|
"umc_kuwo",
|
||||||
"umc_ncm",
|
"umc_ncm",
|
||||||
"umc_qmc",
|
"umc_qmc",
|
||||||
@ -492,16 +462,6 @@ dependencies = [
|
|||||||
"wasm-bindgen-test",
|
"wasm-bindgen-test",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "umc_kgm"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"byteorder",
|
|
||||||
"itertools",
|
|
||||||
"thiserror",
|
|
||||||
"umc_utils",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "umc_kuwo"
|
name = "umc_kuwo"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -547,8 +507,6 @@ name = "umc_utils"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"itertools",
|
|
||||||
"md-5",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -7,7 +7,6 @@ edition = "2021"
|
|||||||
anyhow = "1.0.86"
|
anyhow = "1.0.86"
|
||||||
clap = { version = "4.5.17", features = ["derive"] }
|
clap = { version = "4.5.17", features = ["derive"] }
|
||||||
umc_kuwo = { path = "../um_crypto/kuwo" }
|
umc_kuwo = { path = "../um_crypto/kuwo" }
|
||||||
umc_kgm = { path = "../um_crypto/kgm" }
|
|
||||||
umc_ncm = { path = "../um_crypto/ncm" }
|
|
||||||
umc_qmc = { path = "../um_crypto/qmc" }
|
umc_qmc = { path = "../um_crypto/qmc" }
|
||||||
|
umc_ncm = { path = "../um_crypto/ncm" }
|
||||||
umc_utils = { path = "../um_crypto/utils" }
|
umc_utils = { path = "../um_crypto/utils" }
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
use crate::Cli;
|
|
||||||
use clap::Args;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{Read, Seek, SeekFrom, Write};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use umc_kgm::header::Header;
|
|
||||||
|
|
||||||
/// Decrypt a KGM/VPR file (Kugou Music)
|
|
||||||
#[derive(Args)]
|
|
||||||
pub struct ArgsKGM {
|
|
||||||
/// Path to output file, e.g. /export/Music/song.flac
|
|
||||||
#[arg(short, long)]
|
|
||||||
output: PathBuf,
|
|
||||||
|
|
||||||
/// Path to input file, e.g. /export/Music/song.kgm
|
|
||||||
#[arg(name = "input")]
|
|
||||||
input: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ArgsKGM {
|
|
||||||
pub fn run(&self, cli: &Cli) -> anyhow::Result<i32> {
|
|
||||||
let mut file_input = File::open(&self.input)?;
|
|
||||||
let mut header = [0u8; 0x40];
|
|
||||||
file_input.read_exact(&mut header)?;
|
|
||||||
let kgm_header = Header::from_buffer(&mut header)?;
|
|
||||||
let decipher = kgm_header.make_decipher()?;
|
|
||||||
file_input.seek(SeekFrom::Start(kgm_header.offset_to_data as u64))?;
|
|
||||||
|
|
||||||
let mut offset = 0usize;
|
|
||||||
let mut buffer = vec![0u8; cli.buffer_size].into_boxed_slice();
|
|
||||||
let mut file_output = File::create(&self.output)?;
|
|
||||||
while let Ok(n) = file_input.read(&mut buffer) {
|
|
||||||
if n == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
decipher.decrypt(&mut buffer[..n], offset);
|
|
||||||
file_output.write_all(&buffer[..n])?;
|
|
||||||
offset += n;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(0)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,5 @@
|
|||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
|
|
||||||
pub mod kgm;
|
|
||||||
pub mod ncm;
|
pub mod ncm;
|
||||||
pub mod qmc1;
|
pub mod qmc1;
|
||||||
pub mod qmc2;
|
pub mod qmc2;
|
||||||
@ -13,6 +12,4 @@ pub enum Commands {
|
|||||||
QMCv2(qmc2::ArgsQMCv2),
|
QMCv2(qmc2::ArgsQMCv2),
|
||||||
#[command(name = "ncm")]
|
#[command(name = "ncm")]
|
||||||
NCM(ncm::ArgsNCM),
|
NCM(ncm::ArgsNCM),
|
||||||
#[command(name = "kgm")]
|
|
||||||
KGM(kgm::ArgsKGM),
|
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ pub struct ArgsQMCv2 {
|
|||||||
info_only: bool,
|
info_only: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_ekey(ekey: &str) -> Result<Vec<u8>> {
|
fn read_ekey(ekey: &str) -> Result<Box<[u8]>> {
|
||||||
let mut external_file = false;
|
let mut external_file = false;
|
||||||
let mut decrypt_ekey = true;
|
let mut decrypt_ekey = true;
|
||||||
|
|
||||||
@ -54,7 +54,7 @@ fn read_ekey(ekey: &str) -> Result<Vec<u8>> {
|
|||||||
let ekey = ekey.trim();
|
let ekey = ekey.trim();
|
||||||
let ekey = match decrypt_ekey {
|
let ekey = match decrypt_ekey {
|
||||||
true => umc_qmc::ekey::decrypt(ekey)?,
|
true => umc_qmc::ekey::decrypt(ekey)?,
|
||||||
false => base64::decode(ekey)?,
|
false => base64::decode(ekey)?.into_boxed_slice(),
|
||||||
};
|
};
|
||||||
Ok(ekey)
|
Ok(ekey)
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,6 @@ fn run_command(cli: &Cli) -> Result<i32> {
|
|||||||
Some(Commands::QMCv1(cmd)) => cmd.run(&cli),
|
Some(Commands::QMCv1(cmd)) => cmd.run(&cli),
|
||||||
Some(Commands::QMCv2(cmd)) => cmd.run(&cli),
|
Some(Commands::QMCv2(cmd)) => cmd.run(&cli),
|
||||||
Some(Commands::NCM(cmd)) => cmd.run(&cli),
|
Some(Commands::NCM(cmd)) => cmd.run(&cli),
|
||||||
Some(Commands::KGM(cmd)) => cmd.run(&cli),
|
|
||||||
None => {
|
None => {
|
||||||
// https://github.com/clap-rs/clap/issues/3857#issuecomment-1161796261
|
// https://github.com/clap-rs/clap/issues/3857#issuecomment-1161796261
|
||||||
todo!("implement a sensible default command, similar to um/cli");
|
todo!("implement a sensible default command, similar to um/cli");
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
[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.
@ -1,59 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
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)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -105,7 +105,7 @@ impl Header {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_decipher<T>(&self, ekey: Option<T>) -> Result<Cipher>
|
pub fn get_cipher<T>(&self, ekey: Option<T>) -> Result<Cipher>
|
||||||
where
|
where
|
||||||
T: AsRef<[u8]>,
|
T: AsRef<[u8]>,
|
||||||
{
|
{
|
||||||
|
@ -5,5 +5,3 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
itertools = "0.13.0"
|
|
||||||
md-5 = "0.10.6"
|
|
||||||
|
@ -1,3 +1 @@
|
|||||||
pub mod base64;
|
pub mod base64;
|
||||||
mod md5;
|
|
||||||
pub use md5::md5;
|
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
use md5::{Digest, Md5};
|
|
||||||
|
|
||||||
pub fn md5<T: AsRef<[u8]>>(buffer: T) -> [u8; 16] {
|
|
||||||
Md5::digest(buffer).into()
|
|
||||||
}
|
|
@ -25,9 +25,9 @@ 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=makeCipher)]
|
||||||
pub fn make_decipher(&self, ekey: Option<String>) -> Result<JsCipher, JsError> {
|
pub fn make_cipher(&self, ekey: Option<String>) -> Result<JsCipher, JsError> {
|
||||||
let cipher = self.0.get_decipher(ekey).map_err(map_js_error)?;
|
let cipher = self.0.get_cipher(ekey).map_err(map_js_error)?;
|
||||||
Ok(JsCipher(cipher))
|
Ok(JsCipher(cipher))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user