rust-boss/src/crypto/wiiu.rs
BloxerHD018 2d5d92cc79
All checks were successful
Build and Test / rust-boss (push) Successful in 6m44s
Update Rust crate hmac to 0.13
2026-05-26 15:18:45 +01:00

105 lines
2.9 KiB
Rust

use aes::Aes128;
use ctr::cipher::{KeyIvInit, StreamCipher};
use hmac::{Hmac, KeyInit, Mac};
use sha2::Sha256;
use md5::{Md5, Digest};
use rand::Rng;
use anyhow::{Result, bail};
type HmacSha256 = Hmac<Sha256>;
type Aes128Ctr = ctr::Ctr128BE<Aes128>;
const BOSS_WUP_VER: u32 = 0x20001;
const BOSS_AES_KEY_HASH: [u8; 16] = hex_literal::hex!("5202ce5099232c3d365e28379790a919");
const BOSS_HMAC_KEY_HASH: [u8; 16] = hex_literal::hex!("b4482fef177b0100090ce0dbeb8ce977");
pub struct WupBossInfo {
pub hash_type: u16,
pub iv: Vec<u8>,
pub hmac: Vec<u8>,
pub content: Vec<u8>,
}
fn verify_keys(aes_key: &[u8], hmac_key: &[u8]) -> Result<()> {
let mut hasher = Md5::new();
hasher.update(aes_key);
let aes_md5 = hasher.finalize_reset();
if aes_md5[..] != BOSS_AES_KEY_HASH {
bail!("Invalid BOSS AES key");
}
hasher.update(hmac_key);
let hmac_md5 = hasher.finalize();
if hmac_md5[..] != BOSS_HMAC_KEY_HASH {
bail!("Invalid BOSS HMAC key");
}
Ok(())
}
pub fn decrypt_wiiu(data: &[u8], aes_key: &[u8], hmac_key: &[u8]) -> Result<WupBossInfo> {
verify_keys(aes_key, hmac_key)?;
let hash_type = u16::from_be_bytes([data[0xA], data[0xB]]);
if hash_type != 2 {
bail!("Unknown hash type");
}
let mut iv = Vec::with_capacity(16);
iv.extend_from_slice(&data[0xC..0x18]);
iv.extend_from_slice(&[0, 0, 0, 1]);
let mut cipher = Aes128Ctr::new_from_slices(aes_key, &iv)?;
let mut decrypted = data[0x20..].to_vec();
cipher.apply_keystream(&mut decrypted);
let hmac = decrypted[..0x20].to_vec();
let content = decrypted[0x20..].to_vec();
let mut mac = HmacSha256::new_from_slice(hmac_key)?;
mac.update(&content);
mac.verify_slice(&hmac)
.map_err(|_| anyhow::anyhow!("Content HMAC check failed"))?;
Ok(WupBossInfo {
hash_type,
iv,
hmac,
content,
})
}
pub fn encrypt_wiiu(content: &[u8], aes_key: &[u8], hmac_key: &[u8]) -> Result<Vec<u8>> {
verify_keys(aes_key, hmac_key)?;
let mut mac = HmacSha256::new_from_slice(hmac_key)?;
mac.update(content);
let hmac = mac.finalize().into_bytes();
let mut plaintext = Vec::new();
plaintext.extend_from_slice(&hmac);
plaintext.extend_from_slice(content);
let mut iv12 = [0u8; 12];
rand::rng().fill_bytes(&mut iv12);
let mut iv = Vec::with_capacity(16);
iv.extend_from_slice(&iv12);
iv.extend_from_slice(&[0, 0, 0, 1]);
let mut cipher = Aes128Ctr::new_from_slices(aes_key, &iv)?;
cipher.apply_keystream(&mut plaintext);
let mut header = vec![0u8; 0x20];
header[0..4].copy_from_slice(b"boss");
header[0x4..0x8].copy_from_slice(&BOSS_WUP_VER.to_be_bytes());
header[0x8..0xA].copy_from_slice(&1u16.to_be_bytes());
header[0xA..0xC].copy_from_slice(&2u16.to_be_bytes());
header[0xC..0x18].copy_from_slice(&iv12);
header.extend_from_slice(&plaintext);
Ok(header)
}