feat: qrc file support
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing

This commit is contained in:
鲁树人 2024-10-06 19:55:41 +01:00
parent fdc867bbc3
commit 051805a019
25 changed files with 495 additions and 15 deletions

View File

@ -16,6 +16,7 @@
<sourceFolder url="file://$MODULE_DIR$/um_crypto/xiami/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/um_crypto/xiami/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/um_crypto/qtfm/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/um_crypto/qtfm/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/um_crypto/mg3d/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/um_crypto/mg3d/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/um_crypto/qrc/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" />

36
Cargo.lock generated
View File

@ -2,6 +2,12 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]] [[package]]
name = "aes" name = "aes"
version = "0.8.4" version = "0.8.4"
@ -376,6 +382,15 @@ dependencies = [
"walkdir", "walkdir",
] ]
[[package]]
name = "miniz_oxide"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
dependencies = [
"adler2",
]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.19.0" version = "1.19.0"
@ -493,18 +508,18 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.63" version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.63" version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -558,6 +573,7 @@ dependencies = [
"umc_mg3d", "umc_mg3d",
"umc_ncm", "umc_ncm",
"umc_qmc", "umc_qmc",
"umc_qrc",
"umc_qtfm", "umc_qtfm",
"umc_xiami", "umc_xiami",
"umc_xmly", "umc_xmly",
@ -639,6 +655,18 @@ dependencies = [
"umc_utils", "umc_utils",
] ]
[[package]]
name = "umc_qrc"
version = "0.1.0"
dependencies = [
"byteorder",
"hex",
"itertools",
"miniz_oxide",
"thiserror",
"umc_qmc",
]
[[package]] [[package]]
name = "umc_qtfm" name = "umc_qtfm"
version = "0.1.0" version = "0.1.0"

View File

@ -5,4 +5,4 @@ edition = "2021"
[dependencies] [dependencies]
byteorder = "1.5.0" byteorder = "1.5.0"
thiserror = "1.0.63" thiserror = "1.0.64"

View File

@ -10,6 +10,6 @@ cipher = "0.4.4"
hmac = "0.12.1" hmac = "0.12.1"
pbkdf2 = "0.12.2" pbkdf2 = "0.12.2"
sha1 = "0.10.5" sha1 = "0.10.5"
thiserror = "1.0.63" thiserror = "1.0.64"
umc_qmc = { path = "../qmc" } umc_qmc = { path = "../qmc" }
umc_utils = { path = "../utils" } umc_utils = { path = "../utils" }

View File

@ -6,5 +6,5 @@ edition = "2021"
[dependencies] [dependencies]
byteorder = "1.5.0" byteorder = "1.5.0"
itertools = "0.13.0" itertools = "0.13.0"
thiserror = "1.0.63" thiserror = "1.0.64"
umc_utils = { path = "../utils" } umc_utils = { path = "../utils" }

View File

@ -7,6 +7,6 @@ edition = "2021"
anyhow = "1.0.86" anyhow = "1.0.86"
byteorder = "1.5.0" byteorder = "1.5.0"
itertools = "0.13.0" itertools = "0.13.0"
thiserror = "1.0.63" thiserror = "1.0.64"
umc_qmc = { path = "../qmc" } umc_qmc = { path = "../qmc" }
umc_utils = { path = "../utils" } umc_utils = { path = "../utils" }

View File

@ -5,5 +5,5 @@ edition = "2021"
[dependencies] [dependencies]
hex = "0.4.3" hex = "0.4.3"
thiserror = "1.0.63" thiserror = "1.0.64"
umc_utils = { path = "../utils" } umc_utils = { path = "../utils" }

View File

@ -9,7 +9,7 @@ byteorder = "1.5.0"
cipher = { version = "0.4.4", features = ["block-padding"] } cipher = { version = "0.4.4", features = ["block-padding"] }
crc = "3.2.1" crc = "3.2.1"
itertools = "0.13.0" itertools = "0.13.0"
thiserror = "1.0.63" thiserror = "1.0.64"
umc_utils = { path = "../utils" } umc_utils = { path = "../utils" }
[dev-dependencies] [dev-dependencies]

