feat: implement qmc1 & qmc2

This commit is contained in:
鲁树人 2024-09-04 01:12:20 +01:00
parent f78def74a6
commit 6f9ed3d9cb
13 changed files with 274 additions and 4 deletions

View File

@ -5,6 +5,7 @@
<sourceFolder url="file://$MODULE_DIR$/um_wasm/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/um_wasm/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/um_wasm/tests" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/um_wasm/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/um_crypto/kuwo/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/um_crypto/kuwo/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/um_crypto/qmc/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" /> <excludeFolder url="file://$MODULE_DIR$/target" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />

11
Cargo.lock generated
View File

@ -168,6 +168,7 @@ dependencies = [
"anyhow", "anyhow",
"console_error_panic_hook", "console_error_panic_hook",
"umc_kuwo", "umc_kuwo",
"umc_qmc",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-test", "wasm-bindgen-test",
] ]
@ -182,6 +183,16 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "umc_qmc"
version = "0.1.0"
dependencies = [
"anyhow",
"base64",
"itertools",
"thiserror",
]
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.12" version = "1.0.12"

View File

@ -1,3 +1,7 @@
[workspace] [workspace]
resolver = "2" resolver = "2"
members = ["um_crypto/*", "um_wasm"] members = ["um_crypto/*", "um_wasm"]
[profile.release.package.um_wasm]
# Tell `rustc` to optimize for small code size.
opt-level = "s"

10
um_crypto/qmc/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "umc_qmc"
version = "0.1.0"
edition = "2021"
[dependencies]
base64 = "0.22.1"
itertools = "0.13.0"
anyhow = "1.0.86"
thiserror = "1.0.63"

7
um_crypto/qmc/src/lib.rs Normal file
View File

@ -0,0 +1,7 @@
use thiserror::Error;
pub mod v1;
pub mod v2_rc4;
#[derive(Error, Debug)]
pub enum QmcCryptoError {}

View File

@ -0,0 +1,14 @@
const V1_OFFSET_BOUNDARY: usize = 0x7FFF;
pub const V1_KEY_SIZE: usize = 128;
#[inline]
pub fn qmc1_transform(key: &[u8; V1_KEY_SIZE], value: u8, offset: usize) -> u8 {
let offset = match offset {
0..V1_OFFSET_BOUNDARY => offset,
V1_OFFSET_BOUNDARY => V1_OFFSET_BOUNDARY,
offset => offset % V1_OFFSET_BOUNDARY,
};
value ^ key[offset % V1_KEY_SIZE]
}

View File

@ -0,0 +1,26 @@
pub mod cipher;
use cipher::{qmc1_transform, V1_KEY_SIZE};
const V1_STATIC_KEY: [u8; V1_KEY_SIZE] = [
0xc3, 0x4a, 0xd6, 0xca, 0x90, 0x67, 0xf7, 0x52, 0xd8, 0xa1, 0x66, 0x62, 0x9f, 0x5b, 0x09, 0x00,
0xc3, 0x5e, 0x95, 0x23, 0x9f, 0x13, 0x11, 0x7e, 0xd8, 0x92, 0x3f, 0xbc, 0x90, 0xbb, 0x74, 0x0e,
0xc3, 0x47, 0x74, 0x3d, 0x90, 0xaa, 0x3f, 0x51, 0xd8, 0xf4, 0x11, 0x84, 0x9f, 0xde, 0x95, 0x1d,
0xc3, 0xc6, 0x09, 0xd5, 0x9f, 0xfa, 0x66, 0xf9, 0xd8, 0xf0, 0xf7, 0xa0, 0x90, 0xa1, 0xd6, 0xf3,
0xc3, 0xf3, 0xd6, 0xa1, 0x90, 0xa0, 0xf7, 0xf0, 0xd8, 0xf9, 0x66, 0xfa, 0x9f, 0xd5, 0x09, 0xc6,
0xc3, 0x1d, 0x95, 0xde, 0x9f, 0x84, 0x11, 0xf4, 0xd8, 0x51, 0x3f, 0xaa, 0x90, 0x3d, 0x74, 0x47,
0xc3, 0x0e, 0x74, 0xbb, 0x90, 0xbc, 0x3f, 0x92, 0xd8, 0x7e, 0x11, 0x13, 0x9f, 0x23, 0x95, 0x5e,
0xc3, 0x00, 0x09, 0x5b, 0x9f, 0x62, 0x66, 0xa1, 0xd8, 0x52, 0xf7, 0x67, 0x90, 0xca, 0xd6, 0x4a,
];
pub fn decrypt(data: &mut [u8], offset: usize) {
for (i, datum) in data.iter_mut().enumerate() {
*datum = qmc1_transform(&V1_STATIC_KEY, *datum, offset + i);
}
}
#[test]
fn test_decryption() {
let mut data = *b"\xab\x2f\xba\xa6\xff\x47\x80\x3d\xaa\xcd\x02";
decrypt(&mut data, 0);
assert_eq!(data, *b"hello world");
}

View File

