libfte
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.FTEmapsrank_in -> transform -> unrank_outover aninput_format/output_formatpair (the input defaults to raw bytes) and acipher:"aes-ctr-hmac"(randomized, authenticated) or a deterministic cipher object. FPE is the equal-formats case; classic FTE is the bytes-input case.fte.RegexFormatis the built-in provider; supply your ownRankedFormatfor 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)
| File | Size | Uploaded | |
|---|---|---|---|
| fte-0.4.0.tar.gz | 112.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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