View File

@ -9,5 +9,5 @@ byteorder = "1.5.0"
itertools = "0.13.0" itertools = "0.13.0"
lazy_static = "1.5.0" lazy_static = "1.5.0"
tc_tea = { version = "0.2.0", default-features = false } tc_tea = { version = "0.2.0", default-features = false }
thiserror = "1.0.63" thiserror = "1.0.64"
umc_utils = { path = "../utils" } umc_utils = { path = "../utils" }

12
um_crypto/qrc/Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "umc_qrc"
version = "0.1.0"
edition = "2021"
[dependencies]
byteorder = "1.5.0"
itertools = "0.13.0"
miniz_oxide = "0.8.0"
thiserror = "1.0.64"
umc_qmc = { path = "../qmc" }
hex = "0.4.3"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
B2C78CE32CF1913A99A770B577281231519C5CEA3E75D8B9E80B77B6B6DC5857E948004C2C3D1BC62F22DF38BB8AE5BE33950BAB349485EEFB1CB2BC92657302BF90B85913E2349699AF483235ACF654FC89E8F1D32963D3463CD98D1CF4359AD509CE87B157537CE60F56598AD498D2AA4F46FF0E3948D5F88081E519026C3D952F58ED5E5BCA16827482D37BE303D39091BA5CFF7A1816AA3D25F3E1668BF4E4EDCA0F5FA3833536A6ED3A4D9C213B911BE32B33553C7D4730753E4023ACCEB54AFD6FDE8316F07AB12AC4C53147D26F2927F1C32090711B4C0F5A6B25442CA687C9BF0B5210D51F2E9735E2BEFD6C0C8BBA24FA622AA260851AB2FA48155193B6FE3BEB28BB5604E3ACBAD074398C3FD92E0EB439C87CF730044C550AC3580D228AE18178FB7800F809D244EDB18BE65DD4EA1DCA7B9E8F0C8ECCE028D3C26BDA11D5FE466665C124BD21187D0E000B127D59A6CADECE061F3E327D21CB2811E8D9B82B7FA8BAC20C3839C28A34FBB1AEDDD2524D393363D892F37032C2D1FCD45C92BF1C19643CBB2D58841C8D56DD8C43F03CE44432565BF91916DFC94BAB104A9EBE0B40731CA5F2D8B1EBE7A09CC7DB66E384F9CFEAE38ED674B8BCB62D095C838F84781624598BA7AD028A1D7A924E14B5352D059B5A1AFBEA82C46CDBBCC96FE0265D775ACB34D25D7181FDA6C06952C5AFF7DAC6164C0ABBFF048ED0EB1997171D944AA6F6FC250DFCB65719A6E5F27A55D0024BD46472403B3A833669CB5B50E44D1F4ACE79C55209744FFF8C3EFD9B26B805C9C97CAA13A4F9CB2F1A4841817648BEA9527C72492FF33458DE512E2BA4A578718D983DD264AE5FF0A8BF2E4BE121C4D862017A8CBDA32088B3A922C2B7CE3A7BB01DAB6AAC48AC42CF3CB1A644A5DF083D4313DD5B42439E0DA4A65A35FF914B33ECC7D4B639E5031AC3F4CCA13360F0EBAB4F3C40BC47B7011C4589A2D493C752B10DF637B21216E7213913ED438D500E1BF3FC60FCC95623924B833FEB06B52B7D3000914EE749272087A34330F801BE8C7432A7CE2A234F9D31354A9AD2449F1097B3AC1F25DCDB0E5EC8246B5065A434B9620D245A132363BB0B4CA4A773526233EA7B429E2EC84C266B235DC01E13FC342A2FA2D26382ABCF0FD93F9A05B3FA0FCDF6FAA3209D5193871187890E2115AEB65F543E6AA7AF8E22C3552AE3C81BBEA9FFC85AFAF404573D60A6523192246FCA2015DE5D8E4B22436F70AE84C458A6AD3A71932CD035D05AA6CC98A6D77B5DD7531DE9BBA04960C3C3ACD610E3209A0D09995B0608DC03D8602AD1CFC2BACA632AB07CD10D3D489F33F30753FFF930F07C58182CDDEFE417A8644F71C1F637B99E4BC85FCCB3D8966DA11849D56605FAAD7D89649753447DC258328D02D3972BAF5B4C8AD0581FE1AC3F5E1975F815A4D66F61A6F0AC8FA3D2F806063AB0054D43018592059B79F74CE032884F584BAD3A9FDAD5EDBB35FA7A6756A85E9B102F9ACCEAC467358F3E34734BBADD50645E6870F84B375226A3D68C29943856A32B3AA138C6ED1F4F17DFE9944D512694E974784344DFA32D4B68118779363CBB4D9AB6D9DB545358E39A5B2E4A2F98F0A7506A9945F6C3549C1DCF563A125D7CF3E42D25F60D44D8C6C00774E1A792FB62337595A31742A7D5CB9C84E45487995DF6ED239A0DD58310D8F9398BE23EEA9608373D3490830CD4B5659E1DF896A0E4B934770483482251B2D3C549630D4E1E1EA54DE95414627AB6A76020A27E0D96D5685DCD244071C3D2FA22431B53313FADE7B3D715C84547FCC9E329FDB9CCD19EFD704E8288B8558C7DC626DA319C18829C65777BC6A53843F3FCF78309CBAA748C75E124F4D839325455C5034F47323124F4A498A090562583C2F7409C8EBCC0C539A4815A1F740297D5C6E0FCBA4081A6B0C76D276E774407A4B7A1A4413EC948D87F53B2BA87D23828F899836332DF86AEA9A6D93208AA4317B4DEDCEAF7D8413B29476DDE79296786E8AC8EDD7306C67851AD7E7EEF222F7F7A4D3B9005CC9A06FA2E9F0C36753C125F4FA8CD42F9607C813384F2AD8C2C79952E5A3CAC41EC159890864C20A1F43F10406F021EF9227C187939E6E2FC7E9B91A16063D616A7B988A2FA65EAA5CB93EF434F2D0DDD763FF1EB4C2E04B5B9D0B4D0F3A5AB8834B220E98B89C3F4EE61C2FF52451213DA4EB7ECE204947CD5A79DE6ECDE7FD53B80F7F9DF4797EBBD9A6FE9FE1D4898276D

