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
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fte-0.4.0.tar.gz.
File metadata
- Download URL: fte-0.4.0.tar.gz
- Upload date:
- Size: 112.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b3a792cc2b16362c15c462e5aa8e3796448da297f099a9c6516391f6b62d60eb
|
|
| MD5 |
47c167903a39b2f8cdb64e69e7213c10
|
|
| BLAKE2b-256 |
71b6a82229bde8cdaec2e59455b5afebb85686af329e7cd567976889d685ea76
|
Provenance
The following attestation bundles were made for fte-0.4.0.tar.gz:
Publisher:
publish.yml on kpdyer/libfte
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fte-0.4.0.tar.gz -
Subject digest:
b3a792cc2b16362c15c462e5aa8e3796448da297f099a9c6516391f6b62d60eb - Sigstore transparency entry: 2687430622
- Sigstore integration time:
-
Permalink:
kpdyer/libfte@09fc40c77fd2bbd080411fab6d0b45dac2718fac -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/kpdyer
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@09fc40c77fd2bbd080411fab6d0b45dac2718fac -
Trigger Event:
release
-
Statement type:
File details
Details for the file fte-0.4.0-py3-none-any.whl.
File metadata
- Download URL: fte-0.4.0-py3-none-any.whl
- Upload date:
- Size: 36.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a530b4f3f787c55fa127174abb4e3a26a60b22c7a4de747e6b5552da595cbc03
|
|
| MD5 |
b85a3b583cb9b1a3211ad4622af0592c
|
|
| BLAKE2b-256 |
0acbfeb62308ce4eb6f78863a331c7a5358bf31a7c409a895809b0617b62ccec
|
Provenance
The following attestation bundles were made for fte-0.4.0-py3-none-any.whl:
Publisher:
publish.yml on kpdyer/libfte
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fte-0.4.0-py3-none-any.whl -
Subject digest:
a530b4f3f787c55fa127174abb4e3a26a60b22c7a4de747e6b5552da595cbc03 - Sigstore transparency entry: 2687430722
- Sigstore integration time:
-
Permalink:
kpdyer/libfte@09fc40c77fd2bbd080411fab6d0b45dac2718fac -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/kpdyer
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@09fc40c77fd2bbd080411fab6d0b45dac2718fac -
Trigger Event:
release
-
Statement type: