Oracle Examples

Ready-to-copy templates demonstrating the two oracle shapes documented in Oracle interface. Save these under your project’s oracles/ directory, edit the marked constants, and point the MemDiver brute-force / n-sweep runners at them.

generic_aes_gcm.py — stateless AES-GCM

 1"""Stateless AES-GCM oracle boilerplate.
 2
 3Copy this file, edit the three module-level constants, point
 4``memdiver brute-force --oracle generic_aes_gcm.py`` at it.
 5
 6The verification strategy: AEAD-decrypt a known ciphertext with the
 7candidate as the key. If the tag verifies, we have the right key.
 8Wrong-length candidates are rejected in O(1).
 9"""
10
11from cryptography.hazmat.primitives.ciphers.aead import AESGCM
12
13KEY_LENGTH = 32
14NONCE = bytes.fromhex("00" * 12)
15CIPHERTEXT_WITH_TAG = bytes.fromhex(
16    "aabbccddeeff00112233445566778899"
17    "aabbccddeeff0011"
18)
19ASSOCIATED_DATA: bytes | None = None
20
21
22def verify(candidate: bytes) -> bool:
23    if len(candidate) != KEY_LENGTH:
24        return False
25    try:
26        AESGCM(candidate).decrypt(NONCE, CIPHERTEXT_WITH_TAG, ASSOCIATED_DATA)
27        return True
28    except Exception:
29        return False

gocryptfs.py — stateful factory, HKDF-SHA256 + per-block AEAD

 1"""Gocryptfs master-key oracle.
 2
 3A candidate is treated as the 32-byte **unwrapped** master key (already
 4resident in process memory — scrypt is irrelevant on the hot path).
 5Verification derives the per-instance content key and AEAD-decrypts the
 6first block of a real ciphertext file from the vault.
 7
 8gocryptfs v2 on-disk format (per file):
 9
10    file_header = version(2 big-endian) || file_id(16)
11    block_0     = nonce(16) || ciphertext(4096) || gcm_tag(16)
12    block_1     = nonce(16) || ciphertext(4096) || gcm_tag(16)
13    ...
14
15Content key:
16
17    content_key = HKDF-SHA256(master_key,
18                              info="AES-GCM file content encryption",
19                              length=32)
20
21Per-block AEAD:
22
23    AES-256-GCM( key    = content_key,
24                 nonce  = block.nonce,
25                 ct+tag = block.ciphertext || block.gcm_tag,
26                 aad    = block_num (8 big-endian) || file_id (16) )
27
28A successful tag verify on block 0 ⇒ this candidate is the real master
29key. Wrong candidates fail with ``InvalidTag``; the ``except`` below
30returns False. ~150 µs per candidate on CPython, no filesystem mount.
31
32Requires ``pip install cryptography``.
33
34Driven by a TOML config passed via ``--oracle-config``:
35
36    # gocryptfs_oracle.toml
37    sample_ciphertext = "/path/to/vault/<encrypted_file>"
38
39The ``sample_ciphertext`` path must point at any file encrypted by the
40target gocryptfs instance. Its header_id is read directly from the
41first 18 bytes.
42"""
43
44from pathlib import Path
45
46from cryptography.hazmat.primitives import hashes
47from cryptography.hazmat.primitives.ciphers.aead import AESGCM
48from cryptography.hazmat.primitives.kdf.hkdf import HKDF
49
50MASTER_KEY_LENGTH = 32
51HEADER_VERSION_LENGTH = 2
52HEADER_ID_LENGTH = 16
53NONCE_LENGTH = 16
54GCM_TAG_LENGTH = 16
55CONTENT_KEY_INFO = b"AES-GCM file content encryption"
56
57
58def build_oracle(config: dict) -> "GocryptfsOracle":
59    return GocryptfsOracle(config)
60
61
62class GocryptfsOracle:
63    def __init__(self, config: dict):
64        sample = Path(config["sample_ciphertext"]).read_bytes()
65        min_size = (
66            HEADER_VERSION_LENGTH + HEADER_ID_LENGTH
67            + NONCE_LENGTH + GCM_TAG_LENGTH
68        )
69        if len(sample) < min_size:
70            raise ValueError(
71                f"sample ciphertext too short ({len(sample)} < {min_size})"
72            )
73        self.header_id = sample[
74            HEADER_VERSION_LENGTH : HEADER_VERSION_LENGTH + HEADER_ID_LENGTH
75        ]
76        body = sample[HEADER_VERSION_LENGTH + HEADER_ID_LENGTH :]
77        self.nonce = body[:NONCE_LENGTH]
78        self.ct_and_tag = body[NONCE_LENGTH:]
79        # Block 0 AAD = block_num(8 BE) || file_header_id(16)
80        self.aad = (0).to_bytes(8, "big") + self.header_id
81
82    def verify(self, candidate: bytes) -> bool:
83        if len(candidate) != MASTER_KEY_LENGTH:
84            return False
85        try:
86            content_key = HKDF(
87                algorithm=hashes.SHA256(),
88                length=MASTER_KEY_LENGTH,
89                salt=None,
90                info=CONTENT_KEY_INFO,
91            ).derive(candidate)
92            AESGCM(content_key).decrypt(self.nonce, self.ct_and_tag, self.aad)
93            return True
94        except Exception:
95            return False