View File

@ -0,0 +1,94 @@
pub const KEY_RND_SHIFTS: [u8; 16] = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1];
pub const SBOXES: [[u8; 64]; 8] = [
[
14, 0, 4, 15, 13, 7, 1, 4, 2, 14, 15, 2, 11, 13, 8, 1, 3, 10, 10, 6, 6, 12, 12, 11, 5, 9,
9, 5, 0, 3, 7, 8, 4, 15, 1, 12, 14, 8, 8, 2, 13, 4, 6, 9, 2, 1, 11, 7, 15, 5, 12, 11, 9, 3,
7, 14, 3, 10, 10, 0, 5, 6, 0, 13,
],
[
15, 3, 1, 13, 8, 4, 14, 7, 6, 15, 11, 2, 3, 8, 4, 15, 9, 12, 7, 0, 2, 1, 13, 10, 12, 6, 0,
9, 5, 11, 10, 5, 0, 13, 14, 8, 7, 10, 11, 1, 10, 3, 4, 15, 13, 4, 1, 2, 5, 11, 8, 6, 12, 7,
6, 12, 9, 0, 3, 5, 2, 14, 15, 9,
],
[
10, 13, 0, 7, 9, 0, 14, 9, 6, 3, 3, 4, 15, 6, 5, 10, 1, 2, 13, 8, 12, 5, 7, 14, 11, 12, 4,
11, 2, 15, 8, 1, 13, 1, 6, 10, 4, 13, 9, 0, 8, 6, 15, 9, 3, 8, 0, 7, 11, 4, 1, 15, 2, 14,
12, 3, 5, 11, 10, 5, 14, 2, 7, 12,
],
[
7, 13, 13, 8, 14, 11, 3, 5, 0, 6, 6, 15, 9, 0, 10, 3, 1, 4, 2, 7, 8, 2, 5, 12, 11, 1, 12,
10, 4, 14, 15, 9, 10, 3, 6, 15, 9, 0, 0, 6, 12, 10, 11, 10, 7, 13, 13, 8, 15, 9, 1, 4, 3,
5, 14, 11, 5, 12, 2, 7, 8, 2, 4, 14,
],
[
2, 14, 12, 11, 4, 2, 1, 12, 7, 4, 10, 7, 11, 13, 6, 1, 8, 5, 5, 0, 3, 15, 15, 10, 13, 3, 0,
9, 14, 8, 9, 6, 4, 11, 2, 8, 1, 12, 11, 7, 10, 1, 13, 14, 7, 2, 8, 13, 15, 6, 9, 15, 12, 0,
5, 9, 6, 10, 3, 4, 0, 5, 14, 3,
],
[
12, 10, 1, 15, 10, 4, 15, 2, 9, 7, 2, 12, 6, 9, 8, 5, 0, 6, 13, 1, 3, 13, 4, 14, 14, 0, 7,
11, 5, 3, 11, 8, 9, 4, 14, 3, 15, 2, 5, 12, 2, 9, 8, 5, 12, 15, 3, 10, 7, 11, 0, 14, 4, 1,
10, 7, 1, 6, 13, 0, 11, 8, 6, 13,
],
[
4, 13, 11, 0, 2, 11, 14, 7, 15, 4, 0, 9, 8, 1, 13, 10, 3, 14, 12, 3, 9, 5, 7, 12, 5, 2, 10,
15, 6, 8, 1, 6, 1, 6, 4, 11, 11, 13, 13, 8, 12, 1, 3, 4, 7, 10, 14, 7, 10, 9, 15, 5, 6, 0,
8, 15, 0, 14, 5, 2, 9, 3, 2, 12,
],
[
13, 1, 2, 15, 8, 13, 4, 8, 6, 10, 15, 3, 11, 7, 1, 4, 10, 12, 9, 5, 3, 6, 14, 11, 5, 0, 0,
14, 12, 9, 7, 2, 7, 2, 11, 1, 4, 14, 1, 7, 9, 4, 12, 10, 14, 8, 2, 13, 0, 15, 6, 12, 10, 9,
13, 0, 15, 3, 3, 5, 5, 6, 8, 11,
],
];
pub const PBOX: [u8; 32] = [
15, 6, 19, 20, 28, 11, 27, 16, 0, 14, 22, 25, 4, 17, 30, 9, 1, 7, 23, 13, 31, 26, 2, 8, 18, 12,
29, 5, 21, 10, 3, 24,
];
pub const IP: [u8; 64] = [
57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63,
55, 47, 39, 31, 23, 15, 7, 56, 48, 40, 32, 24, 16, 8, 0, 58, 50, 42, 34, 26, 18, 10, 2, 60, 52,
44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6,
];
pub const IP_INV: [u8; 64] = [
39, 7, 47, 15, 55, 23, 63, 31, 38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29,
36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, 34, 2, 42, 10, 50, 18, 58, 26,
33, 1, 41, 9, 49, 17, 57, 25, 32, 0, 40, 8, 48, 16, 56, 24,
];
pub const KEY_PERMUTATION_TABLE: [u8; 56] = [
// key_param_c
56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59,
51, 43, 35, //
// key_param_d
62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27,
19, 11, 3,
];
pub const KEY_COMPRESSION: [u8; 48] = [
// part 1
13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9, 22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1,
// part 2
45, 56, 35, 41, 51, 59, 34, 44, 55, 49, 37, 52, 48, 53, 43, 60, 38, 57, 50, 46, 54, 40, 33, 36,
];
pub const KEY_EXPANSION: [u8; 48] = [
31, 0, 1, 2, 3, 4, 3, 4, 5, 6, 7, 8, 7, 8, 9, 10, 11, 12, 11, 12, 13, 14, 15, 16, 15, 16, 17,
18, 19, 20, 19, 20, 21, 22, 23, 24, 23, 24, 25, 26, 27, 28, 27, 28, 29, 30, 31, 0,
];
pub const U64_SHIFT_TABLE_CACHE: [u64; 64] = {
let mut data = [0u64; 64];
let mut i = 0;
while i < 32 {
data[i] = 1u64 << (31 - i);
data[i + 32] = 1u64 << (31 - i + 32);
i += 1;
}
data
};

