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

DirectoryResponsibility
src/appPrint, client, and server runtime orchestration.
src/cliArgument parsing and command dispatch.
src/codecRegex, weighted, and Base32 encoding; DNS-name shaping.
src/cryptoKey exchange and session-key derivation.
src/dnsDNS wire handling, packet parsing, and client transport.
src/transferChunking, metadata, packetization, reassembly, and receiver state.
src/ioFile reading and writing.
src/errorsShared 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]
ArgumentPurpose
<file_path>Input file.
<size>Total plaintext chunk size, including the 16-byte metadata section.
<domain_suffix>Controlled domain used to construct query names.
--serverDNS server or resolver address and port.
--encodingSelects regex/FTE, weighted Huffman, or the Base32 baseline.
--qtypeSelects A or TXT data queries.
--kexSelects the fixed development key or an ECDH curve.
--timeout-msWait limit for a query response.
--retriesRetry count.
--retry-delay-msDelay between retries.
--delay-msDelay between queries.
--resolver-safeAdds 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>]
ArgumentPurpose
--listenAddress and port for the UDP DNS listener.
--domainDomain accepted as transfer traffic.
--out-dirDestination directory for reconstructed files.
--encodingRepresentation expected in incoming names.
--response-ipIPv4 address used in generated responses.
--session-ttl-secLifetime of incomplete receiver sessions.
--kexEnables the selected key-exchange 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.

FileResponsibility
src/regex2dfa.rsRegex parsing and the regex-to-DFA/FST pipeline.
src/dfa.rsFST loading, accepted-word counts, and rank/unrank.
src/encrypter.rsAES-CTR and HMAC envelope.
src/encoder.rsHigh-level FTE encoding and decoding.
src/conf.rsRuntime key configuration.
src/weighted_dns.rsFeature-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.