libffx - FF1 Format-Preserving Encryption
A pure-Python implementation of NIST SP 800-38G FF1 format-preserving
encryption (FPE), with AES-128/192/256. The only dependency is
cryptography (OpenSSL-backed AES).
Format-preserving encryption encrypts data while preserving its format: a 16-digit credit card number encrypts to another 16-digit number.
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
dna = FF1(key, alphabet="ACGT")
encrypted = dna.encrypt("ACGTACGTACGT")
# Integers in an arbitrary domain [0, N)
y = cipher.encrypt_int(123456789, domain=10**9)
x = cipher.decrypt_int(y, domain=10**9)
API
FF1(key, *, radix=None, alphabet=None, allow_small_domain=False): key must be 16/24/32 bytes; passradix(2-36) or an explicitalphabet(2-65536 unique Unicode characters), or neither for integer-only use.allow_small_domain=Truelowers the minimum domain from 1,000,000 to 100. Instances are safe to share between threads.encrypt(plaintext, *, tweak=b"")/decrypt(ciphertext, *, tweak=b""): same-length numeral strings over the same alphabet; input is case- and character-exact; the lengthnmust satisfyn >= 2andradix**n >= 1_000_000(or>= 100); tweaks are arbitrary bytes shorter than 2**32.encrypt_int(x, *, domain, tweak=b"")/decrypt_int(y, *, domain, tweak=b""): permute integers in[0, domain)withdomain >= 1_000_000(or>= 100); independent of the instance alphabet and stable across releases.- Errors:
KeyLengthError,AlphabetError, andDomainErrorderive fromFFXErrorandValueError; wrong argument types raiseTypeError.
Security notes
- FF1 is deterministic: equal plaintexts under the same key and tweak give equal ciphertexts. Use tweaks as public associated data to separate records.
- Message domains must satisfy
radix**n >= 1_000_000(Draft SP 800-38G Rev 1).allow_small_domain=Truerelaxes the floor to 100 (original SP 800-38G); small domains are fundamentally weaker. - Always use cryptographically random keys.
Migrating from v1
v1 implemented the FFX[radix] addendum profile, which is FF1 with a numeral
string tweak. FF1 with the old tweak string encoded as ASCII bytes
reproduces v1 FFX[radix] ciphertexts exactly, except that v1 treated any
tweak whose numeric value was zero ("0", "0000000000", ...) as no
tweak: decrypt those records with tweak=b"". v1 also accepted uppercase
input for radix > 10 (lowercase it first) and had no minimum message length
(v2 needs radix**n >= 1_000_000, or >= 100 with
allow_small_domain=True; shorter v1 data cannot be decrypted by v2).
ffx.new / FFXEncrypter / FFXInteger became FF1 with str and int
arguments, FFXException / InvalidRadixException became FFXError /
AlphabetError, and the AES backend is now cryptography. The GitHub
README has the full migration table.
Links
License
MIT License
Metadata
Release files for libffx 2.0.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 | |
|---|---|---|---|
| libffx-2.0.0.tar.gz | 32.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| libffx-2.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 43.3 kB
Release files / libffx-2.0.0.tar.gz
| Download URL | libffx-2.0.0.tar.gz |
|---|---|
| Size | 32.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bfcaa28dd185963e25a960b9ac73c8c848968151c07b1c777ede030d4b780f48
|
|
BLAKE2b-256 checksum How to use checksums |
8890f6fac4367c2cf9de0e6d5c3d4a04fd77e1eab8285e33d21027d80df5c78f
|
| 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 / libffx-2.0.0-py3-none-any.whl
| Download URL | libffx-2.0.0-py3-none-any.whl |
|---|---|
| Size | 11.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
602a51e19f19240113614f79d913ffd2d49acf709dfbc49147eb30941e132c48
|
|
BLAKE2b-256 checksum How to use checksums |
0003383d0864a5d8bad52948192ea05f6db57ea5b2a8651ffab70b66eb608ebf
|
| 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