View File

@ -0,0 +1,148 @@
use super::constants;
use super::utils::{hi32, lo32, make_u64, map_u32_bits, map_u64, swap_u64};
use crate::QrcError;
use byteorder::{ByteOrder, LE};
use itertools::Either;
type DesSubKeys = [u64; 16];
pub enum DESMode {
Encrypt,
Decrypt,
}
/// QRC's modified DES implementation
#[derive(Debug, Default, Clone, Copy)]
pub struct QrcDes {
keys: DesSubKeys,
}
impl QrcDes {
fn ip(data: u64) -> u64 {
map_u64(data, &constants::IP)
}
fn ip_inv(data: u64) -> u64 {
map_u64(data, &constants::IP_INV)
}
const SBOX_SHIFTS: [u8; 8] = [26, 20, 14, 8, 58, 52, 46, 40];
fn sbox_transform(state: u64) -> u32 {
let stream = constants::SBOXES.iter().zip(Self::SBOX_SHIFTS);
stream.fold(0u32, |result, (sbox, large_state_shift)| {
let sbox_idx = (state >> large_state_shift) & 0b111111;
(result << 4) | (sbox[sbox_idx as usize] as u32)
})
}
fn des_crypt_proc(state: u64, key: u64) -> u64 {
let mut state = state;
let state_hi32 = hi32(state);
let state_lo32 = lo32(state);
state = map_u64(make_u64(state_hi32, state_hi32), &constants::KEY_EXPANSION);
state ^= key;
let mut next_lo32 = Self::sbox_transform(state);
next_lo32 = map_u32_bits(next_lo32, &constants::PBOX);
next_lo32 ^= state_lo32;
make_u64(next_lo32, state_hi32)
}
/// Create a new QrcDes Instance
pub fn new(key: &[u8; 8], mode: DESMode) -> Self {
Self {
keys: Self::derive_subkeys(key, mode),
}
}
fn derive_subkeys(key: &[u8; 8], mode: DESMode) -> DesSubKeys {
let key = u64::from_le_bytes(*key);
let param = map_u64(key, &constants::KEY_PERMUTATION_TABLE);
let mut param_c = lo32(param);
let mut param_d = hi32(param);
let update_param = |param: &mut u32, shift_left: u8| {
let shift_right = 28 - shift_left;
*param = (*param << shift_left) | ((*param >> shift_right) & 0xFFFFFFF0);
};
let mut subkeys = DesSubKeys::default();
let key_iter = match mode {
DESMode::Decrypt => Either::Left(subkeys.iter_mut().rev()),
DESMode::Encrypt => Either::Right(subkeys.iter_mut()),
};
for (subkey, shift_left) in key_iter.zip(constants::KEY_RND_SHIFTS) {
update_param(&mut param_c, shift_left);
update_param(&mut param_d, shift_left);
let key = make_u64(param_d, param_c);
*subkey = map_u64(key, &constants::KEY_COMPRESSION);
}
subkeys
}
pub fn transform_block(&self, data: u64) -> u64 {
let mut state = Self::ip(data);
let keys = self.keys.iter();
state = keys.fold(state, |state, &key| Self::des_crypt_proc(state, key));
// Swap data hi32/lo32
state = swap_u64(state);
// Final permutation
state = Self::ip_inv(state);
state
}
pub fn transform_bytes(&self, data: &mut [u8]) -> Result<(), QrcError> {
if data.len() % 8 != 0 {
Err(QrcError::QRCDesInputSizeError)?;
}
for block in data.chunks_exact_mut(8) {
let value = LE::read_u64(block);
let transformed = self.transform_block(value);
LE::write_u64(block, transformed);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::{DESMode, QrcDes};
#[test]
fn test_des_decrypt() {
let mut input = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6];
let expected_data = [
0xFD, 0x0E, 0x64, 0x06, 0x65, 0xBE, 0x74, 0x13, //
0x77, 0x63, 0x3B, 0x02, 0x45, 0x4E, 0x70, 0x7A, //
];
let des = QrcDes::new(b"TEST!KEY", DESMode::Decrypt);
des.transform_bytes(&mut input).unwrap();
assert_eq!(input, expected_data);
}
#[test]
fn test_des_encrypt() {
let mut input = [
0xFD, 0x0E, 0x64, 0x06, 0x65, 0xBE, 0x74, 0x13, //
0x77, 0x63, 0x3B, 0x02, 0x45, 0x4E, 0x70, 0x7A, //
];
let expected_data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6];
let des = QrcDes::new(b"TEST!KEY", DESMode::Encrypt);
des.transform_bytes(&mut input).unwrap();
assert_eq!(input, expected_data);
}
}

