use base64::Engine;
use sha2::{Digest, Sha512};
use crate::error::{Error, Result};
pub const MODIFY_SPIN_COUNT: u32 = 100_000;
const AGILE_SPIN_COUNT: u32 = 100_000;
pub const SALT_LEN: usize = 16;
const AES_KEY_LEN: usize = 32;
const AES_BLOCK_SIZE: usize = 16;
const BLOCK_KEY_ENCRYPTED_KEY_VALUE: &[u8] = &[
0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6, 0x2e, 0x0b, 0x80, 0x14, 0x73, 0x0f, 0xbf, 0x7d,
0x0e, 0xda, 0x25, 0x2e, 0x6b, 0x0f, 0x9b, 0x55, 0xb3, 0x10, 0x0f, 0x65, 0x0f, 0xa3, 0x5c, 0x6b,
];
const BLOCK_KEY_VERIFIER_HASH_INPUT: &[u8] = &[0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, 0x79];
const BLOCK_KEY_VERIFIER_HASH_VALUE: &[u8] = &[0xd7, 0xaa, 0x0f, 0x6d, 0x30, 0x61, 0x27, 0x9c];
#[derive(Clone, Debug)]
pub struct ModifyProtection {
pub salt_b64: String,
pub hash_b64: String,
pub spin_count: u32,
pub crypt_provider_type: &'static str,
pub algorithm_sid: u32,
}
impl ModifyProtection {
pub fn from_password(password: &str, salt: &[u8], spin_count: u32) -> Self {
let hash = compute_modify_hash(password, salt, spin_count);
let salt_b64 = base64::engine::general_purpose::STANDARD.encode(salt);
let hash_b64 = base64::engine::general_purpose::STANDARD.encode(&hash);
ModifyProtection {
salt_b64,
hash_b64,
spin_count,
crypt_provider_type: "rsaFull",
algorithm_sid: 14,
}
}
pub fn verify_password(&self, password: &str) -> bool {
let salt = match base64::engine::general_purpose::STANDARD.decode(&self.salt_b64) {
Ok(s) => s,
Err(_) => return false,
};
let expected_hash = compute_modify_hash(password, &salt, self.spin_count);
let expected_b64 = base64::engine::general_purpose::STANDARD.encode(&expected_hash);
expected_b64 == self.hash_b64
}
pub fn to_xml_element(&self) -> String {
format!(
r#"<p:modifyVerifier cryptProviderType="{}" cryptAlgorithmClass="hash" cryptAlgorithmType="typeAny" cryptAlgorithmSid="{}" cryptSpinCount="{}" hash="{}" salt="{}"/>"#,
self.crypt_provider_type,
self.algorithm_sid,
self.spin_count,
self.hash_b64,
self.salt_b64,
)
}
}
pub fn compute_modify_hash(password: &str, salt: &[u8], spin_count: u32) -> Vec<u8> {
let password_utf16le: Vec<u8> = password
.encode_utf16()
.flat_map(|u| u.to_le_bytes())
.collect();
let mut hasher = Sha512::new();
hasher.update(salt);
hasher.update(&password_utf16le);
let mut h = hasher.finalize().to_vec();
for i in 0..spin_count {
let mut hasher = Sha512::new();
hasher.update(&h);
hasher.update(i.to_le_bytes());
h = hasher.finalize().to_vec();
}
h
}
#[derive(Clone, Debug)]
pub struct AgileEncryptionParams {
pub password_salt_b64: String,
pub data_salt_b64: String,
pub spin_count: u32,
pub encrypted_key_value_b64: String,
pub encrypted_verifier_hash_input_b64: String,
pub encrypted_verifier_hash_value_b64: String,
}
pub fn generate_random_bytes(len: usize) -> Vec<u8> {
let mut result = Vec::with_capacity(len);
let mut counter: u64 = 0;
while result.len() < len {
let mut hasher = Sha512::new();
hasher.update(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos()
.to_le_bytes(),
);
hasher.update(counter.to_le_bytes());
hasher.update(&result);
let digest = hasher.finalize();
let needed = std::cmp::min(64, len - result.len());
result.extend_from_slice(&digest[..needed]);
counter += 1;
}
result
}
fn agile_hash_password(password: &str, salt: &[u8], spin_count: u32) -> Vec<u8> {
let password_utf16le: Vec<u8> = password
.encode_utf16()
.flat_map(|u| u.to_le_bytes())
.collect();
let mut hasher = Sha512::new();
hasher.update(salt);
hasher.update(&password_utf16le);
let mut h = hasher.finalize().to_vec();
for i in 0..spin_count {
let mut hasher = Sha512::new();
hasher.update(i.to_le_bytes());
hasher.update(&h);
h = hasher.finalize().to_vec();
}
h
}
fn agile_hash_key(key: &[u8], salt: &[u8], spin_count: u32) -> Vec<u8> {
let mut hasher = Sha512::new();
hasher.update(salt);
hasher.update(key);
let mut h = hasher.finalize().to_vec();
for i in 0..spin_count {
let mut hasher = Sha512::new();
hasher.update(i.to_le_bytes());
hasher.update(&h);
h = hasher.finalize().to_vec();
}
h
}
fn derive_key_with_block_key(hash: &[u8], block_key: &[u8], key_len: usize) -> Vec<u8> {
let mut hasher = Sha512::new();
hasher.update(hash);
hasher.update(block_key);
let derived = hasher.finalize();
derived[..key_len].to_vec()
}
fn iv_from_salt(salt: &[u8]) -> Vec<u8> {
salt[..AES_BLOCK_SIZE].to_vec()
}
pub fn aes_256_cbc_encrypt(key: &[u8], iv: &[u8], data: &[u8]) -> Result<Vec<u8>> {
if key.len() != AES_KEY_LEN {
return Err(Error::encryption("AES-256 key must be 32 bytes"));
}
if iv.len() != AES_BLOCK_SIZE {
return Err(Error::encryption("AES IV must be 16 bytes"));
}
use aes::Aes256;
use cbc::cipher::{BlockEncryptMut, KeyIvInit};
type Aes256CbcEnc = cbc::Encryptor<Aes256>;
let encryptor = Aes256CbcEnc::new_from_slices(key, iv)
.map_err(|e| Error::encryption(format!("AES init failed: {}", e)))?;
let ciphertext = encryptor.encrypt_padded_vec_mut::<cbc::cipher::block_padding::Pkcs7>(data);
Ok(ciphertext)
}
pub fn aes_256_cbc_decrypt(key: &[u8], iv: &[u8], data: &[u8]) -> Result<Vec<u8>> {
if key.len() != AES_KEY_LEN {
return Err(Error::encryption("AES-256 key must be 32 bytes"));
}
if iv.len() != AES_BLOCK_SIZE {
return Err(Error::encryption("AES IV must be 16 bytes"));
}
if data.len() % AES_BLOCK_SIZE != 0 {
return Err(Error::encryption(
"ciphertext length must be multiple of 16",
));
}
use aes::Aes256;
use cbc::cipher::{BlockDecryptMut, KeyIvInit};
type Aes256CbcDec = cbc::Decryptor<Aes256>;
let decryptor = Aes256CbcDec::new_from_slices(key, iv)
.map_err(|e| Error::encryption(format!("AES init failed: {}", e)))?;
let plaintext = decryptor
.decrypt_padded_vec_mut::<cbc::cipher::block_padding::Pkcs7>(data)
.map_err(|e| Error::encryption(format!("AES decrypt failed: {}", e)))?;
Ok(plaintext)
}
pub fn encrypt_package(zip_bytes: &[u8], password: &str) -> Result<Vec<u8>> {
let spin_count = AGILE_SPIN_COUNT;
let password_salt = generate_random_bytes(SALT_LEN);
let key_data_salt = generate_random_bytes(SALT_LEN);
let secret_key = generate_random_bytes(AES_KEY_LEN);
let password_hash = agile_hash_password(password, &password_salt, spin_count);
let key_encrypt_key =
derive_key_with_block_key(&password_hash, BLOCK_KEY_ENCRYPTED_KEY_VALUE, AES_KEY_LEN);
let key_iv = iv_from_salt(&password_salt);
let encrypted_key = aes_256_cbc_encrypt(&key_encrypt_key, &key_iv, &secret_key)?;
let verifier_hash_input = generate_random_bytes(SALT_LEN);
let verifier_hash_value = {
let mut hasher = Sha512::new();
hasher.update(&verifier_hash_input);
hasher.finalize().to_vec()
};
let verifier_input_key =
derive_key_with_block_key(&password_hash, BLOCK_KEY_VERIFIER_HASH_INPUT, AES_KEY_LEN);
let encrypted_verifier_hash_input =
aes_256_cbc_encrypt(&verifier_input_key, &key_iv, &verifier_hash_input)?;
let verifier_value_key =
derive_key_with_block_key(&password_hash, BLOCK_KEY_VERIFIER_HASH_VALUE, AES_KEY_LEN);
let encrypted_verifier_hash_value =
aes_256_cbc_encrypt(&verifier_value_key, &key_iv, &verifier_hash_value)?;
let data_hash = agile_hash_key(&secret_key, &key_data_salt, spin_count);
let data_key = data_hash[..AES_KEY_LEN].to_vec();
let data_iv = iv_from_salt(&key_data_salt);
let original_len = zip_bytes.len() as u32;
let encrypted_data = aes_256_cbc_encrypt(&data_key, &data_iv, zip_bytes)?;
let mut encrypted_package = Vec::with_capacity(4 + encrypted_data.len());
encrypted_package.extend_from_slice(&original_len.to_le_bytes());
encrypted_package.extend_from_slice(&encrypted_data);
let encryption_info_xml = build_encryption_info_xml(
&password_salt,
&key_data_salt,
&encrypted_key,
&encrypted_verifier_hash_input,
&encrypted_verifier_hash_value,
spin_count,
);
build_encrypted_zip(&encryption_info_xml, &encrypted_package)
}
pub fn decrypt_package(encrypted_bytes: &[u8], password: &str) -> Result<Vec<u8>> {
let cursor = std::io::Cursor::new(encrypted_bytes);
let mut zip = zip::ZipArchive::new(cursor)
.map_err(|e| Error::encryption(format!("encrypted zip open failed: {}", e)))?;
let mut info_xml = String::new();
let mut info_found = false;
for i in 0..zip.len() {
let mut entry = zip
.by_index(i)
.map_err(|e| Error::encryption(format!("zip read failed: {}", e)))?;
if entry.name().contains("EncryptionInfo") {
use std::io::Read;
entry
.read_to_string(&mut info_xml)
.map_err(|e| Error::encryption(format!("read EncryptionInfo failed: {}", e)))?;
info_found = true;
break;
}
}
if !info_found {
return Err(Error::encryption("EncryptionInfo not found in package"));
}
let params = parse_encryption_info(&info_xml)?;
let mut package_data = Vec::new();
let mut package_found = false;
for i in 0..zip.len() {
let mut entry = zip
.by_index(i)
.map_err(|e| Error::encryption(format!("zip read failed: {}", e)))?;
if entry.name().contains("EncryptedPackage") {
use std::io::Read;
entry
.read_to_end(&mut package_data)
.map_err(|e| Error::encryption(format!("read EncryptedPackage failed: {}", e)))?;
package_found = true;
break;
}
}
if !package_found {
return Err(Error::encryption("EncryptedPackage not found in package"));
}
let password_salt = base64::engine::general_purpose::STANDARD
.decode(¶ms.password_salt_b64)
.map_err(|e| Error::encryption(format!("password salt decode failed: {}", e)))?;
let password_hash = agile_hash_password(password, &password_salt, params.spin_count);
let key_encrypt_key =
derive_key_with_block_key(&password_hash, BLOCK_KEY_ENCRYPTED_KEY_VALUE, AES_KEY_LEN);
let key_iv = iv_from_salt(&password_salt);
let encrypted_key_value = base64::engine::general_purpose::STANDARD
.decode(¶ms.encrypted_key_value_b64)
.map_err(|e| Error::encryption(format!("encrypted key decode failed: {}", e)))?;
let secret_key = aes_256_cbc_decrypt(&key_encrypt_key, &key_iv, &encrypted_key_value)?;
if !params.encrypted_verifier_hash_input_b64.is_empty()
&& !params.encrypted_verifier_hash_value_b64.is_empty()
{
let verifier_input_key =
derive_key_with_block_key(&password_hash, BLOCK_KEY_VERIFIER_HASH_INPUT, AES_KEY_LEN);
let verifier_value_key =
derive_key_with_block_key(&password_hash, BLOCK_KEY_VERIFIER_HASH_VALUE, AES_KEY_LEN);
let encrypted_verifier_input = base64::engine::general_purpose::STANDARD
.decode(¶ms.encrypted_verifier_hash_input_b64)
.map_err(|e| {
Error::encryption(format!("encrypted verifier input decode failed: {}", e))
})?;
let encrypted_verifier_value = base64::engine::general_purpose::STANDARD
.decode(¶ms.encrypted_verifier_hash_value_b64)
.map_err(|e| {
Error::encryption(format!("encrypted verifier value decode failed: {}", e))
})?;
let verifier_input =
aes_256_cbc_decrypt(&verifier_input_key, &key_iv, &encrypted_verifier_input)?;
let verifier_value =
aes_256_cbc_decrypt(&verifier_value_key, &key_iv, &encrypted_verifier_value)?;
let expected_hash = {
let mut hasher = Sha512::new();
hasher.update(&verifier_input);
hasher.finalize().to_vec()
};
if expected_hash != verifier_value {
return Err(Error::encryption("password verification failed"));
}
}
let key_data_salt = base64::engine::general_purpose::STANDARD
.decode(¶ms.data_salt_b64)
.map_err(|e| Error::encryption(format!("data salt decode failed: {}", e)))?;
let data_hash = agile_hash_key(&secret_key, &key_data_salt, params.spin_count);
let data_key = data_hash[..AES_KEY_LEN].to_vec();
let data_iv = iv_from_salt(&key_data_salt);
if package_data.len() < 4 {
return Err(Error::encryption("EncryptedPackage too short"));
}
let _original_len = u32::from_le_bytes([
package_data[0],
package_data[1],
package_data[2],
package_data[3],
]);
let encrypted_data = &package_data[4..];
let decrypted = aes_256_cbc_decrypt(&data_key, &data_iv, encrypted_data)?;
Ok(decrypted)
}
pub fn is_encrypted_package(bytes: &[u8]) -> bool {
let cursor = std::io::Cursor::new(bytes);
match zip::ZipArchive::new(cursor) {
Ok(mut zip) => {
for i in 0..zip.len() {
if let Ok(entry) = zip.by_index(i) {
if entry.name().contains("EncryptionInfo") {
return true;
}
}
}
false
}
Err(_) => false,
}
}
#[derive(Clone, Debug)]
struct ParsedEncryptionInfo {
password_salt_b64: String,
data_salt_b64: String,
encrypted_key_value_b64: String,
encrypted_verifier_hash_input_b64: String,
encrypted_verifier_hash_value_b64: String,
spin_count: u32,
}
fn parse_encryption_info(xml: &str) -> Result<ParsedEncryptionInfo> {
use quick_xml::events::Event;
use quick_xml::reader::Reader;
let mut rd = Reader::from_str(xml);
rd.config_mut().trim_text(true);
let mut buf = Vec::new();
let mut password_salt_b64 = String::new();
let mut data_salt_b64 = String::new();
let mut encrypted_key_value_b64 = String::new();
let mut encrypted_verifier_hash_input_b64 = String::new();
let mut encrypted_verifier_hash_value_b64 = String::new();
let mut spin_count: u32 = AGILE_SPIN_COUNT;
loop {
match rd.read_event_into(&mut buf) {
Ok(Event::Empty(e)) | Ok(Event::Start(e)) => {
let name = e.name();
let local = local_name(name.as_ref());
if local == b"keyData" {
for attr in e.attributes().flatten() {
if attr.key.as_ref() == b"saltValue" {
data_salt_b64 = attr_unescape_value(&attr);
}
}
} else if local == b"encryptedKey" {
for attr in e.attributes().flatten() {
let key = attr.key.as_ref();
if key == b"saltValue" {
password_salt_b64 = attr_unescape_value(&attr);
} else if key == b"spinCount" {
let v = attr_unescape_value(&attr);
spin_count = v.parse::<u32>().unwrap_or(AGILE_SPIN_COUNT);
} else if key == b"encryptedKeyValue" {
encrypted_key_value_b64 = attr_unescape_value(&attr);
} else if key == b"encryptedVerifierHashInput" {
encrypted_verifier_hash_input_b64 = attr_unescape_value(&attr);
} else if key == b"encryptedVerifierHashValue" {
encrypted_verifier_hash_value_b64 = attr_unescape_value(&attr);
}
}
}
}
Ok(Event::Eof) => break,
Err(e) => {
return Err(Error::encryption(format!(
"EncryptionInfo XML parse error: {}",
e
)))
}
_ => {}
}
}
Ok(ParsedEncryptionInfo {
password_salt_b64,
data_salt_b64,
encrypted_key_value_b64,
encrypted_verifier_hash_input_b64,
encrypted_verifier_hash_value_b64,
spin_count,
})
}
fn local_name(name: &[u8]) -> &[u8] {
match name.iter().position(|&c| c == b':') {
Some(i) => &name[i + 1..],
None => name,
}
}
fn attr_unescape_value(attr: &quick_xml::events::attributes::Attribute<'_>) -> String {
attr.normalized_value(quick_xml::XmlVersion::Implicit1_0)
.map(|cow| cow.into_owned())
.unwrap_or_else(|_| String::from_utf8_lossy(attr.value.as_ref()).into_owned())
}
fn build_encryption_info_xml(
password_salt: &[u8],
key_data_salt: &[u8],
encrypted_key: &[u8],
encrypted_verifier_hash_input: &[u8],
encrypted_verifier_hash_value: &[u8],
spin_count: u32,
) -> String {
let password_salt_b64 = base64::engine::general_purpose::STANDARD.encode(password_salt);
let key_data_salt_b64 = base64::engine::general_purpose::STANDARD.encode(key_data_salt);
let encrypted_key_b64 = base64::engine::general_purpose::STANDARD.encode(encrypted_key);
let encrypted_verifier_hash_input_b64 =
base64::engine::general_purpose::STANDARD.encode(encrypted_verifier_hash_input);
let encrypted_verifier_hash_value_b64 =
base64::engine::general_purpose::STANDARD.encode(encrypted_verifier_hash_value);
format!(
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<encryption xmlns="http://schemas.microsoft.com/office/2006/encryption" xmlns:p="http://schemas.microsoft.com/office/2006/keyEncryptor/password">
<keyData saltSize="{salt_size}" blockSize="16" keyBits="256" hashSize="64" cipherAlgorithm="AES" cipherChaining="ChainingModeCBC" hashAlgorithm="SHA512" saltValue="{key_data_salt_b64}" />
<dataIntegrity encryptedHmacKey="" encryptedHmacValue="" algorithmIdentifier="SHA512" />
<keyEncryptors>
<keyEncryptor uri="http://schemas.microsoft.com/office/2006/keyEncryptor/password">
<p:encryptedKey spinCount="{spin_count}" saltSize="{salt_size}" saltValue="{password_salt_b64}" hashAlgorithm="SHA512" cipherAlgorithm="AES" keyBits="256" blockSize="16" encryptedKeyValue="{encrypted_key_b64}" encryptedVerifierHashInput="{encrypted_verifier_hash_input_b64}" encryptedVerifierHashValue="{encrypted_verifier_hash_value_b64}" />
</keyEncryptor>
</keyEncryptors>
</encryption>"#,
salt_size = SALT_LEN,
spin_count = spin_count,
key_data_salt_b64 = key_data_salt_b64,
password_salt_b64 = password_salt_b64,
encrypted_key_b64 = encrypted_key_b64,
encrypted_verifier_hash_input_b64 = encrypted_verifier_hash_input_b64,
encrypted_verifier_hash_value_b64 = encrypted_verifier_hash_value_b64,
)
}
fn build_encrypted_zip(encryption_info_xml: &str, encrypted_package: &[u8]) -> Result<Vec<u8>> {
let mut buf = Vec::new();
{
let cursor = std::io::Cursor::new(&mut buf);
let mut zip = zip::ZipWriter::new(cursor);
let opts: zip::write::FileOptions<'_, ()> = zip::write::FileOptions::default()
.compression_method(zip::CompressionMethod::Deflated)
.unix_permissions(0o644);
let ct_xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="bin" ContentType="application/vnd.ms-office.encryptedPackage" />
</Types>"#;
zip.start_file("[Content_Types].xml", opts)
.map_err(|e| Error::encryption(format!("zip write failed: {}", e)))?;
zip.write_all(ct_xml.as_bytes())
.map_err(|e| Error::encryption(format!("zip write failed: {}", e)))?;
let rels_xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.microsoft.com/office/2006/relationships/encryption" Target="EncryptionInfo" />
</Relationships>"#;
zip.start_file("_rels/.rels", opts)
.map_err(|e| Error::encryption(format!("zip write failed: {}", e)))?;
zip.write_all(rels_xml.as_bytes())
.map_err(|e| Error::encryption(format!("zip write failed: {}", e)))?;
zip.start_file("EncryptionInfo", opts)
.map_err(|e| Error::encryption(format!("zip write failed: {}", e)))?;
zip.write_all(encryption_info_xml.as_bytes())
.map_err(|e| Error::encryption(format!("zip write failed: {}", e)))?;
zip.start_file("EncryptedPackage", opts)
.map_err(|e| Error::encryption(format!("zip write failed: {}", e)))?;
zip.write_all(encrypted_package)
.map_err(|e| Error::encryption(format!("zip write failed: {}", e)))?;
zip.finish()
.map_err(|e| Error::encryption(format!("zip finish failed: {}", e)))?;
}
Ok(buf)
}
use std::io::Write;