pub mod wal;
use anyhow::{bail, Result};
use aes::Aes256;
use cbc::Decryptor;
use cbc::cipher::{BlockDecryptMut, KeyIvInit};
use std::path::Path;
pub const PAGE_SZ: usize = 4096;
pub const SALT_SZ: usize = 16;
pub const RESERVE_SZ: usize = 80;
pub const SQLITE_HDR: &[u8] = b"SQLite format 3\x00";
type Aes256CbcDec = Decryptor<Aes256>;
pub fn decrypt_page(enc_key: &[u8; 32], page_data: &[u8], pgno: u32) -> Result<Vec<u8>> {
if page_data.len() < PAGE_SZ {
bail!("页面数据不足 {} 字节", PAGE_SZ);
}
let iv_offset = PAGE_SZ - RESERVE_SZ;
let iv: &[u8; 16] = page_data[iv_offset..iv_offset + 16]
.try_into()
.expect("IV 长度固定为 16");
let mut result = vec![0u8; PAGE_SZ];
if pgno == 1 {
let enc = &page_data[SALT_SZ..PAGE_SZ - RESERVE_SZ];
let dec = aes_cbc_decrypt(enc_key, iv, enc)?;
result[..16].copy_from_slice(SQLITE_HDR);
result[16..PAGE_SZ - RESERVE_SZ].copy_from_slice(&dec);
} else {
let enc = &page_data[..PAGE_SZ - RESERVE_SZ];
let dec = aes_cbc_decrypt(enc_key, iv, enc)?;
result[..PAGE_SZ - RESERVE_SZ].copy_from_slice(&dec);
}
Ok(result)
}
fn aes_cbc_decrypt(key: &[u8; 32], iv: &[u8; 16], data: &[u8]) -> Result<Vec<u8>> {
if data.is_empty() || data.len() % 16 != 0 {
bail!("密文长度不是 AES 块大小的倍数: {}", data.len());
}
let mut buf = data.to_vec();
Aes256CbcDec::new(key.into(), iv.into())
.decrypt_blocks_mut(unsafe {
std::slice::from_raw_parts_mut(
buf.as_mut_ptr() as *mut aes::cipher::Block<Aes256>,
buf.len() / 16,
)
});
Ok(buf)
}
pub fn full_decrypt(db_path: &Path, out_path: &Path, enc_key: &[u8; 32]) -> Result<()> {
let data = std::fs::read(db_path)?;
if data.is_empty() {
bail!("数据库文件为空: {}", db_path.display());
}
if let Some(parent) = out_path.parent() {
std::fs::create_dir_all(parent)?;
}
let total_pages = (data.len() + PAGE_SZ - 1) / PAGE_SZ;
let mut out = Vec::with_capacity(data.len());
for pgno in 1..=total_pages {
let offset = (pgno - 1) * PAGE_SZ;
let end = std::cmp::min(offset + PAGE_SZ, data.len());
let mut page = data[offset..end].to_vec();
if page.len() < PAGE_SZ {
page.resize(PAGE_SZ, 0);
}
let dec = decrypt_page(enc_key, &page, pgno as u32)?;
out.extend_from_slice(&dec);
}
std::fs::write(out_path, &out)?;
Ok(())
}