View File

@ -0,0 +1,5 @@
mod constants;
mod des;
mod utils;
pub use des::{DESMode, QrcDes};

View File

@ -0,0 +1,55 @@
use crate::des::constants::U64_SHIFT_TABLE_CACHE;
pub const fn make_u64(hi32: u32, lo32: u32) -> u64 {
((hi32 as u64) << 32) | (lo32 as u64)
}
pub const fn swap_u64(value: u64) -> u64 {
(value.wrapping_shr(32)) | (value.wrapping_shl(32))
}
pub const fn lo32(value: u64) -> u32 {
value as u32
}
pub const fn hi32(value: u64) -> u32 {
value.wrapping_shr(32) as u32
}
pub const fn get_u64_by_shift_idx(value: u8) -> u64 {
// 1u64.wrapping_shl(31u32.wrapping_sub(value as u32))
// This is not portable, so let's use a pre-computed table...
U64_SHIFT_TABLE_CACHE[value as usize]
}
pub fn map_bit(result: u64, src: u64, check: u8, set: u8) -> u64 {
match get_u64_by_shift_idx(check) & src {
0 => result,
_ => result | get_u64_by_shift_idx(set),
}
}
pub fn map_u32_bits(src_value: u32, table: &[u8]) -> u32 {
let stream = table.iter().enumerate();
stream.fold(0u64, |result, (i, &check_idx)| {
map_bit(result, src_value as u64, check_idx, i as u8)
}) as u32
}
pub fn map_u64(src_value: u64, table: &[u8]) -> u64 {
assert_eq!(table.len() % 2, 0, "table.len() should be even");
let (table_lo32, table_hi32) = table.split_at(table.len() / 2);
let mut lo32 = 0u64;
let mut hi32 = 0u64;
for (i, (&idx_lo32, &idx_hi32)) in table_lo32.iter().zip(table_hi32).enumerate() {
lo32 = map_bit(lo32, src_value, idx_lo32, i as u8);
hi32 = map_bit(hi32, src_value, idx_hi32, i as u8);
}
make_u64(hi32 as u32, lo32 as u32)
}

