use aes::Aes128;
use block_modes::{block_padding::NoPadding, BlockMode, Cbc};
use rand::Rng;
use ring::pbkdf2;
const NONZERO100000: std::option::Option<std::num::NonZeroU32> = std::num::NonZeroU32::new(100_000);
static PBKDF2_ALG: pbkdf2::Algorithm = pbkdf2::PBKDF2_HMAC_SHA512;
pub struct Mega {}
#[allow(dead_code)]
impl Mega {
fn split_salt(start: &str, end: &str, line: &str) -> Option<String> {
let split1 = line.splitn(2, start).collect::<Vec<&str>>();
if split1.len() == 2 {
let split2 = split1[1].splitn(2, end).collect::<Vec<&str>>();
if split2.len() == 2 {
Some(split2[0].to_string())
} else {
None
}
} else {
None
}
}
fn aes_cbc_encrypt(buffer: &mut [u8], key: [u8; 16]) {
let cipher = Cbc::<Aes128, NoPadding>::new_from_slices(&key, &[0; 16]).unwrap();
cipher.encrypt(&mut *buffer, 16).unwrap();
}
fn prepare_key(pwd_str: &str) -> [u8; 16] {
let data: &[u8] = pwd_str.as_bytes();
let mut pkey: [u8; 16] = [
0x93, 0xC4, 0x67, 0xE3, 0x7D, 0xB0, 0xC7, 0xA4, 0xD1, 0xBE, 0x3F, 0x81, 0x01, 0x52,
0xCB, 0x56,
];
for _it in 0..65536 {
for idx in data.chunks(16) {
let mut temp: [u8; 16] = [0; 16];
temp[..idx.len()].copy_from_slice(idx);
Self::aes_cbc_encrypt(&mut pkey, temp);
}
}
pkey
}
fn generate_user_handle(email: &str, key: &[u8; 16]) -> String {
let email_bytes = email.as_bytes();
let mut hash: [u8; 16] = [0; 16];
for i in 0..email_bytes.len() {
hash[i % 16] ^= email_bytes[i];
}
for _ in 0..16384 {
Self::aes_cbc_encrypt(&mut hash, *key)
}
let mut hash_parts = [0u8; 8];
hash_parts[0..4].copy_from_slice(&hash[0..4]);
hash_parts[4..8].copy_from_slice(&hash[8..12]);
let result: String = base64::encode_config(&hash_parts, base64::URL_SAFE_NO_PAD);
result
}
fn hmac_hash(password: &str, salt: &str) -> Result<[u8; 32], std::io::Error> {
if let Ok(salt_work) = base64::decode(salt) {
let mut result: [u8; 32] = [0u8; 32];
pbkdf2::derive(
PBKDF2_ALG,
NONZERO100000.unwrap(),
&salt_work,
password.as_bytes(),
&mut result,
);
Ok(result)
} else {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Error in salt",
))
}
}
fn post_mega(url: &str, body: String) -> Result<String, Box<dyn std::error::Error>> {
let client = reqwest::blocking::Client::new();
let res = client.post(url).body(body).send()?;
let body_return = res.text()?;
Ok(body_return)
}
pub fn login_email_hash(email: &str, hash: &str, url: &str) -> Result<String, std::io::Error> {
let post_data = format!(
"[{{\"a\": \"us\", \"user\": \"{}\", \"uh\": \"{}\"}}]",
email, hash
);
if let Ok(req2) = Self::post_mega(&url, post_data) {
if req2.contains("[-13]") {
Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Wrong email or password",
))
} else if req2.contains("\"csid\":\"") {
Ok(req2)
} else {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("Else: {:?}", req2),
))
}
} else {
Err(std::io::Error::new(
std::io::ErrorKind::NotConnected,
"Connection error",
))
}
}
pub fn login(email: &str, password: &str) -> Result<String, std::io::Error> {
let post_data = format!("[{{\"a\":\"us0\",\"user\":\"{}\"}}]", email);
let url = format!(
"https://g.api.mega.co.nz/cs?id={}",
rand::thread_rng().gen_range(0..10000000)
);
if let Ok(req1) = Self::post_mega(&url, post_data) {
if req1.contains("\"v\":1") {
let key = Self::prepare_key(password);
let hash = Self::generate_user_handle(email, &key);
Self::login_email_hash(email, &hash, &url)
} else if req1.contains("\"v\":2") {
if let Some(salt) = Self::split_salt("\"s\":\"", "\"", &req1) {
if let Ok(hash_parsed) = Self::hmac_hash(password, &salt) {
let hash =
base64::encode_config(&hash_parsed[16..32], base64::URL_SAFE_NO_PAD);
Self::login_email_hash(email, &hash, &url)
} else {
Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Generating hash",
))
}
} else {
Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Parsing salt",
))
}
} else {
Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"No version in post data",
))
}
} else {
Err(std::io::Error::new(
std::io::ErrorKind::NotConnected,
"Connection error",
))
}
}
}