Skip to main content

libffx - FF1 Format-Preserving Encryption

PyPI version Tests Python 3.10+ License: MIT

A pure-Python implementation of NIST SP 800-38G FF1 format-preserving encryption (FPE), with AES-128/192/256.

Format-preserving encryption encrypts data while preserving its format: a 16-digit credit card number encrypts to another 16-digit number, and a DNA string over ACGT encrypts to another DNA string of the same length.

Installation

pip install libffx

The only dependency is cryptography (OpenSSL-backed AES). Python 3.10+.

Quick Start

from ffx import FF1

key = bytes.fromhex("2b7e151628aed2a6abf7158809cf4f3c")  # 16, 24, or 32 bytes

# Numeral strings (radix 2-36, or any custom alphabet)
cipher = FF1(key, radix=10)
ciphertext = cipher.encrypt("4111111111111111", tweak=b"account-42")
plaintext = cipher.decrypt(ciphertext, tweak=b"account-42")

# Custom alphabets (any unique Unicode characters, up to 65536)
dna = FF1(key, alphabet="ACGT")
encrypted = dna.encrypt("ACGTACGTACGT")

# Integers in an arbitrary domain [0, N)
plain = FF1(key)  # no alphabet needed for the integer API
y = plain.encrypt_int(123456789, domain=10**9)
x = plain.decrypt_int(y, domain=10**9)

API

FF1

FF1(key, *, radix=None, alphabet=None, allow_small_domain=False)
  • key: bytes (or bytearray) of exactly 16, 24, or 32 bytes (AES-128/192/256); other lengths raise KeyLengthError.
  • radix: 2-36; the alphabet is "0123456789abcdefghijklmnopqrstuvwxyz"[:radix].
  • alphabet: explicit alphabet string (2-65536 unique Unicode characters). At most one of radix/alphabet may be given. With neither, only the integer API is available.
  • allow_small_domain: relax the minimum domain from 1,000,000 (Draft SP 800-38G Rev 1) to 100 (original SP 800-38G).

Instances hold no per-call state and are safe to share between threads; construct one per key and reuse it.

encrypt / decrypt

encrypt(plaintext, *, tweak=b"") -> str
decrypt(ciphertext, *, tweak=b"") -> str

Encrypt or decrypt a numeral string. Output has the same length over the same alphabet. Input is case- and character-exact (no normalization); characters outside the alphabet raise AlphabetError. The message length n must satisfy n >= 2 and radix**n >= 1_000_000 (or >= 100 with allow_small_domain=True); otherwise DomainError.

Tweaks are arbitrary bytes (len < 2**32, else DomainError) acting as public associated data: the same plaintext under different tweaks yields unrelated ciphertexts.

encrypt_int / decrypt_int

encrypt_int(x, *, domain, tweak=b"") -> int
decrypt_int(y, *, domain, tweak=b"") -> int

Encrypt an integer 0 <= x < domain to another integer in the same range. domain must be at least 1,000,000 (100 with allow_small_domain=True); a smaller domain or an out-of-range value raises DomainError. Results depend only on (key, domain, tweak), not on the instance's radix/alphabet, so the construction is stable across instances and releases.

Errors

KeyLengthError, AlphabetError, and DomainError all derive from FFXError and from ValueError, so except FFXError catches every validation failure. Arguments of the wrong type (a non-bytes key or tweak, a non-str message, a non-int radix, domain, or value) raise TypeError.

Security notes

  • Deterministic: FF1 is a deterministic permutation. Equal plaintexts under the same key and tweak produce equal ciphertexts. Use tweaks (e.g. a record identifier) to prevent cross-record equality leakage.
  • Domain minimums: small domains are fundamentally weak for FPE. The default floor of 1,000,000 follows Draft SP 800-38G Rev 1; allow_small_domain=True opts into the original floor of 100; use it only when you understand the risk.
  • Always use cryptographically random keys (e.g. secrets.token_bytes(16)).

Migrating from v1

v2 is a rewrite with a new API. v1 implemented the FFX[radix] addendum profile, which is FF1 with the tweak taken as a numeral string instead of raw bytes. FF1 with the old tweak string encoded as ASCII bytes reproduces v1 FFX[radix] ciphertexts exactly, with the zero-tweak exception below.

# v1 encrypted "0123456789" under the tweak string "9876543210"; in v2:
FF1(key, radix=10).encrypt("0123456789", tweak=b"9876543210")  # '6124200773'

Behaviour changes to check before decrypting v1 data with v2:

  • Zero-valued tweaks. v1 treated any tweak whose numeric value was zero ("0", "0000000000", the integer 0, ...) as no tweak. Decrypt such records with tweak=b"" (the default), not with the zero string encoded as bytes.
  • Case. v1 accepted uppercase input for radix > 10 and rendered output in lowercase. v2 is case-exact and its radix-N alphabets are lowercase, so lowercase v1 ciphertexts before passing them to v2.
  • Minimum length. v1 enforced no minimum. v2 requires n >= 2 and radix**n >= 1_000_000 by default. Data with 100 <= radix**n < 1_000_000 needs allow_small_domain=True; data with radix**n < 100 (including every one-numeral message) cannot be decrypted by v2 at all and must be re-encrypted in a larger format with v1 before upgrading.
  • Python 3.9 is no longer supported.

API changes:

v1 v2
ffx.new(key, radix), FFXEncrypter FF1(key, radix=...)
enc.encrypt(tweak, FFXInteger) / enc.decrypt(tweak, FFXInteger) cipher.encrypt(str, tweak=bytes) / cipher.decrypt(str, tweak=bytes)
FFXInteger plain str for numeral strings; int with encrypt_int / decrypt_int
FFXException FFXError
InvalidRadixException AlphabetError
UnknownTypeException TypeError
long_to_bytes, bytes_to_long removed; use int.to_bytes / int.from_bytes
pycryptodome dependency cryptography

License

MIT License

Metadata

Release files for libffx 2.0.1

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

Source distribution (sdist)

Source distribution for libffx 2.0.1
File Size Uploaded
libffx-2.0.1.tar.gz 32.9 kB Details

Built distribution (wheel)

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

Total release size: 45.1 kB

Release files / libffx-2.0.1.tar.gz

Download URL libffx-2.0.1.tar.gz
Size 32.9 kB
Tags Source
SHA-256 checksum
How to use checksums
dcc5fcc78b2b80850e4f491ae32fb843f27554cae34b945c43807bfc0122dd0f
BLAKE2b-256 checksum
How to use checksums
b935b70ad025457aa2e97c2dbd98f755056aa08a6292fd6d467ffb9bbab601ec
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 / libffx-2.0.1-py3-none-any.whl

Download URL libffx-2.0.1-py3-none-any.whl
Size 12.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2406b50ed4f331fe8f759be59b439d132bdc263d8f69238972bf32ea94187ed8
BLAKE2b-256 checksum
How to use checksums
5da0ed96103a03f4b0d1973bf928ca7ff962ca5ef6c229dc2ed25a2819788423
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

2.0.1 This release

2 release files

2.0.0

2 release files

1.0.3

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

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