118
um_crypto/qrc/src/lib.rs Normal file
View File

@ -0,0 +1,118 @@
use hex::FromHexError;
use miniz_oxide::inflate::{decompress_to_vec_zlib_with_limit as inflate, DecompressError};
use thiserror::Error;
mod des;
use crate::des::DESMode;
use des::QrcDes;
#[derive(Error, Debug)]
pub enum QrcError {
#[error("QRCDes: input is not block of 8 bytes")]
QRCDesInputSizeError,
#[error("QRC: Failed to inflate: {0}")]
QRCInflateError(DecompressError),
#[error("QRC: Failed to decode hex: {0}")]
QRCHexDecodeError(FromHexError),
#[error("QRC: Invalid file magic header")]
QRCInvalidMagicHeader,
}
// Max 4MiB for QRC
const MAX_QRC_SIZE: usize = 4 * 1024 * 1024;
const DES_KEY_1: &[u8; 8] = b"!@#)(NHL";
const DES_KEY_2: &[u8; 8] = b"123ZXC!@";
const DES_KEY_3: &[u8; 8] = b"!@#)(*$%";
pub fn decrypt_qrc(data: &[u8]) -> Result<Vec<u8>, QrcError> {
let mut temp = data.to_vec();
QrcDes::new(DES_KEY_1, DESMode::Decrypt).transform_bytes(&mut temp)?;
QrcDes::new(DES_KEY_2, DESMode::Encrypt).transform_bytes(&mut temp)?;
QrcDes::new(DES_KEY_3, DESMode::Decrypt).transform_bytes(&mut temp)?;
let result = inflate(&temp[..], MAX_QRC_SIZE).map_err(QrcError::QRCInflateError)?;
Ok(result)
}
/// Decrypt QRC data from API response
pub fn decrypt_qrc_network(data: &str) -> Result<Vec<u8>, QrcError> {
let data = hex::decode(data).map_err(QrcError::QRCHexDecodeError)?;
decrypt_qrc(&data[..])
}
const QRC_MAGIC: [u8; 11] = [
0x98, 0x25, 0xB0, 0xAC, 0xE3, 0x02, 0x83, 0x68, 0xE8, 0xFC, 0x6C,
];
/// Decrypt QRC data from cached local file
pub fn decrypt_qrc_file(data: &[u8]) -> Result<Vec<u8>, QrcError> {
let data = match data.strip_prefix(&QRC_MAGIC) {
None => Err(QrcError::QRCInvalidMagicHeader)?,
Some(data) => data,
};
let mut temp = data.to_vec();
umc_qmc::v1::decrypt(&mut temp, QRC_MAGIC.len());
decrypt_qrc(&temp[..])
}
#[cfg(test)]
mod tests {
use crate::{decrypt_qrc_file, decrypt_qrc_network};
#[test]
fn test_qrc_file() {
let data = [
0x98, 0x25, 0xB0, 0xAC, 0xE3, 0x02, 0x83, 0x68, 0xE8, 0xFC, 0x6C, 0xAB, 0x9A, 0x34,
0xE2, 0x31, 0x26, 0xAF, 0x6E, 0x2A, 0x23, 0xB3, 0x56, 0xC3, 0xBF, 0x8A, 0xA6,
];
let result = decrypt_qrc_file(&data).expect("Decryption failed.");
assert_eq!(result, b"nothing");
}
#[test]
fn test_qrc_file_2() {
let data = [
0x98, 0x25, 0xB0, 0xAC, 0xE3, 0x02, 0x83, 0x68, 0xE8, 0xFC, 0x6C, 0x07, 0xBC, 0x8C,
0x46, 0x97, 0x36, 0xDF, 0x06, 0x13, 0xE0, 0x31, 0xD1, 0xF8, 0x98, 0xEF, 0xD0, 0x1B,
0xEA, 0x6B, 0x04, 0x1D, 0xDB, 0xE0, 0x0F, 0x33, 0x2B, 0xBE, 0x95, 0x27, 0xB9, 0xF6,
0xEE, 0x0C, 0x75, 0x0C, 0x46, 0x4C, 0xA8, 0xE8, 0x37, 0x93, 0x03, 0xC0, 0xA6, 0x98,
0xD0, 0x4B, 0x6E, 0xBB, 0x2A, 0x8C, 0x3E, 0xE8, 0x7F, 0xC2, 0x0F, 0x6E, 0x2E, 0x3E,
0xAD, 0x38, 0xCF, 0x74, 0x01, 0x17, 0xDA, 0xE0, 0x62, 0x45, 0x4F, 0xF8, 0x35,
];
let result = decrypt_qrc_file(&data).expect("Decryption failed.");
assert_eq!(
String::from_utf8_lossy(&result),
"[00:00:00]此歌曲为没有填词的纯音乐,请您欣赏"
);
}
#[test]
fn test_qrc_from_network() {
// Original QRC
let input_data = include_str!("__fixture__/qrc_network_1_jp.txt");
let qrc_original_b = decrypt_qrc_network(input_data).expect("decrypt failed");
let qrc_original = String::from_utf8(qrc_original_b).expect("decode failed");
assert!(qrc_original.contains("太(1399,388)陽(1787,486)系(2273,1433)を(3706,404)"));
// QRC 罗马字
let input_data = include_str!("__fixture__/qrc_network_1_roma.txt");
let qrc_romaji_b = decrypt_qrc_network(input_data).expect("decrypt failed");
let qrc_romaji = String::from_utf8(qrc_romaji_b).expect("decode failed");
assert!(qrc_romaji.contains("ta (1399,194)i (1593,194)yo (1787,243)u (2029,243)ke"));
// QRC 翻译 (LRC)
let input_data = include_str!("__fixture__/qrc_network_1_trans.txt");
let qrc_translation_b = decrypt_qrc_network(input_data).expect("decrypt failed");
let qrc_translation = String::from_utf8(qrc_translation_b).expect("decode failed");
assert!(qrc_translation.contains("[00:01.39]摆脱太阳系"));
println!("qrc_original: {}", qrc_original);
println!("qrc_romaji: {}", qrc_romaji);
println!("qrc_translation: {}", qrc_translation);
}
}

