{"id":51595167,"url":"https://github.com/securityronin/bitlocker-forensic","last_synced_at":"2026-07-11T18:01:39.296Z","repository":{"id":370750716,"uuid":"1294090444","full_name":"SecurityRonin/bitlocker-forensic","owner":"SecurityRonin","description":"BitLocker Drive Encryption (BDE) forensic library — parse FVE metadata, unlock via password, decrypt AES-CBC+Elephant-Diffuser volumes, and grade key-protector/clear-key/weak-cipher findings. Panic-free, validated byte-for-byte against libbde.","archived":false,"fork":false,"pushed_at":"2026-07-10T17:28:05.000Z","size":174,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-10T19:12:31.521Z","etag":null,"topics":["bitlocker","dfir","digital-forensics","encryption","forensics","incident-response","rust","windows"],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SecurityRonin.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-07-08T19:03:08.000Z","updated_at":"2026-07-10T17:28:57.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/SecurityRonin/bitlocker-forensic","commit_stats":null,"previous_names":["securityronin/bitlocker-forensic"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/SecurityRonin/bitlocker-forensic","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SecurityRonin%2Fbitlocker-forensic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SecurityRonin%2Fbitlocker-forensic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SecurityRonin%2Fbitlocker-forensic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SecurityRonin%2Fbitlocker-forensic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SecurityRonin","download_url":"https://codeload.github.com/SecurityRonin/bitlocker-forensic/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SecurityRonin%2Fbitlocker-forensic/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35370428,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-11T02:00:05.354Z","response_time":104,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bitlocker","dfir","digital-forensics","encryption","forensics","incident-response","rust","windows"],"created_at":"2026-07-11T18:01:36.029Z","updated_at":"2026-07-11T18:01:39.291Z","avatar_url":"https://github.com/SecurityRonin.png","language":"Rust","funding_links":["https://github.com/sponsors/h4x0r"],"categories":[],"sub_categories":[],"readme":"# bitlocker-forensic\n\n[![Crates.io: bitlocker-core](https://img.shields.io/crates/v/bitlocker-core.svg?label=bitlocker-core)](https://crates.io/crates/bitlocker-core)\n[![Crates.io: bitlocker-forensic](https://img.shields.io/crates/v/bitlocker-forensic.svg?label=bitlocker-forensic)](https://crates.io/crates/bitlocker-forensic)\n[![Docs.rs](https://img.shields.io/docsrs/bitlocker-core?label=docs.rs)](https://docs.rs/bitlocker-core)\n[![Rust 1.81+](https://img.shields.io/badge/rust-1.81%2B-blue.svg)](https://www.rust-lang.org)\n[![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)\n[![Sponsor](https://img.shields.io/badge/sponsor-h4x0r-ea4aaa?logo=githubsponsors)](https://github.com/sponsors/h4x0r)\n\n[![CI](https://github.com/SecurityRonin/bitlocker-forensic/actions/workflows/ci.yml/badge.svg)](https://github.com/SecurityRonin/bitlocker-forensic/actions/workflows/ci.yml)\n[![Coverage](https://img.shields.io/badge/coverage-100%25%20lines-brightgreen.svg)](docs/validation.md)\n[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance)\n[![Security advisories](https://img.shields.io/badge/advisories-clean-success.svg)](https://rustsec.org)\n\n**Unlock a BitLocker volume from its password and read the plaintext — a\nfrom-scratch, pure-Rust BitLocker (BDE) decryptor, validated byte-for-byte\nagainst `pybde` on real disk images.**\n\nNo `dislocker` C dependency, no FUSE, no mounting: one library that parses the\nFVE metadata, derives the keys from a password or recovery password, and\ndecrypts sectors — AES-CBC (± Elephant Diffuser) and AES-XTS, 128- and 256-bit.\n\n```rust,ignore\nuse std::fs::File;\nuse bitlocker::BitLockerVolume;\n\n// Unlock the dfvfs BitLocker To Go test image with its published password.\nlet mut vol = BitLockerVolume::unlock_with_password(File::open(\"bdetogo.raw\")?, \"bde-TEST\")?;\n\nlet mut boot = [0u8; 512];\nvol.read_at(0, \u0026mut boot)?;      // decrypted FAT boot sector\nassert_eq!(\u0026boot[3..11], b\"MSWIN4.1\");\n# Ok::\u003c(), Box\u003cdyn std::error::Error\u003e\u003e(())\n```\n\n## Scope\n\nThis build unlocks **four of the five** BitLocker unlock protectors — **password**\n(`0x2000`), **recovery-password** (`0x0800`), **clear-key** (`0x0000`, no\ncredential — a suspended volume), and **startup-key** (`0x0200`, a `.BEK` external\nkey) — and decrypts **five of the six** BitLocker ciphers, each validated against a\n`pybde` oracle:\n\n| Method | Cipher | Oracle (tier) |\n|---|---|---|\n| `0x8000` | AES-128-CBC + Elephant Diffuser | dfvfs `bdetogo.raw` (Tier-1) |\n| `0x8002` | AES-128-CBC | picoCTF 2025 `bitlocker-1.dd` (Tier-1) |\n| `0x8003` | AES-256-CBC | self-minted `m8003` (Tier-2) |\n| `0x8004` | XTS-AES-128 | BelkaCTF6 `vault` (Tier-1) + `m8004` (Tier-2) |\n| `0x8005` | XTS-AES-256 | self-minted `m8005` (Tier-2) |\n\n`BitLockerVolume::unlock_clear_key(reader)` unlocks a **clear-key** volume with no\ncredential (Tier-2, self-minted `clearkey` vs `pybde`), and\n`BitLockerVolume::unlock_with_startup_key(reader, bek_bytes)` unlocks a\n**startup-key** volume from its `.BEK` external-key file (Tier-2, self-minted\n`sk8004` vs `pybde` `read_startup_key`, cross-checked against the recovery\npassword). The dispatch decodes all six ciphers (`0x8000`–`0x8005`) into their axes\nand ships a decrypt for a cipher only once a real oracle validates it. The remaining\nmethod, AES-256-CBC + Elephant Diffuser (`0x8001`), is **recognized and refused with\na named error** — never decrypted by construction — so it lights up as a one-line\nchange plus a test the moment it gets an oracle. The TPM protector is out of scope\nfor *unlock*, but the metadata parser still **reports** every protector and cipher\nit finds. See [`docs/RESEARCH.md`](docs/RESEARCH.md).\n\n## The two-crate split\n\nFollowing the fleet reader/analyzer standard:\n\n| Crate | Role | Emits |\n|---|---|---|\n| **`bitlocker-core`** | reader / decryptor (`aes` · `cbc` · `ccm` · `xts-mode` · `sha2`) | plaintext `Read + Seek` view + typed FVE metadata |\n| **`bitlocker-forensic`** | anomaly analyzer over the metadata | graded `forensicnomicon::report` `Finding`s |\n\n### Analyzer findings\n\n| Code | Severity | Meaning |\n|---|---|---|\n| `BDE-CLEAR-KEY-PRESENT` | High | a clear-key protector (`0x0000`) is present ⇒ the volume is effectively unencrypted |\n| `BDE-PROTECTOR-INVENTORY` | Info | one per protector (password / recovery / TPM / startup key / …) |\n| `BDE-WEAK-CIPHER` | Low | AES-CBC ± diffuser is weaker than AES-XTS — consistent with an older OS |\n| `BDE-TO-GO` | Info | a BitLocker To Go removable-media volume |\n\nFindings are **observations, never verdicts** — the examiner draws conclusions.\n\n## Trust but verify\n\n- **Every primitive is an audited RustCrypto crate** (`aes`, `cbc`, `ccm`,\n  `sha2`). The only hand-written cryptographic routine is the **Elephant\n  Diffuser** — no crate exists for it — implemented to the `libbde` reference and\n  validated **only** against the independent `pybde` oracle on the real\n  `bdetogo.raw` image, never a self-authored round-trip.\n- **Panic-free, bounds-checked** parsing of untrusted volumes; `unwrap`/`expect`\n  denied in production code (`#![forbid(unsafe_code)]`); the metadata parser is\n  fuzzed.\n- **Tier-1 validated**: decrypted sectors match `pybde` byte-for-byte — see\n  [`docs/validation.md`](docs/validation.md).\n\n[Privacy Policy](https://securityronin.github.io/bitlocker-forensic/privacy/) · [Terms of Service](https://securityronin.github.io/bitlocker-forensic/terms/) · © 2026 Security Ronin Ltd\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsecurityronin%2Fbitlocker-forensic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsecurityronin%2Fbitlocker-forensic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsecurityronin%2Fbitlocker-forensic/lists"}