tls13_stub.py — TLS 1.3 traffic-secret verification scaffold

 1"""TLS 1.3 traffic-secret oracle — fill in the key schedule.
 2
 3Treats each candidate as a 32- or 48-byte traffic secret (SHA-256 or
 4SHA-384 cipher suite), re-derives the record protection keys via
 5HKDF-Expand-Label, and attempts to AEAD-decrypt one captured TLS
 6record. Returns True on a successful tag verify.
 7
 8This is a scaffold — the ``_derive_keys`` body is a TODO. Point it at
 9RFC 8446 §7.1 once you know the AEAD your target uses.
10
11Driven by TOML:
12
13    # tls13_oracle.toml
14    encrypted_record   = "/path/to/record0.bin"   # full TLS record body
15    record_nonce_hex   = "001122334455"           # explicit nonce bytes
16    aead               = "AES-128-GCM"            # or "AES-256-GCM"
17    hash_algo          = "SHA-256"                # or "SHA-384"
18"""
19
20from pathlib import Path
21
22from cryptography.hazmat.primitives.ciphers.aead import AESGCM
23
24SHA256_LENGTH = 32
25SHA384_LENGTH = 48
26
27
28def build_oracle(config: dict) -> "Tls13Oracle":
29    return Tls13Oracle(config)
30
31
32class Tls13Oracle:
33    def __init__(self, config: dict):
34        self.record = Path(config["encrypted_record"]).read_bytes()
35        self.nonce = bytes.fromhex(config["record_nonce_hex"])
36        self.aead_name = config.get("aead", "AES-128-GCM")
37        self.hash_name = config.get("hash_algo", "SHA-256")
38        self.expected_length = (
39            SHA256_LENGTH if self.hash_name == "SHA-256" else SHA384_LENGTH
40        )
41
42    def _derive_keys(self, traffic_secret: bytes) -> bytes:
43        """Return the AEAD key derived from a TLS 1.3 traffic secret.
44
45        TODO: implement per RFC 8446 §7.1 using HKDF-Expand-Label with
46        the label "tls13 key". Return the AEAD key bytes (16 for
47        AES-128-GCM, 32 for AES-256-GCM).
48        """
49        raise NotImplementedError("fill in TLS 1.3 HKDF-Expand-Label here")
50
51    def verify(self, candidate: bytes) -> bool:
52        if len(candidate) != self.expected_length:
53            return False
54        try:
55            key = self._derive_keys(candidate)
56            AESGCM(key).decrypt(self.nonce, self.record, None)
57            return True
58        except Exception:
59            return False