View File

@ -8,5 +8,5 @@ aes = "0.8.4"
byteorder = "1.5.0" byteorder = "1.5.0"
cbc = "0.1.2" cbc = "0.1.2"
ctr = "0.9.2" ctr = "0.9.2"
thiserror = "1.0.63" thiserror = "1.0.64"
umc_utils = { path = "../utils" } umc_utils = { path = "../utils" }

View File

@ -4,4 +4,4 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
thiserror = "1.0.63" thiserror = "1.0.64"

View File

@ -10,5 +10,5 @@ cbc = "0.1.2"
cipher = "0.4.4" cipher = "0.4.4"
hex = "0.4.3" hex = "0.4.3"
lazy_static = "1.5.0" lazy_static = "1.5.0"
thiserror = "1.0.63" thiserror = "1.0.64"
umc_utils = { path = "../utils" } umc_utils = { path = "../utils" }

View File

@ -29,6 +29,7 @@ umc_kuwo = { path = "../um_crypto/kuwo" }
umc_mg3d = { path = "../um_crypto/mg3d" } umc_mg3d = { path = "../um_crypto/mg3d" }
umc_ncm = { path = "../um_crypto/ncm" } umc_ncm = { path = "../um_crypto/ncm" }
umc_qmc = { path = "../um_crypto/qmc" } umc_qmc = { path = "../um_crypto/qmc" }
umc_qrc = { path = "../um_crypto/qrc" }
umc_qtfm = { path = "../um_crypto/qtfm" } umc_qtfm = { path = "../um_crypto/qtfm" }
umc_xiami = { path = "../um_crypto/xiami" } umc_xiami = { path = "../um_crypto/xiami" }
umc_xmly = { path = "../um_crypto/xmly" } umc_xmly = { path = "../um_crypto/xmly" }

