|
use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _}; |
|
use rand::Rng; |
|
use sha2::{Digest, Sha256}; |
|
|
|
pub fn generate_hash() -> String { |
|
let random_bytes = rand::thread_rng().gen::<[u8; 32]>(); |
|
let mut hasher = Sha256::new(); |
|
hasher.update(random_bytes); |
|
hex::encode(hasher.finalize()) |
|
} |
|
|
|
fn obfuscate_bytes(bytes: &mut [u8]) { |
|
let mut prev: u8 = 165; |
|
for (idx, byte) in bytes.iter_mut().enumerate() { |
|
let old_value = *byte; |
|
*byte = (old_value ^ prev).wrapping_add((idx % 256) as u8); |
|
prev = *byte; |
|
} |
|
} |
|
|
|
fn deobfuscate_bytes(bytes: &mut [u8]) { |
|
let mut prev: u8 = 165; |
|
for (idx, byte) in bytes.iter_mut().enumerate() { |
|
let temp = *byte; |
|
*byte = (*byte).wrapping_sub((idx % 256) as u8) ^ prev; |
|
prev = temp; |
|
} |
|
} |
|
|
|
pub fn generate_timestamp_header() -> String { |
|
let timestamp = std::time::SystemTime::now() |
|
.duration_since(std::time::UNIX_EPOCH) |
|
.unwrap() |
|
.as_secs() |
|
/ 1_000; |
|
|
|
let mut timestamp_bytes = vec![ |
|
((timestamp >> 8) & 0xFF) as u8, |
|
(0xFF & timestamp) as u8, |
|
((timestamp >> 24) & 0xFF) as u8, |
|
((timestamp >> 16) & 0xFF) as u8, |
|
((timestamp >> 8) & 0xFF) as u8, |
|
(0xFF & timestamp) as u8, |
|
]; |
|
|
|
obfuscate_bytes(&mut timestamp_bytes); |
|
BASE64.encode(×tamp_bytes) |
|
} |
|
|
|
pub fn generate_checksum(device_id: &str, mac_addr: Option<&str>) -> String { |
|
let encoded = generate_timestamp_header(); |
|
match mac_addr { |
|
Some(mac) => format!("{}{}/{}", encoded, device_id, mac), |
|
None => format!("{}{}", encoded, device_id), |
|
} |
|
} |
|
|
|
pub fn generate_checksum_with_default() -> String { |
|
generate_checksum(&generate_hash(), Some(&generate_hash())) |
|
} |
|
|
|
pub fn generate_checksum_with_repair(checksum: &str) -> String { |
|
let bytes = checksum.as_bytes(); |
|
let len = bytes.len(); |
|
|
|
|
|
if len != 72 && len != 129 && len != 137 { |
|
return generate_checksum_with_default(); |
|
} |
|
|
|
|
|
for (i, &b) in bytes.iter().enumerate() { |
|
let valid = match (len, i) { |
|
|
|
(_, _) if !b.is_ascii_alphanumeric() && b != b'/' && b != b'+' && b != b'=' => false, |
|
|
|
|
|
(72, 8..=71) => b.is_ascii_hexdigit(), |
|
|
|
|
|
(129, 0..=63) => b.is_ascii_hexdigit(), |
|
(129, 64) => b == b'/', |
|
(129, 65..=128) => b.is_ascii_hexdigit(), |
|
|
|
|
|
(137, 8..=71) => b.is_ascii_hexdigit(), |
|
(137, 72) => b == b'/', |
|
(137, 73..=136) => b.is_ascii_hexdigit(), |
|
|
|
|
|
(72 | 137, 0..=7) => true, |
|
|
|
_ => unreachable!(), |
|
}; |
|
|
|
if !valid { |
|
return generate_checksum_with_default(); |
|
} |
|
} |
|
|
|
|
|
match len { |
|
72 => format!( |
|
"{}{}/{}", |
|
generate_timestamp_header(), |
|
unsafe { std::str::from_utf8_unchecked(&bytes[8..]) }, |
|
generate_hash() |
|
), |
|
129 => format!( |
|
"{}{}/{}", |
|
generate_timestamp_header(), |
|
unsafe { std::str::from_utf8_unchecked(&bytes[..64]) }, |
|
unsafe { std::str::from_utf8_unchecked(&bytes[65..]) } |
|
), |
|
137 => format!( |
|
"{}{}/{}", |
|
generate_timestamp_header(), |
|
unsafe { std::str::from_utf8_unchecked(&bytes[8..72]) }, |
|
unsafe { std::str::from_utf8_unchecked(&bytes[73..]) } |
|
), |
|
_ => unreachable!(), |
|
} |
|
} |
|
|
|
pub fn extract_time_ks(timestamp_base64: &str) -> Option<u64> { |
|
let mut timestamp_bytes = BASE64.decode(timestamp_base64).ok()?; |
|
|
|
if timestamp_bytes.len() != 6 { |
|
return None; |
|
} |
|
|
|
deobfuscate_bytes(&mut timestamp_bytes); |
|
|
|
if timestamp_bytes[0] != timestamp_bytes[4] || timestamp_bytes[1] != timestamp_bytes[5] { |
|
return None; |
|
} |
|
|
|
|
|
Some( |
|
((timestamp_bytes[2] as u64) << 24) |
|
| ((timestamp_bytes[3] as u64) << 16) |
|
| ((timestamp_bytes[4] as u64) << 8) |
|
| (timestamp_bytes[5] as u64), |
|
) |
|
} |
|
|
|
pub fn validate_checksum(checksum: &str) -> bool { |
|
let bytes = checksum.as_bytes(); |
|
let len = bytes.len(); |
|
|
|
|
|
if len != 72 && len != 137 { |
|
return false; |
|
} |
|
|
|
|
|
for (i, &b) in bytes.iter().enumerate() { |
|
let valid = match (len, i) { |
|
|
|
(_, _) if !b.is_ascii_alphanumeric() && b != b'/' && b != b'+' && b != b'=' => false, |
|
|
|
|
|
(72, 0..=7) => true, |
|
(72, 8..=71) => b.is_ascii_hexdigit(), |
|
|
|
(137, 0..=7) => true, |
|
(137, 8..=71) => b.is_ascii_hexdigit(), |
|
(137, 72) => b == b'/', |
|
(137, 73..=136) => b.is_ascii_hexdigit(), |
|
|
|
_ => unreachable!(), |
|
}; |
|
|
|
if !valid { |
|
return false; |
|
} |
|
} |
|
|
|
|
|
let time_valid = extract_time_ks(&checksum[..8]).is_some(); |
|
|
|
|
|
let mac_hash_valid = if len == 137 { |
|
checksum[73..].len() == 64 |
|
} else { |
|
true |
|
}; |
|
|
|
time_valid && mac_hash_valid |
|
} |
|
|
|
|
|
|
|
pub fn extract_hashes(checksum: &str) -> Option<(Vec<u8>, Vec<u8>)> { |
|
|
|
if !validate_checksum(checksum) { |
|
return None; |
|
} |
|
|
|
|
|
match checksum.len() { |
|
72 => { |
|
|
|
let device_hash = hex::decode(&checksum[8..]).ok()?; |
|
Some((device_hash, Vec::new())) |
|
} |
|
137 => { |
|
|
|
|
|
let device_hash = hex::decode(&checksum[8..72]).ok()?; |
|
let mac_hash = hex::decode(&checksum[73..]).ok()?; |
|
Some((device_hash, mac_hash)) |
|
} |
|
|
|
_ => unreachable!("Invalid length after validation: {}", checksum.len()), |
|
} |
|
} |
|
|