mirror of
https://git.um-react.app/um/lib_um_crypto_rust.git
synced 2026-03-08 04:29:54 +00:00
feat: add kugou db decryption logic
This commit is contained in:
@@ -5,6 +5,8 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.5.0"
|
||||
itertools = "0.13.0"
|
||||
thiserror = "2.0.7"
|
||||
umc_utils = { path = "../utils" }
|
||||
aes = "0.8.4"
|
||||
cbc = "0.1.2"
|
||||
block-padding = "0.3.3"
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
pub mod header;
|
||||
mod pc_db_decrypt;
|
||||
pub mod v2;
|
||||
pub mod v3;
|
||||
|
||||
pub use pc_db_decrypt::decrypt_db;
|
||||
|
||||
use crate::header::Header;
|
||||
use crate::v2::DecipherV2;
|
||||
use crate::v3::DecipherV3;
|
||||
use thiserror::Error;
|
||||
|
||||
use block_padding::UnpadError;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum KugouError {
|
||||
#[error("Header too small, need at least {0} bytes.")]
|
||||
@@ -23,6 +28,18 @@ pub enum KugouError {
|
||||
|
||||
#[error("Unsupported cipher (self-test failed)")]
|
||||
SelfTestFailed,
|
||||
|
||||
#[error("Failed decrypt kugou db data: {0}")]
|
||||
DecryptKugouDbError(UnpadError),
|
||||
|
||||
#[error("Invalid database size: {0}")]
|
||||
InvalidDatabaseSize(usize),
|
||||
|
||||
#[error("Failed to decrypt page 1 (invalid header)")]
|
||||
DecryptPage1Failed,
|
||||
|
||||
#[error("Database does not seem valid")]
|
||||
InvalidPage1Header,
|
||||
}
|
||||
|
||||
pub enum Decipher {
|
||||
|
||||
114
um_crypto/kgm/src/pc_db_decrypt/key_derive.rs
Normal file
114
um_crypto/kgm/src/pc_db_decrypt/key_derive.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
use byteorder::{ByteOrder, LE};
|
||||
use umc_utils::md5;
|
||||
|
||||
use aes::cipher::{
|
||||
block_padding::NoPadding, generic_array::GenericArray, BlockDecryptMut, KeyIvInit,
|
||||
};
|
||||
|
||||
use crate::KugouError;
|
||||
|
||||
type Aes128CbcDec = cbc::Decryptor<aes::Aes128Dec>;
|
||||
|
||||
const DEFAULT_MASTER_KEY: [u8; 0x18] = [
|
||||
// master key (0x10 bytes)
|
||||
0x1D, 0x61, 0x31, 0x45, 0xB2, 0x47, 0xBF, 0x7F, 0x3D, 0x18, 0x96, 0x72, 0x14, 0x4F, 0xE4, 0xBF,
|
||||
0x00, 0x00, 0x00, 0x00, // page number (le)
|
||||
0x73, 0x41, 0x6C, 0x54, // fixed value
|
||||
];
|
||||
|
||||
fn next_page_iv(seed: u32) -> u32 {
|
||||
let left = seed.wrapping_mul(0x9EF4);
|
||||
let right = seed.wrapping_div(0xce26).wrapping_mul(0x7FFFFF07);
|
||||
let value = left.wrapping_sub(right);
|
||||
match value & 0x8000_0000 {
|
||||
0 => value,
|
||||
_ => value.wrapping_add(0x7FFF_FF07),
|
||||
}
|
||||
}
|
||||
|
||||
fn derive_page_aes_key(seed: u32) -> [u8; 0x10] {
|
||||
let mut master_key = DEFAULT_MASTER_KEY;
|
||||
LE::write_u32(&mut master_key[0x10..0x14], seed);
|
||||
md5(&mut master_key)
|
||||
}
|
||||
|
||||
fn derive_page_aes_iv(seed: u32) -> [u8; 0x10] {
|
||||
let mut buffer = [0u8; 0x10];
|
||||
let mut iv = seed + 1;
|
||||
for i in (0..0x10).step_by(4) {
|
||||
iv = next_page_iv(iv);
|
||||
LE::write_u32(&mut buffer[i..i + 4], iv);
|
||||
}
|
||||
md5(buffer)
|
||||
}
|
||||
|
||||
/// Page number starts from 1.
|
||||
/// Buffer should have size of ().
|
||||
pub fn decrypt_db_page(buffer: &mut [u8], page_number: u32) -> Result<(), KugouError> {
|
||||
let key = derive_page_aes_key(page_number);
|
||||
let iv = derive_page_aes_iv(page_number);
|
||||
|
||||
let key = GenericArray::from(key);
|
||||
let iv = GenericArray::from(iv);
|
||||
let dec = Aes128CbcDec::new(&key, &iv);
|
||||
dec.decrypt_padded_mut::<NoPadding>(buffer)
|
||||
.map_err(KugouError::DecryptKugouDbError)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn test_derive(page_no: u32, expected_key: [u8; 0x10], expected_iv: [u8; 0x10]) {
|
||||
let aes_key = derive_page_aes_key(page_no);
|
||||
assert_eq!(aes_key, expected_key, "key mismatch for page {}", page_no);
|
||||
|
||||
let aes_iv = derive_page_aes_iv(page_no);
|
||||
assert_eq!(aes_iv, expected_iv, "iv mismatch for page {}", page_no);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_derive_page_0_iv() {
|
||||
test_derive(
|
||||
0,
|
||||
[
|
||||
0x19, 0x62, 0xc0, 0x5f, 0xa2, 0xeb, 0xbe, 0x24, 0x28, 0xff, 0x52, 0x2b, 0x9e, 0x03,
|
||||
0xea, 0xd4,
|
||||
],
|
||||
[
|
||||
0x05, 0x5a, 0x67, 0x35, 0x93, 0x89, 0x2d, 0xdf, 0x3a, 0xb3, 0xb3, 0xc6, 0x21, 0xc3,
|
||||
0x48, 0x02,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_derive_page_12345_iv() {
|
||||
test_derive(
|
||||
12345,
|
||||
[
|
||||
0xc1, 0x70, 0x06, 0x4e, 0xf8, 0x1e, 0x15, 0x35, 0xc2, 0x9a, 0x65, 0xe4, 0xb6, 0xf5,
|
||||
0x78, 0xe9,
|
||||
],
|
||||
[
|
||||
0xd0, 0xcd, 0x91, 0xd0, 0x23, 0xc5, 0x1e, 0x21, 0xbc, 0x01, 0xaa, 0xd2, 0x81, 0x4c,
|
||||
0x9b, 0xb8,
|
||||
],
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn test_derive_page_498651347_iv() {
|
||||
test_derive(
|
||||
498651347,
|
||||
[
|
||||
0x5a, 0x69, 0xb3, 0xdc, 0x58, 0xca, 0x16, 0x2e, 0xb4, 0xa7, 0x71, 0x4e, 0xf2, 0x73,
|
||||
0x6b, 0xf7,
|
||||
],
|
||||
[
|
||||
0x62, 0xa7, 0x22, 0x26, 0x64, 0x08, 0x89, 0xb8, 0xff, 0x5d, 0xdc, 0x31, 0x7e, 0x7c,
|
||||
0x7e, 0xcc,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
71
um_crypto/kgm/src/pc_db_decrypt/mod.rs
Normal file
71
um_crypto/kgm/src/pc_db_decrypt/mod.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
mod key_derive;
|
||||
|
||||
use crate::KugouError;
|
||||
use byteorder::{ByteOrder, LE};
|
||||
use key_derive::decrypt_db_page;
|
||||
|
||||
const PAGE_SIZE: usize = 0x400;
|
||||
const SQLITE_HEADER: [u8; 0x10] = *b"SQLite format 3\0";
|
||||
|
||||
fn validate_page_1_header(header: &[u8]) -> Result<(), KugouError> {
|
||||
let o10 = LE::read_u32(&header[0x10..0x14]);
|
||||
let o14 = LE::read_u32(&header[0x14..0x18]);
|
||||
|
||||
let v6 = ((o10 & 0xff) << 8) | ((o10 & 0xff00) << 16);
|
||||
let ok = o14 == 0x20204000 && (v6 - 0x200) <= 0xFE00 && ((v6 - 1) & v6) == 0;
|
||||
if !ok {
|
||||
Err(KugouError::InvalidPage1Header)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn decrypt_db<T: AsMut<[u8]> + ?Sized>(buffer: &mut T) -> Result<(), KugouError> {
|
||||
let buffer = buffer.as_mut();
|
||||
let db_size = buffer.len();
|
||||
|
||||
// not encrypted
|
||||
if buffer.starts_with(&SQLITE_HEADER) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if db_size % PAGE_SIZE != 0 || db_size == 0 {
|
||||
Err(KugouError::InvalidDatabaseSize(db_size))?;
|
||||
}
|
||||
|
||||
let last_page = db_size / PAGE_SIZE;
|
||||
|
||||
// page 1 is the header
|
||||
decrypt_page_1(&mut buffer[0..PAGE_SIZE])?;
|
||||
|
||||
let mut offset = PAGE_SIZE;
|
||||
for page_no in 2..=last_page {
|
||||
decrypt_db_page(&mut buffer[offset..offset + PAGE_SIZE], page_no as u32)?;
|
||||
offset += PAGE_SIZE;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn decrypt_page_1(page: &mut [u8]) -> Result<(), KugouError> {
|
||||
validate_page_1_header(page)?;
|
||||
|
||||
// Backup expected hdr value
|
||||
let mut expected_hdr_value = [0u8; 0x08];
|
||||
expected_hdr_value.copy_from_slice(&page[0x10..0x18]);
|
||||
|
||||
// Copy encrypted hdr over
|
||||
let (hdr, encrypted_page_data) = page.split_at_mut(0x10);
|
||||
encrypted_page_data[0..0x08].copy_from_slice(&hdr[0x08..0x10]);
|
||||
|
||||
decrypt_db_page(encrypted_page_data, 1)?;
|
||||
|
||||
// Validate header
|
||||
if encrypted_page_data[..8] != expected_hdr_value[..8] {
|
||||
Err(KugouError::DecryptPage1Failed)?;
|
||||
}
|
||||
|
||||
// Apply SQLite header
|
||||
hdr.copy_from_slice(&SQLITE_HEADER);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user