View File

@ -5,6 +5,7 @@ pub mod kuwo;
pub mod mg3d; pub mod mg3d;
pub mod ncm; pub mod ncm;
pub mod qmc; pub mod qmc;
mod qrc;
pub mod qtfm; pub mod qtfm;
pub mod xiami; pub mod xiami;
pub mod xmly; pub mod xmly;

View File

@ -0,0 +1,14 @@
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsError;
/// QRC Decrypt ("*.qrc" cache file)
#[wasm_bindgen(js_name=decryptQRCFile)]
pub fn js_decrypt_qrc(buffer: &mut [u8]) -> Result<Vec<u8>, JsError> {
umc_qrc::decrypt_qrc_file(buffer).map_err(JsError::from)
}
/// QRC Decrypt (network response)
#[wasm_bindgen(js_name=decryptQRCNetwork)]
pub fn js_decrypt_qrc_network(buffer: &str) -> Result<Vec<u8>, JsError> {
umc_qrc::decrypt_qrc_network(buffer).map_err(JsError::from)
}

View File

@ -1,6 +1,6 @@
{ {
"name": "@unlock-music/crypto", "name": "@unlock-music/crypto",
"version": "0.1.0", "version": "0.1.1",
"description": "Project Unlock Music: 加解密支持库", "description": "Project Unlock Music: 加解密支持库",
"scripts": { "scripts": {
"build": "node build.js", "build": "node build.js",