Skip to main content

libfte

PyPI version Tests Python 3.10+ License: MIT

Format-Transforming Encryption: encrypt data so the ciphertext matches any format you specify.

What is FTE?

Unlike standard encryption that produces random-looking output, FTE produces ciphertext that looks like whatever format you specify (via a regular expression, or any RankedFormat provider you supply), so it can look like hex strings, alphanumeric tokens, any language a regex can denote, or a custom format of your own.

One engine, two axes. fte.FTE maps rank_in -> transform -> unrank_out over an input_format / output_format pair (the input defaults to raw bytes) and a cipher: "aes-ctr-hmac" (randomized, authenticated) or a deterministic cipher object. FPE is the equal-formats case; classic FTE is the bytes-input case. fte.RegexFormat is the built-in provider; supply your own RankedFormat for any other language. The wire format changed in 0.4.0 and is not compatible with libfte 0.3.x and earlier.

Installation

pip install fte

libfte itself is pure Python. It depends on cryptography (AES-CTR on OpenSSL; it ships prebuilt wheels with OpenSSL bundled), regex2dfa (pure Python) and libffx (pure Python; the FF1 format-preserving cipher), so no compiler or system library is needed on the platforms cryptography publishes wheels for.

Quick Example

Encrypt a secret so the ciphertext looks like words:

import os
import fte

key = os.urandom(32)  # 32 bytes, shared by both endpoints

# Pick a covertext format, then build a cipher over it and the key.
# 73 characters of words hold up to 15 plaintext bytes (cipher.max_plaintext_bytes).
word_format = fte.RegexFormat(r'^([a-z]+ )+[a-z]+$', length=73)
cipher = fte.FTE(output_format=word_format, key=key)

ciphertext = cipher.encrypt(b'Attack at dawn')
print(ciphertext.decode())
# One real run; the exact text varies per call, because the cipher is randomized:
# aa migbcjfbkvhczkjjwogvkpr m hnczwlthnujcutvnxqtrfhfnvnjhowaax mg nazfkrf

plaintext = cipher.decrypt(ciphertext)
# → b'Attack at dawn'

The covertext is a string of the chosen format that carries your encrypted message. Because the format holds one byte more than the message needs, the covertext can begin with a short run of the format's lowest-ranked symbols (a and space); a much larger length would make that run long.

RegexFormat also takes a min_length/max_length range for variable-length covertext; a fixed length is the special case where they are equal.

Format-preserving and deterministic FTE. cipher="ff1" (NIST SP 800-38G FF1 via libffx) is deterministic and zero-expansion: pass the same format as input_format and output_format to re-encrypt a value in place (FPE, length preserved), or two different formats for a deterministic rank map between them. It refuses an input domain below one million values, is unauthenticated, and leaks plaintext equality, so pass per-record tweak values and never reuse a key across the two ciphers.

Ranked-Format Providers

FTE accepts any object implementing the structural RankedFormat protocol: reversible rank() and unrank() methods. Providers need no inheritance, registration, or runtime dependency on libfte:

import secrets

import fte


class DecimalText:
    def rank(self, value: str, /) -> int:
        if not value.isascii() or not value.isdigit():
            raise ValueError("not canonical decimal text")
        if value != "0" and value.startswith("0"):
            raise ValueError("not canonical decimal text")
        return int(value)

    def unrank(self, index: int, /) -> str:
        if type(index) is not int or index < 0:
            raise ValueError("invalid rank")
        return str(index)

shared_32_byte_key = secrets.token_bytes(32)
cipher = fte.FTE(output_format=DecimalText(), key=shared_32_byte_key)
covertext: str = cipher.encrypt(b"secret")
assert cipher.decrypt(covertext) == b"secret"

The key and exact ranked-format ordering must match at both endpoints. Generic FTE framing exposes plaintext length through the rank and guarantees membership in the format's language, not a uniform distribution over unused format capacity: a fixed-length covertext much larger than the message begins with a run of the format's lowest-ranked symbols.

Use Cases

  • Protocol obfuscation: Make encrypted traffic look like benign data
  • Bypassing filters: Evade systems that block encrypted-looking content
  • Constrained fields: Confine ciphertext to a required character set or field shape, such as an alphanumeric account token or a fixed-width record field

Documentation

Full docs and examples: github.com/kpdyer/libfte

Reference

Based on Protocol Misidentification Made Easy with Format-Transforming Encryption (ACM CCS 2013) and LibFTE: A Toolkit for Constructing Practical, Format-Abiding Encryption Schemes (USENIX Security 2014).

License

MIT

Release files for fte 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fte 0.4.0
File Size Uploaded
fte-0.4.0.tar.gz 112.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for fte 0.4.0
File Interpreter ABI Platform
fte-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 148.9 kB

Release files / fte-0.4.0.tar.gz

Download URL fte-0.4.0.tar.gz
Size 112.3 kB
Tags Source
SHA-256 checksum
How to use checksums
b3a792cc2b16362c15c462e5aa8e3796448da297f099a9c6516391f6b62d60eb
BLAKE2b-256 checksum
How to use checksums
71b6a82229bde8cdaec2e59455b5afebb85686af329e7cd567976889d685ea76
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.

Transparency log

Release files / fte-0.4.0-py3-none-any.whl

Download URL fte-0.4.0-py3-none-any.whl
Size 36.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a530b4f3f787c55fa127174abb4e3a26a60b22c7a4de747e6b5552da595cbc03
BLAKE2b-256 checksum
How to use checksums
0acbfeb62308ce4eb6f78863a331c7a5358bf31a7c409a895809b0617b62ccec
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.1.3

1 release file

0.1.2

1 release file

0.1.1

1 release file

0.1.0

1 release file

0.0.5

1 release file

0.0.4

1 release file

0.0.3

1 release file

0.0.2

1 release file

0.0.1

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page