feat: implement qmc1 & qmc2
This commit is contained in:
parent
f78def74a6
commit
6f9ed3d9cb
@ -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
11
Cargo.lock
generated
@ -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"
|
||||||
|
@ -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
10
um_crypto/qmc/Cargo.toml
Normal 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
7
um_crypto/qmc/src/lib.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub mod v1;
|
||||||
|
pub mod v2_rc4;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum QmcCryptoError {}
|
14
um_crypto/qmc/src/v1/cipher.rs
Normal file
14
um_crypto/qmc/src/v1/cipher.rs
Normal 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]
|
||||||
|
}
|
26
um_crypto/qmc/src/v1/mod.rs
Normal file
26
um_crypto/qmc/src/v1/mod.rs
Normal 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");
|
||||||
|
}
|
86
um_crypto/qmc/src/v2_rc4/cipher.rs
Normal file
86
um_crypto/qmc/src/v2_rc4/cipher.rs
Normal 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
|
24
um_crypto/qmc/src/v2_rc4/hash.rs
Normal file
24
um_crypto/qmc/src/v2_rc4/hash.rs
Normal 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);
|
||||||
|
}
|
4
um_crypto/qmc/src/v2_rc4/mod.rs
Normal file
4
um_crypto/qmc/src/v2_rc4/mod.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
pub mod cipher;
|
||||||
|
pub mod hash;
|
||||||
|
pub mod rc4;
|
||||||
|
pub mod segment_key;
|
62
um_crypto/qmc/src/v2_rc4/rc4.rs
Normal file
62
um_crypto/qmc/src/v2_rc4/rc4.rs
Normal 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")
|
||||||
|
}
|
24
um_crypto/qmc/src/v2_rc4/segment_key.rs
Normal file
24
um_crypto/qmc/src/v2_rc4/segment_key.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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"
|
|
||||||
|
Loading…
Reference in New Issue
Block a user