ext4 com diário e entradas apagadas; LUKS1/2 e BitLocker (todas as imagens de referência do cryptsetup)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b1ac320193
commit
86344c0536
15 files changed
+2583
-80
No files matched your search
@@ -0,0 +1,95 @@
|
||||
//! BitLocker: imagens reais do conjunto de testes do cryptsetup (GitLab), com senhas conhecidas.
|
||||
//! Baixe com `scripts/fetch-testdata.sh`; sem elas os testes são pulados.
|
||||
|
||||
use guara_core::crypt::{bitlocker, Secret, UnlockError};
|
||||
use guara_core::partition::detect_fs_in;
|
||||
use guara_core::source::{read_vec, FileSource, Source};
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn dir() -> Option<PathBuf> {
|
||||
let d = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../target/testdata/bitlk-images");
|
||||
d.join("images.conf").exists().then_some(d)
|
||||
}
|
||||
|
||||
fn conf(d: &PathBuf) -> BTreeMap<String, BTreeMap<String, String>> {
|
||||
let mut m: BTreeMap<String, BTreeMap<String, String>> = BTreeMap::new();
|
||||
let mut cur = String::new();
|
||||
for l in std::fs::read_to_string(d.join("images.conf")).unwrap().lines() {
|
||||
let l = l.trim();
|
||||
if let Some(n) = l.strip_prefix('[').and_then(|x| x.strip_suffix(']')) {
|
||||
cur = n.to_string();
|
||||
} else if let Some((k, v)) = l.split_once('=') {
|
||||
m.entry(cur.clone()).or_default().insert(k.into(), v.into());
|
||||
}
|
||||
}
|
||||
m
|
||||
}
|
||||
|
||||
fn check_fs(src: &dyn Source, what: &str) {
|
||||
// Imagens "encrypt-on-write" não têm sistema de arquivos reconhecível (UUID vazio no images.conf).
|
||||
if what.contains("-eow ") {
|
||||
return;
|
||||
}
|
||||
let head = read_vec(src, 0, 4096).unwrap();
|
||||
let fs = detect_fs_in(&head);
|
||||
assert!(fs.is_some(), "{what}: setor 0 aberto não é um sistema de arquivos: {:02x?}", &head[..32]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opens_all_reference_images_with_password_and_recovery_key() {
|
||||
let Some(d) = dir() else {
|
||||
eprintln!("SKIP: execute scripts/fetch-testdata.sh");
|
||||
return;
|
||||
};
|
||||
let mut opened = 0;
|
||||
for (name, c) in conf(&d) {
|
||||
let path = d.join(format!("{name}.img"));
|
||||
if !path.exists() {
|
||||
continue;
|
||||
}
|
||||
let src: Arc<dyn Source> = Arc::new(FileSource::open(&path).unwrap());
|
||||
let pw = c.get("PW").cloned().unwrap_or_default();
|
||||
let rp = c.get("RP").cloned().unwrap_or_default();
|
||||
// Proteção suspensa (chave limpa): abre sem segredo nenhum.
|
||||
if let Ok(u) = bitlocker::unlock(src.clone(), &[]) {
|
||||
check_fs(u.source.as_ref(), &format!("{name} (chave limpa)"));
|
||||
eprintln!("ok {name}: chave limpa");
|
||||
opened += 1;
|
||||
continue;
|
||||
}
|
||||
match bitlocker::unlock(src.clone(), &[Secret::Password("errada".into())]) {
|
||||
Err(UnlockError::WrongSecret) | Err(UnlockError::Unsupported(_)) => {}
|
||||
Err(e) => panic!("{name}: {e}"),
|
||||
Ok(_) => panic!("{name}: abriu com senha errada"),
|
||||
}
|
||||
let mut views = vec![];
|
||||
if !pw.is_empty() {
|
||||
let u = bitlocker::unlock(src.clone(), &[Secret::Password(pw.clone())]).unwrap_or_else(|e| panic!("{name} senha: {e}"));
|
||||
check_fs(u.source.as_ref(), &format!("{name} (senha)"));
|
||||
views.push(u);
|
||||
}
|
||||
if !rp.is_empty() {
|
||||
let u = bitlocker::unlock(src.clone(), &[Secret::RecoveryKey(rp.clone())]).unwrap_or_else(|e| panic!("{name} recuperação: {e}"));
|
||||
check_fs(u.source.as_ref(), &format!("{name} (recuperação)"));
|
||||
views.push(u);
|
||||
}
|
||||
if let Some(g) = c.get("SK_VMK_GUID") {
|
||||
let f = d.join(format!("{}.BEK", g.to_uppercase()));
|
||||
if f.exists() {
|
||||
let u = bitlocker::unlock(src.clone(), &[Secret::KeyFile(std::fs::read(f).unwrap())]).unwrap_or_else(|e| panic!("{name} BEK: {e}"));
|
||||
check_fs(u.source.as_ref(), &format!("{name} (BEK)"));
|
||||
views.push(u);
|
||||
}
|
||||
}
|
||||
assert!(!views.is_empty(), "{name}: nenhum protetor testável");
|
||||
let first = read_vec(views[0].source.as_ref(), 0, 1 << 20).unwrap();
|
||||
for v in &views[1..] {
|
||||
assert!(read_vec(v.source.as_ref(), 0, 1 << 20).unwrap() == first, "{name}: protetores divergem");
|
||||
}
|
||||
opened += 1;
|
||||
eprintln!("ok {name}: {} ({} protetores)", views[0].description, views.len());
|
||||
}
|
||||
assert!(opened > 0);
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
//! ext4: arquivos apagados com nome, a partir de imagens criadas com mke2fs/debugfs.
|
||||
|
||||
mod common;
|
||||
use common::*;
|
||||
use guara_core::*;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
fn have(tool: &str) -> bool {
|
||||
Command::new("sh")
|
||||
.args(["-c", &format!("command -v {tool}")])
|
||||
.output()
|
||||
.map(|o| o.status.success())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn debugfs(img: &Path, cmds: &[&str]) -> String {
|
||||
let mut out = String::new();
|
||||
for c in cmds {
|
||||
let o = Command::new("debugfs").arg("-w").arg("-R").arg(c).arg(img).output().unwrap();
|
||||
out.push_str(&String::from_utf8_lossy(&o.stdout));
|
||||
out.push_str(&String::from_utf8_lossy(&o.stderr));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn inode_of(img: &Path, path: &str) -> u64 {
|
||||
let o = debugfs(img, &[&format!("stat {path}")]);
|
||||
let l = o.lines().find(|l| l.starts_with("Inode:")).unwrap_or_else(|| panic!("{o}"));
|
||||
l.split_whitespace().nth(1).unwrap().parse().unwrap()
|
||||
}
|
||||
|
||||
fn content(seed: u32, len: usize) -> Vec<u8> {
|
||||
let mut x = 88172645463325252u64 ^ seed as u64;
|
||||
(0..len)
|
||||
.map(|_| {
|
||||
x ^= x << 13;
|
||||
x ^= x >> 7;
|
||||
x ^= x << 17;
|
||||
(x >> 24) as u8
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
struct Img {
|
||||
_dir: tempfile::TempDir,
|
||||
img: PathBuf,
|
||||
files: Vec<(&'static str, Vec<u8>)>,
|
||||
}
|
||||
|
||||
fn build(opts: &[&str]) -> Option<Img> {
|
||||
if !have("mke2fs") || !have("debugfs") {
|
||||
eprintln!("SKIP: e2fsprogs ausente");
|
||||
return None;
|
||||
}
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let root = dir.path().join("root");
|
||||
std::fs::create_dir_all(root.join("docs/sub")).unwrap();
|
||||
std::fs::create_dir_all(root.join("fotos")).unwrap();
|
||||
let files = vec![
|
||||
("docs/relatorio.dat", content(1, 150_000)),
|
||||
("docs/sub/contrato.dat", content(2, 9_000)),
|
||||
("fotos/viagem.dat", content(3, 700_000)),
|
||||
("fotos/fica.dat", content(4, 30_000)),
|
||||
];
|
||||
for (p, c) in &files {
|
||||
std::fs::write(root.join(p), c).unwrap();
|
||||
}
|
||||
let img = dir.path().join("e.img");
|
||||
let f = std::fs::File::create(&img).unwrap();
|
||||
f.set_len(24 << 20).unwrap();
|
||||
let st = Command::new("mke2fs")
|
||||
.args(["-q", "-F", "-d"])
|
||||
.arg(&root)
|
||||
.args(opts)
|
||||
.arg(&img)
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(st.status.success(), "{}", String::from_utf8_lossy(&st.stderr));
|
||||
Some(Img { _dir: dir, img, files })
|
||||
}
|
||||
|
||||
fn deep(img: &Path) -> common::Collect {
|
||||
init();
|
||||
let opts = ScanOptions { mode: ScanMode::Deep, ..ScanOptions::default() };
|
||||
scan_path(img, &opts)
|
||||
}
|
||||
|
||||
fn recovered(img: &Path, f: &Found) -> Vec<u8> {
|
||||
let d = tempfile::tempdir().unwrap();
|
||||
let rep = recover_all(img, std::slice::from_ref(f), d.path());
|
||||
assert!(rep.failed.is_empty(), "{:?}", rep.failed);
|
||||
let p = &rep.recovered[0].path;
|
||||
let p = if Path::new(p).is_absolute() { PathBuf::from(p) } else { d.path().join(p) };
|
||||
std::fs::read(p).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deleted_by_debugfs_keeps_extents_and_name() {
|
||||
let Some(i) = build(&["-t", "ext4"]) else { return };
|
||||
debugfs(&i.img, &["rm /docs/relatorio.dat", "rm /fotos/viagem.dat"]);
|
||||
let c = deep(&i.img);
|
||||
for (p, data) in i.files.iter().filter(|f| f.0 == "docs/relatorio.dat" || f.0 == "fotos/viagem.dat") {
|
||||
let name = p.rsplit('/').next().unwrap();
|
||||
let f = c.found.iter().find(|f| f.name == name && f.origin == Origin::Ext).unwrap_or_else(|| panic!("{name} não encontrado: {:?}", c.found.iter().map(|f| (&f.name, f.origin)).collect::<Vec<_>>()));
|
||||
assert_eq!(f.size, data.len() as u64);
|
||||
assert_eq!(f.dir.as_deref(), Some(&format!("/{}", p.rsplit_once('/').unwrap().0)[..]));
|
||||
assert!(recovered(&i.img, f) == *data, "{name}");
|
||||
}
|
||||
// O arquivo que não foi apagado não aparece.
|
||||
assert!(!c.found.iter().any(|f| f.name == "fica.dat" && f.origin == Origin::Ext));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn truncated_inode_recovered_by_remnant_extents() {
|
||||
// Simula o que o kernel faz ao apagar: tamanho zerado e contagem de extents zerada.
|
||||
let Some(i) = build(&["-t", "ext4"]) else { return };
|
||||
let ino = inode_of(&i.img, "/docs/sub/contrato.dat");
|
||||
debugfs(
|
||||
&i.img,
|
||||
&[
|
||||
"rm /docs/sub/contrato.dat",
|
||||
&format!("sif <{ino}> size 0"),
|
||||
&format!("sif <{ino}> block[0] 0xF30A"),
|
||||
],
|
||||
);
|
||||
let c = deep(&i.img);
|
||||
let f = c.found.iter().find(|f| f.name == "contrato.dat").expect("contrato.dat");
|
||||
assert_eq!(f.origin, Origin::Ext);
|
||||
// Tamanho exato é perdido: recupera todos os blocos (9000 bytes arredondados para 3 blocos de 4K).
|
||||
assert_eq!(f.size, 12288);
|
||||
let got = recovered(&i.img, f);
|
||||
assert_eq!(&got[..9000], &i.files[1].1[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn journal_copy_restores_exact_size() {
|
||||
if !have("debugfs") {
|
||||
return;
|
||||
}
|
||||
let Some(i) = build(&["-t", "ext4", "-O", "has_journal"]) else { return };
|
||||
// Bloco da tabela de inodes que contém o inode do arquivo.
|
||||
let ino = inode_of(&i.img, "/fotos/viagem.dat");
|
||||
let o = debugfs(&i.img, &["stats"]);
|
||||
let _ = o;
|
||||
let blk_line = debugfs(&i.img, &[&format!("imap <{ino}>")]);
|
||||
// "Inode 12 is part of block group 0\n\tlocated at block 1058, offset 0x0300"
|
||||
let blk: u64 = blk_line
|
||||
.split("located at block ")
|
||||
.nth(1)
|
||||
.and_then(|s| s.split(',').next())
|
||||
.and_then(|s| s.trim().parse().ok())
|
||||
.unwrap_or_else(|| panic!("imap: {blk_line}"));
|
||||
let bs = 4096u64;
|
||||
let raw = std::fs::read(&i.img).unwrap();
|
||||
let old = &raw[(blk * bs) as usize..((blk + 1) * bs) as usize];
|
||||
let bin = i.img.with_extension("blk");
|
||||
std::fs::write(&bin, old).unwrap();
|
||||
let script = i.img.with_extension("cmds");
|
||||
std::fs::write(&script, format!("jo\njw -b {blk} {}\njc\njc\n", bin.display())).unwrap();
|
||||
let o = Command::new("debugfs").arg("-w").arg("-f").arg(&script).arg(&i.img).output().unwrap();
|
||||
let j = format!("{}{}", String::from_utf8_lossy(&o.stdout), String::from_utf8_lossy(&o.stderr));
|
||||
if j.contains("Unknown request") || j.contains("not supported") {
|
||||
eprintln!("SKIP: debugfs sem suporte a escrita no diário: {j}");
|
||||
return;
|
||||
}
|
||||
// Exclui e zera como o kernel faria.
|
||||
debugfs(
|
||||
&i.img,
|
||||
&[
|
||||
"rm /fotos/viagem.dat",
|
||||
&format!("sif <{ino}> size 0"),
|
||||
&format!("sif <{ino}> block[0] 0xF30A"),
|
||||
],
|
||||
);
|
||||
let c = deep(&i.img);
|
||||
let f = c.found.iter().find(|f| f.name == "viagem.dat").unwrap_or_else(|| panic!("sem viagem.dat: {:?}", c.found.iter().map(|f| &f.name).collect::<Vec<_>>()));
|
||||
assert_eq!(f.size, 700_000, "{:?}", f.note);
|
||||
assert!(recovered(&i.img, f) == i.files[2].1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ext2_blockmap_deleted_is_not_invented() {
|
||||
// ext2 zera os ponteiros ao apagar; sem diário não há o que recuperar por metadados,
|
||||
// mas a varredura não pode falhar nem listar o arquivo que continua existindo.
|
||||
let Some(i) = build(&["-t", "ext2"]) else { return };
|
||||
let c = deep(&i.img);
|
||||
assert!(!c.found.iter().any(|f| f.origin == Origin::Ext));
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
//! LUKS1/LUKS2: imagens criadas pelo cryptsetup; o conteúdo é cifrado por uma
|
||||
//! implementação independente (python cryptography) com a chave real do volume.
|
||||
|
||||
use guara_core::crypt::{luks, Secret, UnlockError};
|
||||
use guara_core::source::{read_vec, FileSource, Source};
|
||||
use std::path::Path;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::Arc;
|
||||
|
||||
fn have(tool: &str) -> bool {
|
||||
Command::new("sh")
|
||||
.args(["-c", &format!("command -v {tool}")])
|
||||
.output()
|
||||
.map(|o| o.status.success())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn py_ok() -> bool {
|
||||
Command::new("python3")
|
||||
.args(["-c", "import cryptography"])
|
||||
.output()
|
||||
.map(|o| o.status.success())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn run_stdin(cmd: &mut Command, input: &str) -> bool {
|
||||
use std::io::Write;
|
||||
let mut ch = cmd.stdin(Stdio::piped()).stdout(Stdio::null()).stderr(Stdio::null()).spawn().unwrap();
|
||||
ch.stdin.take().unwrap().write_all(input.as_bytes()).unwrap();
|
||||
ch.wait().unwrap().success()
|
||||
}
|
||||
|
||||
fn plain_data(len: usize) -> Vec<u8> {
|
||||
let mut x = 0x9E3779B97F4A7C15u64;
|
||||
(0..len)
|
||||
.map(|_| {
|
||||
x ^= x << 13;
|
||||
x ^= x >> 7;
|
||||
x ^= x << 17;
|
||||
(x >> 32) as u8
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
struct Case<'a> {
|
||||
name: &'a str,
|
||||
format_args: Vec<&'a str>,
|
||||
cipher: &'a str,
|
||||
sector: u32,
|
||||
ver2: bool,
|
||||
}
|
||||
|
||||
fn payload_offset(img: &Path, ver2: bool) -> u64 {
|
||||
let o = Command::new("cryptsetup").arg("luksDump").arg(img).output().unwrap();
|
||||
let t = String::from_utf8_lossy(&o.stdout).to_string();
|
||||
if ver2 {
|
||||
let l = t.lines().skip_while(|l| !l.starts_with("Data segments")).find(|l| l.contains("offset:")).unwrap();
|
||||
l.split(':').nth(1).unwrap().split_whitespace().next().unwrap().parse().unwrap()
|
||||
} else {
|
||||
let l = t.lines().find(|l| l.starts_with("Payload offset")).unwrap();
|
||||
l.split(':').nth(1).unwrap().trim().parse::<u64>().unwrap() * 512
|
||||
}
|
||||
}
|
||||
|
||||
fn run_case(c: &Case) {
|
||||
if !have("cryptsetup") || !py_ok() {
|
||||
eprintln!("SKIP: cryptsetup/python3-cryptography ausentes");
|
||||
return;
|
||||
}
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let img = dir.path().join("l.img");
|
||||
let plain = dir.path().join("plain.bin");
|
||||
let data = plain_data(3 * 1024 * 1024 + 4096);
|
||||
std::fs::write(&plain, &data).unwrap();
|
||||
std::fs::File::create(&img).unwrap().set_len(24 << 20).unwrap();
|
||||
let pw = "senha com acentuação ç";
|
||||
let mut fmt = Command::new("cryptsetup");
|
||||
fmt.args(["luksFormat", "--batch-mode", "--key-file", "-"]);
|
||||
fmt.args(&c.format_args);
|
||||
fmt.arg(&img);
|
||||
assert!(run_stdin(&mut fmt, pw), "luksFormat falhou ({})", c.name);
|
||||
let vkf = dir.path().join("vk.bin");
|
||||
let mut dump = Command::new("cryptsetup");
|
||||
dump.args(["luksDump", "--dump-volume-key", "--batch-mode", "--key-file", "-", "--volume-key-file"]);
|
||||
dump.arg(&vkf).arg(&img);
|
||||
assert!(run_stdin(&mut dump, pw), "dump da chave falhou ({})", c.name);
|
||||
let off = payload_offset(&img, c.ver2);
|
||||
let st = Command::new("python3")
|
||||
.arg(Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/support/luks_encrypt.py"))
|
||||
.arg(&img)
|
||||
.arg(&plain)
|
||||
.arg(&vkf)
|
||||
.arg(off.to_string())
|
||||
.arg(c.cipher)
|
||||
.arg(c.sector.to_string())
|
||||
.status()
|
||||
.unwrap();
|
||||
assert!(st.success());
|
||||
|
||||
let src: Arc<dyn Source> = Arc::new(FileSource::open(&img).unwrap());
|
||||
// Senha errada primeiro.
|
||||
match luks::unlock(src.clone(), &[Secret::Password("errada".into())]) {
|
||||
Err(UnlockError::WrongSecret) => {}
|
||||
Err(e) => panic!("{}: erro inesperado {e}", c.name),
|
||||
Ok(_) => panic!("{}: abriu com senha errada", c.name),
|
||||
}
|
||||
let u = luks::unlock(src.clone(), &[Secret::Password("x".into()), Secret::Password(pw.into())])
|
||||
.unwrap_or_else(|e| panic!("{}: {e}", c.name));
|
||||
let got = read_vec(u.source.as_ref(), 0, data.len()).unwrap();
|
||||
assert!(got == data, "{}: conteúdo difere", c.name);
|
||||
// Também abre com a chave do volume.
|
||||
let vk = std::fs::read(&vkf).unwrap();
|
||||
let u2 = luks::unlock(src, &[Secret::VolumeKey(vk)]).unwrap();
|
||||
assert_eq!(read_vec(u2.source.as_ref(), 1000, 5000).unwrap(), &data[1000..6000]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn luks1_xts() {
|
||||
run_case(&Case {
|
||||
name: "luks1-xts",
|
||||
format_args: vec!["--type", "luks1", "--pbkdf-force-iterations", "1000", "-c", "aes-xts-plain64", "-s", "512"],
|
||||
cipher: "aes-xts-plain64",
|
||||
sector: 512,
|
||||
ver2: false,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn luks1_cbc_essiv_sha1() {
|
||||
run_case(&Case {
|
||||
name: "luks1-cbc-essiv",
|
||||
format_args: vec!["--type", "luks1", "--pbkdf-force-iterations", "1000", "-c", "aes-cbc-essiv:sha256", "-s", "256", "-h", "sha1"],
|
||||
cipher: "aes-cbc-essiv:sha256",
|
||||
sector: 512,
|
||||
ver2: false,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn luks2_argon2id_xts() {
|
||||
run_case(&Case {
|
||||
name: "luks2-argon2id",
|
||||
format_args: vec!["--type", "luks2", "--pbkdf", "argon2id", "--pbkdf-force-iterations", "4", "--pbkdf-memory", "64", "-c", "aes-xts-plain64", "-s", "512"],
|
||||
cipher: "aes-xts-plain64",
|
||||
sector: 4096,
|
||||
ver2: true,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn luks2_pbkdf2_sector512_aes256() {
|
||||
run_case(&Case {
|
||||
name: "luks2-pbkdf2",
|
||||
format_args: vec!["--type", "luks2", "--pbkdf", "pbkdf2", "--pbkdf-force-iterations", "1000", "-c", "aes-xts-plain64", "-s", "256", "--sector-size", "512"],
|
||||
cipher: "aes-xts-plain64",
|
||||
sector: 512,
|
||||
ver2: true,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Cifra um volume em claro dentro de uma imagem LUKS, com cryptography (referência independente).
|
||||
uso: luks_encrypt.py IMG PLAIN VOLUME_KEY_FILE PAYLOAD_OFFSET CIPHER SECTOR_SIZE"""
|
||||
import sys, hashlib
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
|
||||
img, plain, vkf, off, cipher, ss = sys.argv[1:7]
|
||||
off = int(off); ss = int(ss)
|
||||
vk = open(vkf, 'rb').read()
|
||||
data = open(plain, 'rb').read()
|
||||
data += bytes((-len(data)) % ss)
|
||||
out = bytearray()
|
||||
for i in range(0, len(data), ss):
|
||||
sector512 = i // 512
|
||||
blk = data[i:i+ss]
|
||||
if 'xts' in cipher:
|
||||
tweak = sector512.to_bytes(16, 'little')
|
||||
e = Cipher(algorithms.AES(vk), modes.XTS(tweak)).encryptor()
|
||||
else:
|
||||
if 'essiv:sha256' in cipher:
|
||||
salt = hashlib.sha256(vk).digest()
|
||||
iv = Cipher(algorithms.AES(salt), modes.ECB()).encryptor().update(sector512.to_bytes(16, 'little'))
|
||||
else:
|
||||
iv = sector512.to_bytes(16, 'little')
|
||||
e = Cipher(algorithms.AES(vk), modes.CBC(iv)).encryptor()
|
||||
out += e.update(blk) + e.finalize()
|
||||
with open(img, 'r+b') as f:
|
||||
f.seek(off)
|
||||
f.write(out)
|
||||
Reference in new issue
Block a user