Skip to main content

libffx - FF1 Format-Preserving Encryption

PyPI version Python 3.10+ License: MIT

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; pass radix (2-36) or an explicit alphabet (2-65536 unique Unicode characters), or neither for integer-only use. allow_small_domain=True lowers 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 length n must satisfy n >= 2 and radix**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) with domain >= 1_000_000 (or >= 100); independent of the instance alphabet and stable across releases.
  • Errors: KeyLengthError, AlphabetError, and DomainError derive from FFXError and ValueError; wrong argument types raise TypeError.

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=True relaxes 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.

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)

Source distribution for libffx 2.0.0
File Size Uploaded
libffx-2.0.0.tar.gz 32.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for libffx 2.0.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

2.0.1

2 release files

This release

2.0.0 This release

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