From 2195896fe07699b104104a0f76b56f91a1a0633e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B2=81=E6=A0=91=E4=BA=BA?= Date: Fri, 6 Sep 2024 00:51:56 +0100 Subject: [PATCH] feat: add ekey --- um_crypto/qmc/Cargo.toml | 7 ++-- um_crypto/qmc/src/ekey.rs | 73 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 um_crypto/qmc/src/ekey.rs diff --git a/um_crypto/qmc/Cargo.toml b/um_crypto/qmc/Cargo.toml index a4fd420..e1fa004 100644 --- a/um_crypto/qmc/Cargo.toml +++ b/um_crypto/qmc/Cargo.toml @@ -4,8 +4,9 @@ version = "0.1.0" edition = "2021" [dependencies] -base64 = "0.22.1" -itertools = "0.13.0" anyhow = "1.0.86" -thiserror = "1.0.63" byteorder = "1.5.0" +itertools = "0.13.0" +tc_tea = "0.1.4" +thiserror = "1.0.63" +umc_utils = { path = "../utils" } \ No newline at end of file diff --git a/um_crypto/qmc/src/ekey.rs b/um_crypto/qmc/src/ekey.rs new file mode 100644 index 0000000..8d138fb --- /dev/null +++ b/um_crypto/qmc/src/ekey.rs @@ -0,0 +1,73 @@ +use anyhow::Result; +use itertools::Itertools; +use std::ops::Mul; +use thiserror::Error; +use umc_utils::base64; + +/// Base64 encoded prefix: "QQMusic EncV2,Key:" +const EKEY_V2_PREFIX: &[u8; 24] = b"UVFNdXNpYyBFbmNWMixLZXk6"; +const EKEY_V2_KEY1: [u8; 16] = [ + 0x33, 0x38, 0x36, 0x5A, 0x4A, 0x59, 0x21, 0x40, 0x23, 0x2A, 0x24, 0x25, 0x5E, 0x26, 0x29, 0x28, +]; +const EKEY_V2_KEY2: [u8; 16] = [ + 0x2A, 0x2A, 0x23, 0x21, 0x28, 0x23, 0x24, 0x25, 0x26, 0x5E, 0x61, 0x31, 0x63, 0x5A, 0x2C, 0x54, +]; + +#[derive(Debug, Clone, PartialEq, Error)] +pub enum EKeyDecryptError { + #[error("EKey is too short for decryption")] + EKeyTooShort, + #[error("Error when decrypting ekey v1")] + FailDecryptV1, + #[error("Error when decrypting ekey v2")] + FailDecryptV2, +} + +fn make_simple_key() -> [u8; N] { + let mut result = [0u8; N]; + + for (i, v) in result.iter_mut().enumerate() { + let i = i as f32; + let value = 106.0 + i * 0.1; + let value = value.tan().abs().mul(100.0); + *v = value as u8; + } + + result +} + +pub fn decrypt_v1(ekey: &[u8]) -> Result> { + if ekey.len() < 12 { + Err(EKeyDecryptError::EKeyTooShort)?; + } + + let ekey = base64::decode(ekey)?; + let (header, cipher) = ekey.split_at(8); + + let simple_key = make_simple_key::<8>(); + let tea_key = simple_key + .iter() + .zip(header) + .flat_map(|(&simple_part, &header_part)| [simple_part, header_part]) + .collect::>(); + + let plaintext = tc_tea::decrypt(cipher, tea_key).ok_or(EKeyDecryptError::FailDecryptV1)?; + Ok([header, &plaintext].concat().into()) +} + +pub fn decrypt_v2(ekey: &[u8]) -> Result> { + let ekey = base64::decode(ekey)?; + let ekey = tc_tea::decrypt(ekey, EKEY_V2_KEY1).ok_or(EKeyDecryptError::FailDecryptV2)?; + let ekey = tc_tea::decrypt(ekey, EKEY_V2_KEY2).ok_or(EKeyDecryptError::FailDecryptV2)?; + let ekey = ekey.iter().take_while(|&&b| b != 0).copied().collect_vec(); + + decrypt_v1(&ekey) +} + +pub fn decrypt>(ekey: T) -> Result> { + let ekey = ekey.as_ref(); + match ekey.strip_prefix(EKEY_V2_PREFIX) { + Some(v2_ekey) => decrypt_v2(v2_ekey), + None => decrypt_v1(ekey), + } +}