init: it builds

This commit is contained in:
鲁树人
2024-09-02 21:01:19 +01:00
commit ff11a8186e
23 changed files with 1073 additions and 0 deletions

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

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

View File

@@ -0,0 +1,108 @@
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 KEY_SHIFT_MASKS: [u64; 3] = [0, 0x100001, 0x300003];
pub const KEY_SHIFT_LEFT_MASKS: [u64; 16] = {
let mut result = [0u64; 16];
let mut i = 0usize;
while i < 16 {
result[i] = KEY_SHIFT_MASKS[KEY_RND_SHIFTS[i] as usize];
i += 1;
}
result
};
pub const SBOXES: [[u8; 64]; 8] = [
[
13, 7, 10, 0, 6, 9, 5, 15, 8, 4, 3, 10, 11, 14, 12, 5, 2, 11, 9, 6, 15, 12, 0, 3, 4, 1, 14,
13, 1, 2, 7, 8, 1, 2, 12, 15, 10, 4, 0, 3, 13, 14, 6, 9, 7, 8, 9, 6, 15, 1, 5, 12, 3, 10,
14, 5, 8, 7, 11, 0, 4, 13, 2, 11,
],
[
4, 1, 3, 10, 15, 12, 5, 0, 2, 11, 9, 6, 8, 7, 6, 9, 11, 4, 12, 15, 0, 3, 10, 5, 14, 13, 7,
8, 13, 14, 1, 2, 13, 6, 14, 9, 4, 1, 2, 14, 11, 13, 5, 0, 1, 10, 8, 3, 0, 11, 3, 5, 9, 4,
15, 2, 7, 8, 12, 15, 10, 7, 6, 12,
],
[
12, 9, 0, 7, 9, 2, 14, 1, 10, 15, 3, 4, 6, 12, 5, 11, 1, 14, 13, 0, 2, 8, 7, 13, 15, 5, 4,
10, 8, 3, 11, 6, 10, 4, 6, 11, 7, 9, 0, 6, 4, 2, 13, 1, 9, 15, 3, 8, 15, 3, 1, 14, 12, 5,
11, 0, 2, 12, 14, 7, 5, 10, 8, 13,
],
[
2, 4, 8, 15, 7, 10, 13, 6, 4, 1, 3, 12, 11, 7, 14, 0, 12, 2, 5, 9, 10, 13, 0, 3, 1, 11, 15,
5, 6, 8, 9, 14, 14, 11, 5, 6, 4, 1, 3, 10, 2, 12, 15, 0, 13, 2, 8, 5, 11, 8, 0, 15, 7, 14,
9, 4, 12, 7, 10, 9, 1, 13, 6, 3,
],
[
7, 10, 1, 15, 0, 12, 11, 5, 14, 9, 8, 3, 9, 7, 4, 8, 13, 6, 2, 1, 6, 11, 12, 2, 3, 0, 5,
14, 10, 13, 15, 4, 13, 3, 4, 9, 6, 10, 1, 12, 11, 0, 2, 5, 0, 13, 14, 2, 8, 15, 7, 4, 15,
1, 10, 7, 5, 6, 12, 11, 3, 8, 9, 14,
],
[
10, 13, 1, 11, 6, 8, 11, 5, 9, 4, 12, 2, 15, 3, 2, 14, 0, 6, 13, 1, 3, 15, 4, 10, 14, 9, 7,
12, 5, 0, 8, 7, 13, 1, 2, 4, 3, 6, 12, 11, 0, 13, 5, 14, 6, 8, 15, 2, 7, 10, 8, 15, 4, 9,
11, 5, 9, 0, 14, 3, 10, 7, 1, 12,
],
[
15, 0, 9, 5, 6, 10, 12, 9, 8, 7, 2, 12, 3, 13, 5, 2, 1, 14, 7, 8, 11, 4, 0, 3, 14, 11, 13,
6, 4, 1, 10, 15, 3, 13, 12, 11, 15, 3, 6, 0, 4, 10, 1, 7, 8, 4, 11, 14, 13, 8, 0, 6, 2, 15,
9, 5, 7, 1, 10, 12, 14, 2, 5, 9,
],
[
14, 4, 3, 15, 2, 13, 5, 3, 13, 14, 6, 9, 11, 2, 0, 5, 4, 1, 10, 12, 15, 6, 9, 10, 1, 8, 12,
7, 8, 11, 7, 0, 0, 15, 10, 5, 14, 4, 9, 10, 7, 8, 12, 3, 13, 1, 3, 6, 15, 12, 6, 11, 2, 9,
5, 0, 4, 2, 11, 14, 1, 7, 8, 13,
],
];
// custom
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,
];
// custom
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,
];
// custom
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,
];
// custom
pub const KEY_PERMUTATION_TABLE: [u8; 56] = [
//key_param_c + key_param_d
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, 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,
];
// custom
pub const KEY_COMPRESSION: [u8; 64] = [
13, 16, 10, 23, 0, 4, 255, 255, 2, 27, 14, 5, 20, 9, 255, 255, 22, 18, 11, 3, 25, 7, 255, 255,
15, 6, 26, 19, 12, 1, 255, 255, 40, 51, 30, 36, 46, 54, 255, 255, 29, 39, 50, 44, 32, 47, 255,
255, 43, 48, 38, 55, 33, 52, 255, 255, 45, 41, 49, 35, 28, 31, 255, 255,
];
// custom
pub const KEY_EXPANSION: [u8; 64] = [
31, 0, 1, 2, 3, 4, 255, 255, 3, 4, 5, 6, 7, 8, 255, 255, 7, 8, 9, 10, 11, 12, 255, 255, 11, 12,
13, 14, 15, 16, 255, 255, 15, 16, 17, 18, 19, 20, 255, 255, 19, 20, 21, 22, 23, 24, 255, 255,
23, 24, 25, 26, 27, 28, 255, 255, 27, 28, 29, 30, 31, 30, 255, 255,
];
pub const U64_SHIFT_TABLE_CACHE: [u64; 64] = {
let mut data = [0u64; 64];
let mut i = 0;
while i < 32 {
data[i] = 1u64 << i;
data[i + 32] = 1u64 << (i + 32);
i += 1;
}
data
};

