use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyInit, KeyIvInit};
use md5::{Digest, Md5};
use rand::Rng;
use rsa::traits::PublicKeyParts;
use rsa::{BigUint, RsaPublicKey};
use std::collections::HashMap;
type Aes128CbcEnc = cbc::Encryptor<aes::Aes128>;
type Aes128EcbEnc = ecb::Encryptor<aes::Aes128>;
type Aes128EcbDec = ecb::Decryptor<aes::Aes128>;
const IV: &[u8] = b"0102030405060708";
const PRESET_KEY: &[u8] = b"0CoJUm6Qyw8W8jud";
const LINUXAPI_KEY: &[u8] = b"rFgB&h#%2?^eDg:Q";
const EAPI_KEY: &[u8] = b"e82ckenh8dichen8";
const BASE62: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const PUBLIC_KEY_DER_B64: &str = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgtQn2JZ34ZC28NWYpAUd98iZ37BUrX/aKzmFbt7clFSs6sXqHauqKWqdtLkF2KexO40H1YTX8z2lSgBBOAxLsvaklV8k4cBFK9snQXE9/DDaFt6Rr7iVZMldczhC0JNgTz+SHXT6CBHuX3e9SdB1Ua44oncaTWz7OBGLbCiK45wIDAQAB";
fn aes_cbc_encrypt_base64(plaintext: &[u8], key: &[u8], iv: &[u8]) -> String {
let cipher = Aes128CbcEnc::new(key.into(), iv.into());
let ciphertext = cipher.encrypt_padded_vec_mut::<Pkcs7>(plaintext);
base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &ciphertext)
}
fn aes_ecb_encrypt_hex(plaintext: &[u8], key: &[u8]) -> String {
let cipher = Aes128EcbEnc::new(key.into());
let ciphertext = cipher.encrypt_padded_vec_mut::<Pkcs7>(plaintext);
hex::encode_upper(&ciphertext)
}
fn aes_ecb_decrypt_hex(ciphertext_hex: &str, key: &[u8]) -> Result<Vec<u8>, String> {
let ciphertext = hex::decode(ciphertext_hex).map_err(|e| e.to_string())?;
let cipher = Aes128EcbDec::new(key.into());
cipher
.decrypt_padded_vec_mut::<Pkcs7>(&ciphertext)
.map_err(|e| e.to_string())
}
fn rsa_encrypt_no_padding(plaintext: &[u8]) -> String {
use base64::Engine;
use rsa::pkcs8::DecodePublicKey;
let der_bytes = base64::engine::general_purpose::STANDARD
.decode(PUBLIC_KEY_DER_B64)
.expect("Failed to decode RSA public key base64");
let public_key =
RsaPublicKey::from_public_key_der(&der_bytes).expect("Failed to parse RSA public key DER");
let n = public_key.n().clone();
let e = public_key.e().clone();
let m = BigUint::from_bytes_be(plaintext);
let c = m.modpow(&e, &n);
let n_bytes = n.bits() / 8;
let c_bytes = c.to_bytes_be();
let mut padded = vec![0u8; n_bytes - c_bytes.len()];
padded.extend_from_slice(&c_bytes);
hex::encode(&padded)
}
pub fn weapi(object: &serde_json::Value) -> HashMap<String, String> {
let text = serde_json::to_string(object).unwrap();
let mut rng = rand::thread_rng();
let secret_key: String = (0..16)
.map(|_| BASE62[rng.gen_range(0..62)] as char)
.collect();
let first_encrypt = aes_cbc_encrypt_base64(text.as_bytes(), PRESET_KEY, IV);
let params = aes_cbc_encrypt_base64(first_encrypt.as_bytes(), secret_key.as_bytes(), IV);
let reversed_key: String = secret_key.chars().rev().collect();
let enc_sec_key = rsa_encrypt_no_padding(reversed_key.as_bytes());
let mut result = HashMap::new();
result.insert("params".to_string(), params);
result.insert("encSecKey".to_string(), enc_sec_key);
result
}
pub fn linuxapi(object: &serde_json::Value) -> HashMap<String, String> {
let text = serde_json::to_string(object).unwrap();
let mut result = HashMap::new();
result.insert(
"eparams".to_string(),
aes_ecb_encrypt_hex(text.as_bytes(), LINUXAPI_KEY),
);
result
}
pub fn eapi(url: &str, object: &serde_json::Value) -> HashMap<String, String> {
let text = serde_json::to_string(object).unwrap();
let message = format!("nobody{}use{}md5forencrypt", url, text);
let digest = format!("{:x}", Md5::digest(message.as_bytes()));
let data = format!("{}-36cd479b6b5-{}-36cd479b6b5-{}", url, text, digest);
let mut result = HashMap::new();
result.insert(
"params".to_string(),
aes_ecb_encrypt_hex(data.as_bytes(), EAPI_KEY),
);
result
}
pub fn eapi_res_decrypt(encrypted_hex: &str) -> Option<serde_json::Value> {
let decrypted = aes_ecb_decrypt_hex(encrypted_hex, EAPI_KEY).ok()?;
let text = String::from_utf8(decrypted).ok()?;
serde_json::from_str(&text).ok()
}
pub fn eapi_req_decrypt(encrypted_hex: &str) -> Option<(String, serde_json::Value)> {
let decrypted = aes_ecb_decrypt_hex(encrypted_hex, EAPI_KEY).ok()?;
let text = String::from_utf8(decrypted).ok()?;
let parts: Vec<&str> = text.splitn(3, "-36cd479b6b5-").collect();
if parts.len() >= 2 {
let url = parts[0].to_string();
let data: serde_json::Value = serde_json::from_str(parts[1]).ok()?;
Some((url, data))
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_aes_ecb_roundtrip() {
let plaintext = b"hello world test";
let encrypted = aes_ecb_encrypt_hex(plaintext, EAPI_KEY);
let decrypted = aes_ecb_decrypt_hex(&encrypted, EAPI_KEY).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_weapi_produces_params_and_encseckey() {
let obj = serde_json::json!({"id": 123});
let result = weapi(&obj);
assert!(result.contains_key("params"));
assert!(result.contains_key("encSecKey"));
assert!(!result["params"].is_empty());
assert_eq!(result["encSecKey"].len(), 256);
}
#[test]
fn test_linuxapi_produces_eparams() {
let obj = serde_json::json!({"method": "POST", "url": "https://music.163.com/api/test"});
let result = linuxapi(&obj);
assert!(result.contains_key("eparams"));
assert!(!result["eparams"].is_empty());
}
#[test]
fn test_eapi_encrypt_decrypt() {
let url = "/api/song/detail";
let obj = serde_json::json!({"id": 123});
let encrypted = eapi(url, &obj);
let params = &encrypted["params"];
let (dec_url, dec_data) = eapi_req_decrypt(params).unwrap();
assert_eq!(dec_url, url);
assert_eq!(dec_data, obj);
}
}