@ -0,0 +1,86 @@
use crate::v2_rc4::hash::hash;
use crate::v2_rc4::rc4::RC4;
use crate::v2_rc4::segment_key::get_segment_key;
use std::cmp::min;
const FIRST_SEGMENT_SIZE: usize = 0x0080;
const OTHER_SEGMENT_SIZE: usize = 0x1400;
const RC4_STREAM_CACHE_SIZE: usize = OTHER_SEGMENT_SIZE + 512;
#[derive(Debug, Clone)]
pub struct QMC2RC4 {
hash: f64,
key: Box<[u8]>,
key_stream: [u8; RC4_STREAM_CACHE_SIZE],
}
impl QMC2RC4 {
pub fn new(key: &[u8]) -> Self {
let mut rc4 = RC4::new(key);
let mut key_stream = [0u8; RC4_STREAM_CACHE_SIZE];
rc4.derive(&mut key_stream);
Self {
hash: hash(key),
key: key.into(),
key_stream,
}
}
fn transform_first_segment(&mut self, offset: usize, dst: &mut [u8]) {
let n = self.key.len();
for (value, offset) in dst.iter_mut().zip(offset..) {
let idx = get_segment_key(offset as u64, self.key[offset % n], self.hash);
*value ^= self.key[idx % n];
}
}
fn transform_other_segment(&mut self, offset: usize, data: &mut [u8]) {
let n = self.key.len();
let id = offset / OTHER_SEGMENT_SIZE;
let block_offset = offset % OTHER_SEGMENT_SIZE;
let skip = get_segment_key(id as u64, self.key[block_offset % n], self.hash);
debug_assert!(data.len() <= OTHER_SEGMENT_SIZE - block_offset);
let key_stream = self.key_stream.iter().skip(skip + block_offset);
for (datum, &key) in data.iter_mut().zip(key_stream) {
*datum ^= key;
}
}
pub fn transform(&mut self, start_offset: usize, data: &mut [u8]) {
let mut offset = start_offset;
let mut buffer = data;
if offset < FIRST_SEGMENT_SIZE {
let n = min(FIRST_SEGMENT_SIZE - offset, buffer.len());
let (block, rest) = buffer.split_at_mut(n);
buffer = rest;
self.transform_first_segment(offset, block);
offset += n;
}
match offset % OTHER_SEGMENT_SIZE {
0 => {} // we are already in the boundary, nothing to do.
excess => {
let n = min(OTHER_SEGMENT_SIZE - excess, buffer.len());
let (block, rest) = buffer.split_at_mut(n);
buffer = rest;
self.transform_other_segment(offset, block);
offset += n;
}
};
while !buffer.is_empty() {
let n = min(OTHER_SEGMENT_SIZE, buffer.len());
let (block, rest) = buffer.split_at_mut(n);
buffer = rest;
self.transform_other_segment(offset, block);
offset += n;
}
}
}
// TODO: Add tests here

View File

@ -0,0 +1,24 @@
pub fn hash<T: AsRef<[u8]>>(key: T) -> f64 {
let mut hash = 1u32;
for &v in key.as_ref().iter() {
if v == 0 {
continue;
}
let next_hash = hash.wrapping_mul(v as u32);
if next_hash == 0 || next_hash <= hash {
break;
}
hash = next_hash;
}
hash.into()
}
#[test]
fn test_hash() {
let expected = 4045008896.0;
let actual = hash(b"hello world");
assert_eq!(expected, actual);
}

View File

@ -0,0 +1,4 @@
pub mod cipher;
pub mod hash;
pub mod rc4;
pub mod segment_key;

View File

@ -0,0 +1,62 @@
use std::ops::Rem;
#[derive(Debug, Clone)]
pub struct RC4 {
state: Box<[u8]>,
i: usize,
j: usize,
}
fn init_state(key: &[u8]) -> Box<[u8]> {
let n = key.len();
let mut state: Box<[u8]> = (0..n).map(|i| i as u8).collect();
let mut j = 0usize;
for i in 0..state.len() {
j = (j + usize::from(state[i]) + usize::from(key[i % n])) % n;
state.swap(i, j);
}
state
}
impl RC4 {
pub fn new<K: AsRef<[u8]>>(key: K) -> Self {
Self {
state: init_state(key.as_ref()),
i: 0,
j: 0,
}
}
pub fn generate(&mut self) -> u8 {
let n = self.state.len();
self.i = self.i.wrapping_add(1).rem(n);
self.j = self.j.wrapping_add(self.state[self.i].into()).rem(n);
self.state.swap(self.i, self.j);
let i = usize::from(self.state[self.i]);
let j = usize::from(self.state[self.j]);
let index = (i + j) % n;
self.state[index]
}
pub fn derive(&mut self, buffer: &mut [u8]) {
for item in buffer.iter_mut() {
*item ^= self.generate();
}
}
}
#[test]
fn test_rc4() {
let mut rc4 = RC4::new(b"this is a test key");
let rc4_copy = rc4.clone();
let mut data = *b"hello world";
rc4.derive(&mut data[..]);
assert_ne!(rc4.state, rc4_copy.state);
assert_eq!(&data, b"\x68\x75\x6b\x64\x64\x24\x7f\x60\x7c\x7d\x60")
}

View File

@ -0,0 +1,24 @@
pub fn get_segment_key(id: u64, seed: u8, hash: f64) -> usize {
match seed {
0 => 0,
seed => {
let result = hash / ((id + 1).wrapping_mul(seed.into()) as f64) * 100.0;
result as usize
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_segment_key_nil_seed() {
assert_eq!(get_segment_key(1, 0, 12345.0), 0);
}
#[test]
fn test_segment_key_123() {
assert_eq!(get_segment_key(1, 123, 12345.0), 5018);
}
}

View File

@ -23,10 +23,7 @@ anyhow = "1.0.86"
# code size when deploying. # code size when deploying.
console_error_panic_hook = { version = "0.1.7", optional = true } console_error_panic_hook = { version = "0.1.7", optional = true }
umc_kuwo = { path = "../um_crypto/kuwo" } umc_kuwo = { path = "../um_crypto/kuwo" }
umc_qmc = { path = "../um_crypto/qmc" }
[dev-dependencies] [dev-dependencies]
wasm-bindgen-test = "0.3.34" wasm-bindgen-test = "0.3.34"
[profile.release]
# Tell `rustc` to optimize for small code size.
opt-level = "s"