View File

@@ -0,0 +1,109 @@
use super::{constants, helper};
use crate::KuwoCryptoError;
use anyhow::Result;
use itertools::Either;
/// Encrypt or Decrypt?
pub enum Mode {
Encrypt,
Decrypt,
}
#[derive(Debug, Clone, Copy)]
pub struct KuwoDes {
subkeys: [u64; 16],
}
fn des_subkey_expansion(key: &[u8; 8], mode: Mode) -> [u64; 16] {
let key = u64::from_le_bytes(*key);
let mut param = helper::map_u64(key, &constants::KEY_PERMUTATION_TABLE);
let mut subkeys = [0u64; 16];
let subkey_iter = match mode {
Mode::Decrypt => Either::Left(subkeys.iter_mut().rev()),
Mode::Encrypt => Either::Right(subkeys.iter_mut()),
};
for ((subkey, shl), shl_mask) in subkey_iter
.zip(constants::KEY_RND_SHIFTS)
.zip(constants::KEY_SHIFT_LEFT_MASKS)
{
param = ((param & shl_mask) << (28 - shl)) | ((param & !shl_mask) >> (shl));
*subkey = helper::map_u64(param, &constants::KEY_COMPRESSION);
}
subkeys
}
fn des_subkey_round(state: u64, subkey: u64) -> u64 {
let old_left = helper::u64_get_hi32(state);
let old_right = helper::u64_get_lo32(state);
// Key expansion
let state = helper::map_u64(old_left as u64, &constants::KEY_EXPANSION);
let state = state ^ subkey;
// SBox transformation
let right = constants::SBOXES
.iter()
.zip(state.to_be_bytes())
.fold(0u32, |next, (sbox, b)| {
(next << 4) | (sbox[b as usize] as u32)
});
let right = helper::map_u32(right, &constants::PBOX);
let right = right ^ old_right;
helper::make_u64(right, old_left)
}
impl KuwoDes {
pub fn new(key: &[u8; 8], mode: Mode) -> Self {
Self {
subkeys: des_subkey_expansion(key, mode),
}
}
pub fn transform_block(&self, data: u64) -> u64 {
let mut state = helper::map_u64(data, &constants::IP);
state = self
.subkeys
.iter()
.fold(state, |state, &subkey| des_subkey_round(state, subkey));
// Swap data hi32/lo32
state = helper::swap_u64_side(state);
// Final permutation
state = helper::map_u64(state, &constants::IP_INV);
state
}
pub fn transform(&self, data: &mut [u8]) -> Result<()> {
if data.len() % 8 != 0 {
Err(KuwoCryptoError::InvalidDesDataSize(data.len()))?
}
for block in data.chunks_exact_mut(8) {
let value = u64::from_le_bytes(block.try_into()?);
let value = self.transform_block(value);
block.copy_from_slice(&value.to_le_bytes());
}
Ok(())
}
}
#[test]
fn test_des_decrypt() {
let mut input = [
0x36, 0x3C, 0x3E, 0x0D, 0x30, 0x31, 0xA4, 0x6C, 0xA0, 0xF0, 0x3A, 0xEC, 0x7F, 0x26, 0xF6,
0xF4,
];
let des = KuwoDes::new(b"ylzsxkwm", Mode::Decrypt);
des.transform(&mut input).unwrap();
assert_eq!(&input, b"12345678ABCDEFGH");
}

