Implementation
Rust application
FTExfil is implemented as a Rust command-line application. Client mode transmits a file using DNS queries, server mode receives queries and reconstructs completed transfers, and print mode produces encoded output without performing the transport.
The application was tested on Linux, macOS, and Android through Termux. Platform-specific binaries are required. It uses established crates for its cryptographic primitives, public-key operations, randomness, and encoding.
The software is divided between two repositories:
- ftexfil: application orchestration, DNS transport, chunk metadata, and receiver state.
- fte_rs: the FTE encryption and representation layer, plus weighted DNS Huffman utilities.
Application modules
| Directory | Responsibility |
|---|---|
src/app | Print, client, and server runtime orchestration. |
src/cli | Argument parsing and command dispatch. |
src/codec | Regex, weighted, and Base32 encoding; DNS-name shaping. |
src/crypto | Key exchange and session-key derivation. |
src/dns | DNS wire handling, packet parsing, and client transport. |
src/transfer | Chunking, metadata, packetization, reassembly, and receiver state. |
src/io | File reading and writing. |
src/errors | Shared error representation. |
This organization separates representation from transport. The encoder can be changed without replacing the metadata and reassembly logic, and the same transport can be used to compare the available encodings.
Command-line interface
Client mode
ftexfil client <file_path> <size> <domain_suffix> --server <ip:port>
[--encoding <regex|weighted|base32>] [--qtype <A|TXT>]
[--timeout-ms <ms>] [--retries <n>]
[--retry-delay-ms <ms>] [--delay-ms <ms>]
[--kex <none|x25519|p256>] [--resolver-safe]
| Argument | Purpose |
|---|---|
<file_path> | Input file. |
<size> | Total plaintext chunk size, including the 16-byte metadata section. |
<domain_suffix> | Controlled domain used to construct query names. |
--server | DNS server or resolver address and port. |
--encoding | Selects regex/FTE, weighted Huffman, or the Base32 baseline. |
--qtype | Selects A or TXT data queries. |
--kex | Selects the fixed development key or an ECDH curve. |
--timeout-ms | Wait limit for a query response. |
--retries | Retry count. |
--retry-delay-ms | Delay between retries. |
--delay-ms | Delay between queries. |
--resolver-safe | Adds retry handling for NXDOMAIN responses on recursive paths. |
The input size must exceed the metadata length and remain compatible with the selected representation and domain length. A chunk size valid for Base32 is not necessarily valid for the weighted encoder.
Server mode
ftexfil server --listen <ip:port> --domain <domain_suffix> --out-dir <dir>
[--encoding <regex|weighted|base32>] [--response-ip <ipv4>]
[--session-ttl-sec <n>] [--kex <none|x25519|p256>]
| Argument | Purpose |
|---|---|
--listen | Address and port for the UDP DNS listener. |
--domain | Domain accepted as transfer traffic. |
--out-dir | Destination directory for reconstructed files. |
--encoding | Representation expected in incoming names. |
--response-ip | IPv4 address used in generated responses. |
--session-ttl-sec | Lifetime of incomplete receiver sessions. |
--kex | Enables the selected key-exchange mode. |
Print mode
ftexfil print <file_path> <size> <domain_suffix>
[--encoding <regex|weighted|base32>]
Print mode exposes the names generated from file chunks without sending them through DNS. It is useful when examining output shape, size, and the effect of the selected encoding.
FTE library
fte_rs reimplements the original Python libfte functionality in Rust. Its purpose is to provide a native encryption and encoding layer rather than interface with the Python library.
| File | Responsibility |
|---|---|
src/regex2dfa.rs | Regex parsing and the regex-to-DFA/FST pipeline. |
src/dfa.rs | FST loading, accepted-word counts, and rank/unrank. |
src/encrypter.rs | AES-CTR and HMAC envelope. |
src/encoder.rs | High-level FTE encoding and decoding. |
src/conf.rs | Runtime key configuration. |
src/weighted_dns.rs | Feature-gated weighted DNS Huffman utilities. |
The application enables the library’s weighted-dns feature and uses it as a local sibling dependency. The FTE translation was partly AI-assisted and subsequently reviewed and modified. The work does not introduce a new FTE cryptographic construction; the experimental encoding contribution is the weighted Huffman transformation.
Regex encoding
An anchored regular expression is compiled into a DFA. For the chosen fixed_slice length, the library computes accepted-suffix counts and obtains a deterministic mapping between integer values and valid strings.
During encoding, plaintext is protected as W1 || W2 || T. Bytes from the ciphertext are packed into an integer, and unranking produces the fixed-length regex-conformant string. Decoding ranks that string back into bytes and reconstructs the ciphertext for authentication and decryption.
The chosen format determines the available language capacity. Capacity checks prevent an input chunk from exceeding the representation’s supported size.
Weighted encoding
The weighted encoder constructs a codebook for a–z from frequency tables such as ENGLISH_FREQ and DNS_FREQ. Codeword lengths depend on the weights in the selected model.
During encoding, encrypted bits traverse the Huffman tree. Reaching a leaf emits its character and restarts traversal at the root. During decoding, each character is replaced by its codeword, and the recovered bits are assembled into ciphertext bytes.
The library estimates plaintext capacity from the expected bits represented per output character. Because the output is variable length, this is an approximation rather than a worst-case size bound.
Client-side processing
The sender reads the file into bytes and splits it into payload fragments. It appends metadata, encrypts and authenticates each complete chunk, and transforms the result into a name under the configured domain.
One request is generated per encoded chunk. The client applies the selected timeout and retry policy, records delivery outcomes, and can insert a delay between requests. A random nonce or initial value causes encrypted output to differ between runs even for the same input file.
Server-side processing
The UDP receiver parses incoming DNS packets, filters names by the accepted domain, and reverses the selected representation. Authentication and decryption precede metadata processing.
After validating the metadata, the server selects a transfer buffer using file_id and places the payload using chunk_index. Duplicate indexes are counted without being appended again. Completion is detected when the number of expected fragment positions has been satisfied, then the file is reconstructed in index order.
Development checks
The library exposes ordinary Rust test execution and a feature-enabled test mode:
cargo test
cargo test --features weighted-dns
The FTE library also includes a deterministic cryptographic vector for cross-checking encryption behaviour. Together with the module separation, these support development of the individual encoding and protection layers independently of DNS transport.