Compare commits
No commits in common. "1e1d6421493e8f861e34787762c7ec9acfc92b63" and "d989b15582e0b9a381b0c388a23345c1d063e164" have entirely different histories.
1e1d642149
...
d989b15582
@ -11,8 +11,6 @@
|
|||||||
<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" />
|
||||||
<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/pkg" />
|
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
@ -7,7 +7,7 @@ use core::{KuwoDes, Mode};
|
|||||||
use umc_utils::base64;
|
use umc_utils::base64;
|
||||||
|
|
||||||
/// Decrypt string content
|
/// Decrypt string content
|
||||||
pub fn decrypt_ksing<T: AsRef<[u8]>>(data: T, key: &[u8; 8]) -> Result<String> {
|
pub fn decrypt_ksing(data: &str, key: &[u8; 8]) -> Result<String> {
|
||||||
let mut decoded = base64::decode(data)?;
|
let mut decoded = base64::decode(data)?;
|
||||||
|
|
||||||
let des = KuwoDes::new(key, Mode::Decrypt);
|
let des = KuwoDes::new(key, Mode::Decrypt);
|
||||||
@ -30,7 +30,7 @@ pub fn encrypt_ksing<T: AsRef<[u8]>>(data: T, key: &[u8; 8]) -> Result<String> {
|
|||||||
Ok(base64::encode(data))
|
Ok(base64::encode(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decode_ekey<T: AsRef<[u8]>>(data: T, key: &[u8; 8]) -> Result<String> {
|
pub fn decode_ekey(data: &str, key: &[u8; 8]) -> Result<String> {
|
||||||
let decoded = decrypt_ksing(data, key)?;
|
let decoded = decrypt_ksing(data, key)?;
|
||||||
Ok(decoded[16..].to_string())
|
Ok(decoded[16..].to_string())
|
||||||
}
|
}
|
||||||
|
@ -11,10 +11,7 @@ const KEY: [u8; 0x20] = [
|
|||||||
impl CipherV1 {
|
impl CipherV1 {
|
||||||
pub fn new(resource_id: u32) -> Self {
|
pub fn new(resource_id: u32) -> Self {
|
||||||
let mut key = KEY;
|
let mut key = KEY;
|
||||||
for (k, r) in key
|
for (k, r) in key.iter_mut().zip(resource_id.to_string().as_bytes()) {
|
||||||
.iter_mut()
|
|
||||||
.zip(resource_id.to_string().as_bytes().iter().cycle())
|
|
||||||
{
|
|
||||||
*k ^= r;
|
*k ^= r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use crate::kwm_v1::CipherV1;
|
use crate::kwm_v1::CipherV1;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use byteorder::{ReadBytesExt, LE};
|
use byteorder::{ReadBytesExt, LE};
|
||||||
use std::fmt;
|
|
||||||
use std::io::{Cursor, Read};
|
use std::io::{Cursor, Read};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use umc_qmc::QMCv2Cipher;
|
use umc_qmc::QMCv2Cipher;
|
||||||
@ -14,16 +13,13 @@ pub use umc_qmc::QMCv2Cipher as CipherV2;
|
|||||||
/// Commonly used secret key for Kuwo services.
|
/// Commonly used secret key for Kuwo services.
|
||||||
pub const SECRET_KEY: [u8; 8] = *b"ylzsxkwm";
|
pub const SECRET_KEY: [u8; 8] = *b"ylzsxkwm";
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct HeaderMagicBytes(pub [u8; 16]);
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum KuwoCryptoError {
|
pub enum KuwoCryptoError {
|
||||||
#[error("Invalid DES data size (expected: {0} mod 8 == 0)")]
|
#[error("Invalid DES data size (expected: {0} mod 8 == 0)")]
|
||||||
InvalidDesDataSize(usize),
|
InvalidDesDataSize(usize),
|
||||||
|
|
||||||
#[error("Invalid KWM header magic bytes: {0}")]
|
#[error("Invalid KWM header magic bytes: {0:?}")]
|
||||||
InvalidHeaderMagic(HeaderMagicBytes),
|
InvalidHeaderMagic([u8; 16]),
|
||||||
|
|
||||||
#[error("KWMv2: EKey required")]
|
#[error("KWMv2: EKey required")]
|
||||||
V2EKeyRequired,
|
V2EKeyRequired,
|
||||||
@ -32,15 +28,6 @@ pub enum KuwoCryptoError {
|
|||||||
UnsupportedVersion(usize),
|
UnsupportedVersion(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for HeaderMagicBytes {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
for byte in self.0.iter() {
|
|
||||||
write!(f, "{:02x} ", byte)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const DATA_START_OFFSET: usize = 0x400;
|
pub const DATA_START_OFFSET: usize = 0x400;
|
||||||
|
|
||||||
pub enum Cipher {
|
pub enum Cipher {
|
||||||
@ -91,8 +78,8 @@ impl Header {
|
|||||||
let mut format_name = [0u8; 0x0C];
|
let mut format_name = [0u8; 0x0C];
|
||||||
cursor.read_exact(&mut format_name)?;
|
cursor.read_exact(&mut format_name)?;
|
||||||
|
|
||||||
if magic != Self::MAGIC_1 && magic != Self::MAGIC_2 {
|
if magic != Self::MAGIC_1 || magic != Self::MAGIC_2 {
|
||||||
Err(KuwoCryptoError::InvalidHeaderMagic(HeaderMagicBytes(magic)))?;
|
Err(KuwoCryptoError::InvalidHeaderMagic(magic))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
@ -112,7 +99,7 @@ impl Header {
|
|||||||
let cipher = match self.version {
|
let cipher = match self.version {
|
||||||
1 => Cipher::V1(CipherV1::new(self.resource_id)),
|
1 => Cipher::V1(CipherV1::new(self.resource_id)),
|
||||||
2 => match ekey {
|
2 => match ekey {
|
||||||
Some(ekey) => Cipher::V2(CipherV2::new_from_ekey(ekey)?),
|
Some(ekey) => Cipher::V2(CipherV2::new(ekey)?),
|
||||||
None => Err(KuwoCryptoError::V2EKeyRequired)?,
|
None => Err(KuwoCryptoError::V2EKeyRequired)?,
|
||||||
},
|
},
|
||||||
version => Err(KuwoCryptoError::UnsupportedVersion(version as usize))?,
|
version => Err(KuwoCryptoError::UnsupportedVersion(version as usize))?,
|
||||||
@ -134,9 +121,9 @@ impl Header {
|
|||||||
pub struct CipherBoDian(QMCv2Cipher);
|
pub struct CipherBoDian(QMCv2Cipher);
|
||||||
|
|
||||||
impl CipherBoDian {
|
impl CipherBoDian {
|
||||||
pub fn new<T: AsRef<[u8]>>(ekey: T) -> Result<Self> {
|
pub fn new(ekey: &str) -> Result<Self> {
|
||||||
let ekey = des::decode_ekey(ekey, &SECRET_KEY)?;
|
let ekey = des::decode_ekey(&ekey, &SECRET_KEY)?;
|
||||||
let cipher = CipherV2::new_from_ekey(ekey.as_str())?;
|
let cipher = CipherV2::new(ekey)?;
|
||||||
Ok(Self(cipher))
|
Ok(Self(cipher))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ fn make_simple_key<const N: usize>() -> [u8; N] {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decrypt_v1(ekey: &[u8]) -> Result<Vec<u8>> {
|
pub fn decrypt_v1(ekey: &[u8]) -> Result<Box<[u8]>> {
|
||||||
if ekey.len() < 12 {
|
if ekey.len() < 12 {
|
||||||
Err(EKeyDecryptError::EKeyTooShort)?;
|
Err(EKeyDecryptError::EKeyTooShort)?;
|
||||||
}
|
}
|
||||||
@ -58,10 +58,10 @@ pub fn decrypt_v1(ekey: &[u8]) -> Result<Vec<u8>> {
|
|||||||
.collect_vec();
|
.collect_vec();
|
||||||
|
|
||||||
let plaintext = tc_tea::decrypt(cipher, tea_key).map_err(EKeyDecryptError::FailDecryptV1)?;
|
let plaintext = tc_tea::decrypt(cipher, tea_key).map_err(EKeyDecryptError::FailDecryptV1)?;
|
||||||
Ok([header, &plaintext].concat())
|
Ok([header, &plaintext].concat().into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decrypt_v2(ekey: &[u8]) -> Result<Vec<u8>> {
|
pub fn decrypt_v2(ekey: &[u8]) -> Result<Box<[u8]>> {
|
||||||
let ekey = base64::decode(ekey)?;
|
let ekey = base64::decode(ekey)?;
|
||||||
let ekey = tc_tea::decrypt(ekey, EKEY_V2_KEY1).map_err(EKeyDecryptError::FailDecryptV2)?;
|
let ekey = tc_tea::decrypt(ekey, EKEY_V2_KEY1).map_err(EKeyDecryptError::FailDecryptV2)?;
|
||||||
let ekey = tc_tea::decrypt(ekey, EKEY_V2_KEY2).map_err(EKeyDecryptError::FailDecryptV2)?;
|
let ekey = tc_tea::decrypt(ekey, EKEY_V2_KEY2).map_err(EKeyDecryptError::FailDecryptV2)?;
|
||||||
@ -70,7 +70,7 @@ pub fn decrypt_v2(ekey: &[u8]) -> Result<Vec<u8>> {
|
|||||||
decrypt_v1(&ekey)
|
decrypt_v1(&ekey)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decrypt<T: AsRef<[u8]>>(ekey: T) -> Result<Vec<u8>> {
|
pub fn decrypt<T: AsRef<[u8]>>(ekey: T) -> Result<Box<[u8]>> {
|
||||||
let ekey = ekey.as_ref();
|
let ekey = ekey.as_ref();
|
||||||
match ekey.strip_prefix(EKEY_V2_PREFIX) {
|
match ekey.strip_prefix(EKEY_V2_PREFIX) {
|
||||||
Some(v2_ekey) => decrypt_v2(v2_ekey),
|
Some(v2_ekey) => decrypt_v2(v2_ekey),
|
||||||
|
@ -35,11 +35,6 @@ impl QMCv2Cipher {
|
|||||||
Ok(cipher)
|
Ok(cipher)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_from_ekey<T: AsRef<[u8]>>(ekey_str: T) -> Result<Self> {
|
|
||||||
let key = ekey::decrypt(ekey_str)?;
|
|
||||||
Self::new(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
||||||
|
@ -19,7 +19,8 @@ impl JsQMC2 {
|
|||||||
/// Create a new QMC2 (mgg/mflac) cipher instance.
|
/// Create a new QMC2 (mgg/mflac) cipher instance.
|
||||||
#[wasm_bindgen(constructor)]
|
#[wasm_bindgen(constructor)]
|
||||||
pub fn new(ekey: &str) -> Result<JsQMC2, JsError> {
|
pub fn new(ekey: &str) -> Result<JsQMC2, JsError> {
|
||||||
let cipher = QMCv2Cipher::new_from_ekey(ekey).map_err(map_js_error)?;
|
let key = umc_qmc::ekey::decrypt(ekey).map_err(map_js_error)?;
|
||||||
|
let cipher = QMCv2Cipher::new(key).map_err(map_js_error)?;
|
||||||
Ok(JsQMC2(cipher))
|
Ok(JsQMC2(cipher))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@unlock-music/crypto",
|
"name": "@unlock-music/crypto",
|
||||||
"version": "0.0.0-alpha.11",
|
"version": "0.0.0-alpha.10",
|
||||||
"description": "Project Unlock Music: 加解密支持库",
|
"description": "Project Unlock Music: 加解密支持库",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node build.js",
|
"build": "node build.js",
|
||||||
|
Loading…
Reference in New Issue
Block a user