View File

@@ -0,0 +1,49 @@
pub(super) const fn make_u64(hi32: u32, lo32: u32) -> u64 {
((hi32 as u64) << 32) | (lo32 as u64)
}
pub(super) const fn swap_u64_side(value: u64) -> u64 {
(value.wrapping_shr(32)) | (value.wrapping_shl(32))
}
pub(super) const fn u64_get_lo32(value: u64) -> u32 {
value as u32
}
pub(super) const fn u64_get_hi32(value: u64) -> u32 {
value.wrapping_shr(32) as u32
}
pub(super) fn get_u64_by_shift_idx(value: u8) -> u64 {
if value == 255 {
return 0;
}
if cfg!(target_pointer_width = "64") {
1u64.wrapping_shl(value as u32)
} else {
super::constants::U64_SHIFT_TABLE_CACHE
.get(value as usize)
.copied()
.unwrap_or_default()
}
}
#[test]
fn test_get_u64_by_shift_idx() {
assert_eq!(get_u64_by_shift_idx(0), 1);
assert_eq!(get_u64_by_shift_idx(63), 0x8000000000000000);
}
pub(super) fn map_u64(src_value: u64, table: &[u8]) -> u64 {
table.iter().enumerate().fold(0u64, |acc, (i, &idx)| {
match get_u64_by_shift_idx(idx) & src_value {
0 => acc,
_ => acc | get_u64_by_shift_idx(i as u8),
}
})
}
pub(super) fn map_u32(src_value: u32, table: &[u8]) -> u32 {
map_u64(src_value as u64, table) as u32
}

View File

@@ -0,0 +1,52 @@
use anyhow::Result;
use base64::alphabet;
use base64::engine::{DecodePaddingMode, GeneralPurpose as Base64Engine, GeneralPurposeConfig};
use base64::prelude::*;
mod constants;
mod des_impl;
mod helper;
pub use des_impl::{KuwoDes, Mode};
/// Don't add padding when encoding, and require no padding when decoding.
const B64: Base64Engine = Base64Engine::new(
&alphabet::STANDARD,
GeneralPurposeConfig::new().with_decode_padding_mode(DecodePaddingMode::Indifferent),
);
/// Decrypt string content
pub fn decrypt_ksing(data: &str, key: &[u8; 8]) -> Result<String> {
let mut decoded = B64.decode(data)?;
let des = KuwoDes::new(key, Mode::Decrypt);
des.transform(&mut decoded[..])?;
let result = String::from_utf8_lossy(&decoded[..])
.trim_end_matches('\x00')
.to_string();
Ok(result)
}
pub fn encrypt_ksing<T: AsRef<[u8]>>(data: T, key: &[u8; 8]) -> Result<String> {
let mut data = Vec::from(data.as_ref());
let padded_len = ((data.len() + 7) / 8) * 8;
data.resize(padded_len, 0u8);
let des = KuwoDes::new(key, Mode::Encrypt);
des.transform(&mut data[..])?;
Ok(B64.encode(data))
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_decode() {
let expected = "hello world";
let decoded =
decrypt_ksing("tx5ct5ilzeLs7pN1C4RI6w==", b"12345678").expect("decrypt failed");
assert_eq!(decoded, expected);
}
}

11
um_crypto/kuwo/src/lib.rs Normal file
View File

@@ -0,0 +1,11 @@
pub mod des;
use thiserror::Error;
/// Commonly used secret key for Kuwo services.
pub const SECRET_KEY: [u8; 8] = *b"ylzsxkwm";
#[derive(Error, Debug)]
pub enum KuwoCryptoError {
#[error("Invalid DES data size (expected: {0} mod 8 == 0)")]
InvalidDesDataSize(usize),
}