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

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fte-0.4.0.tar.gz (112.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fte-0.4.0-py3-none-any.whl (36.6 kB view details)

Uploaded Python 3

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

Hashes for fte-0.4.0.tar.gz
Algorithm Hash digest
SHA256 b3a792cc2b16362c15c462e5aa8e3796448da297f099a9c6516391f6b62d60eb
MD5 47c167903a39b2f8cdb64e69e7213c10
BLAKE2b-256 71b6a82229bde8cdaec2e59455b5afebb85686af329e7cd567976889d685ea76

See more details on using hashes here.

Provenance

The following attestation bundles were made for fte-0.4.0.tar.gz:

Publisher: publish.yml on kpdyer/libfte

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for fte-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a530b4f3f787c55fa127174abb4e3a26a60b22c7a4de747e6b5552da595cbc03
MD5 b85a3b583cb9b1a3211ad4622af0592c
BLAKE2b-256 0acbfeb62308ce4eb6f78863a331c7a5358bf31a7c409a895809b0617b62ccec

See more details on using hashes here.

Provenance

The following attestation bundles were made for fte-0.4.0-py3-none-any.whl:

Publisher: publish.yml on kpdyer/libfte

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 files

0.3.0

2 files

0.2.1

2 files

0.1.3

1 file

0.1.2

1 file

0.1.1

1 file

0.1.0

1 file

0.0.5

1 file

0.0.4

1 file

0.0.3

1 file

0.0.2

1 file

0.0.1

1 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