# ewf-image
Read, verify, and write Expert Witness Format (EWF) forensic images in pure Rust.
[](https://crates.io/crates/ewf-image)
[](https://docs.rs/ewf-image)
[](https://github.com/ebrig/ewf-image/actions/workflows/ci.yml)
[](LICENSE)

`ewf-image` opens EnCase-style `.E01`, `.L01`, `.S01`, `.Ex01`, and `.Lx01`
images and exposes their decoded media as ordinary Rust readers. The library
forbids unsafe code and runs without libewf or any other external tool.
- **Read** physical, logical, and SMART images, including split segment sets.
- **Verify** decoded media against stored and independently recorded MD5, SHA1,
and SHA256 digests.
- **Browse** logical file catalogs, then verify and extract individual files.
- **Write** EWF1 and EWF2 images, with resumable E01 acquisition and bounded
streaming writers.
- **Analyze** damaged images and recover readable data with a provenance map.
The examples below target version 0.5.0. See the
[changelog](CHANGELOG.md#050) for its changes and the
[migration guide](docs/migrating-to-0.5.md) for upgrades from 0.4.
This workspace also includes the experimental
[`aff4-image`](https://github.com/ebrig/ewf-image/tree/main/crates/aff4-image)
crate for reading, verifying, and writing supported AFF4 containers, plus
`ewf-cli` for EWF, AFF4, and raw images.
## Installation
```toml
[dependencies]
ewf-image = "0.5"
```
The crate requires Rust 1.96 or later. Its runtime features are:
| `verify` | Media verification, per-file verification, and integrity analysis. Enabled by default. |
| `parallel` | Verification across multiple worker threads. Enables `verify`. |
| `serde` | Serialization of reports and metadata types. |
Stored-hash parsing, section integrity checks, and writer hashing remain available
with `default-features = false`.
## Read an image
Open the first segment. The remaining segments are found automatically.
```rust,no_run
use std::io::Read;
fn main() -> ewf_image::Result<()> {
let image = ewf_image::Image::open("case.E01")?;
let info = image.info();
println!("{:?}, {} bytes in {} segments", info.format, info.logical_size, info.segment_count);
// Read sequentially through a Read + Seek cursor.
let mut first_sector = [0u8; 512];
image.cursor().read_exact(&mut first_sector)?;
// Or read at an absolute offset without a cursor.
let mut buffer = [0u8; 4096];
image.read_at(&mut buffer, 1024 * 1024)?;
Ok(())
}
```
`Image` is a cheap, shareable handle. Clones and cursors share bounded caches,
so one image can serve many readers. `OpenOptions` adjusts cache sizes, handle
limits, and strictness. Segment files must remain unchanged while an image is open.
## Verify an image
`verify` decodes the complete media and compares it with the digests stored in
the image. Verification bypasses caches, so corrupt data cannot pass as valid.
```rust,no_run
use ewf_image::{Image, VerifyOptions};
fn main() -> ewf_image::Result<()> {
let image = Image::open("case.E01")?;
let result = image.verify()?;
println!("MD5 match: {:?}", result.md5_match);
println!("SHA256 match: {:?}", result.sha256_match);
// Compare against a digest recorded outside the image.
let acquisition_sha256 = [0u8; 32]; // Replace with the recorded value.
let options = VerifyOptions::default().with_expected_sha256(acquisition_sha256);
let report = image.verify_with_options(&options)?;
println!("references match: {:?}", report.references_match());
Ok(())
}
```
A match value of `None` means the image stores no digest of that type. A match
shows that the decoded media equals what was hashed at acquisition. It cannot
show whether unreadable source sectors were replaced with zeros at that time. See
[verification, analysis, and recovery](docs/reader-analysis.md).
## Work with logical files
Logical images (`.L01` and `.Lx01`) contain a catalog of files and folders.
```rust,no_run
use ewf_image::{Image, SingleFileEntryType};
use std::{fs::File, io};
fn main() -> ewf_image::Result<()> {
let image = Image::open("files.L01")?;
let Some(root) = image.root_file_entry() else {
println!("not a logical image");
return Ok(());
};
for entry in &root.children {
println!("{} ({} bytes)", entry.name().unwrap_or("?"), entry.size().unwrap_or(0));
}
let first_file = root
.children
.iter()
.find(|entry| entry.entry_type() == Some(SingleFileEntryType::File));
if let Some(entry) = first_file {
let check = image.verify_single_file(entry)?;
println!("stored file hashes match: {:?}", check.references_match());
let mut output = File::create_new("extracted.bin")?;
io::copy(&mut image.single_file_cursor(entry), &mut output)?;
}
Ok(())
}
```
Extraction copies file content only. Timestamps and other recorded metadata
remain available on each catalog entry but are not applied to extracted files.
The `ewf-cli extract --restore-times` option applies recorded file access and
modification times to a selected new output file.
## Write an image
`EwfWriter` creates EWF1 or EWF2 images from any readable source.
```rust,no_run
use ewf_image::{EwfWriter, WriteCompression, WriteFormat, WriteOptions};
use std::{fs::File, io};
fn main() -> ewf_image::Result<()> {
let mut options = WriteOptions {
format: WriteFormat::Ewf2Physical,
compression: WriteCompression::Zlib,
..WriteOptions::default()
};
options.metadata.set_header_value("case_number", "CASE-001");
let mut writer = EwfWriter::create("case.Ex01", options)?;
io::copy(&mut File::open("disk.raw")?, &mut writer)?;
let result = writer.finish()?;
println!("wrote {} segments", result.segment_paths.len());
Ok(())
}
```
`EwfWriter` supports every output format and positioned writes, but it spools
the complete source to temporary storage. Specialized writers cover large or
long-running jobs:
- `AcquisitionWriter` acquires physical E01 images with checkpoints and can
resume after an interruption.
- `SequentialWriter` streams known-length E01, Ex01, and Lx01 output and stages
one segment at a time. The CLI uses it for E01 conversion.
- `LogicalWriter` builds L01 and Lx01 file catalogs from files you supply.
Writers return computed MD5, SHA1, and SHA256 digests but do not reread their
output. [Acquisition and writing](docs/acquisition.md) explains resume,
publication, and recovery for each writer.
## Supported formats
| EWF1 physical `.E01` | Raw and zlib; X-Ways Zstandard; X-Ways AES-128/AES-256 encryption | Raw and zlib |
| EWF1 logical `.L01` | Media and file catalog | Media and file catalog |
| EWF1 SMART `.S01` | Media | Media |
| EWF2 physical `.Ex01` | Raw, zlib, BZip2, and pattern-fill | Raw, zlib, BZip2, and pattern-fill |
| EWF2 logical `.Lx01` | Media and file catalog | Media and file catalog |
Encrypted X-Ways images open with `Image::open_with_password`. Encrypted EWF2
images, encrypted output, and delta (overlay) images are not supported.
[Compatibility](docs/compatibility.md) describes tested producers and consumers,
and [limitations](docs/limitations.md) lists unsupported workflows.
For supported AFF4 profiles and limitations, see
[`aff4-image`](https://github.com/ebrig/ewf-image/tree/main/crates/aff4-image).
`ewf-image` has no AFF4 dependencies.
## Command-line tool
The workspace includes `ewf-cli`, an unpublished command-line tool for EWF, AFF4,
and raw images. Build it from this repository:
```sh
cargo build -p ewf-cli --release --locked
```
```text
ewf-cli info case.E01
ewf-cli verify case.E01
ewf-cli acquire /dev/sdb case.E01
ewf-cli convert case.E01 case.aff4
ewf-cli collect evidence-folder files.Lx01
ewf-cli extract files.Lx01 2 recovered.bin
```
The output file extension selects the format. Commands never overwrite existing
files. Acquisition requires a stable source, and directory collection does not
create a filesystem snapshot. See the [CLI guide](docs/ewf-cli.md) for device
acquisition, conversion, JSON output, and exit codes, and the [EWF command
guide](docs/cli.md) for advanced acquisition and recovery options.
## Documentation
- [API reference](https://docs.rs/ewf-image) and [runnable examples](examples)
- [Verification, analysis, and recovery](docs/reader-analysis.md)
- [Acquisition and writing](docs/acquisition.md)
- [CLI guide](docs/ewf-cli.md) and [EWF command guide](docs/cli.md)
- [Compatibility](docs/compatibility.md) and [limitations](docs/limitations.md)
- [Architecture](docs/architecture.md)
- [Testing](docs/testing.md)
- [Release process](RELEASING.md)
- Upgrading: [0.5 guide](docs/migrating-to-0.5.md),
[0.4 release notes](CHANGELOG.md#040---2026-09-12),
[0.3 guide](docs/migrating-to-0.3.md), [0.2 guide](docs/migrating-to-0.2.md)
## Contributing and security
Read [Contributing](CONTRIBUTING.md) before opening a pull request. Report
vulnerabilities privately as described in the [security policy](SECURITY.md).
## License
Licensed under the [Apache License 2.0](LICENSE).