Skip to main content

FFX - Format Preserving Encryption (NIST FFX-A2 mode of operation)

Project description

FFX - Format Preserving Encryption

Tests Python 3.9+ License: MIT

A Python implementation of the FFX Mode of Operation for Format-Preserving Encryption (FPE).

Format-preserving encryption encrypts data while preserving its format. For example, a 16-digit credit card number encrypts to another 16-digit number, and a 9-digit SSN encrypts to another 9-digit number.

Specification

This implementation follows the NIST FFX-A2 specification:

Algorithm Details

  • Cipher: AES-128
  • Mode: Maximally-balanced Feistel network
  • Rounds: 10 (constant, independent of message size)
  • Radix: Supports 2–36 (binary through alphanumeric)
  • Message sizes: Tested with 2–128+ characters

Installation

pip install -e .

Or install dependencies directly:

pip install -r requirements.txt

Dependencies

  • gmpy2 - Fast arbitrary precision arithmetic
  • pycryptodome - AES implementation

Quick Start

import ffx

# Create key, tweak, and plaintext
key = ffx.FFXInteger('0' * 128, radix=2, blocksize=128)
tweak = ffx.FFXInteger('0' * 8, radix=2, blocksize=8)
plaintext = ffx.FFXInteger('0' * 8, radix=2, blocksize=8)

# Create encrypter (radix=2 for binary)
ffx_obj = ffx.new(key.to_bytes(16), radix=2)

# Encrypt and decrypt
ciphertext = ffx_obj.encrypt(tweak, plaintext)
decrypted = ffx_obj.decrypt(tweak, ciphertext)

print(f"Plaintext:  {plaintext}")   # 00000000
print(f"Ciphertext: {ciphertext}")  # 10100010
print(f"Decrypted:  {decrypted}")   # 00000000

Encrypting Credit Card Numbers (Radix 10)

import ffx

# 128-bit key (as hex)
key = ffx.FFXInteger('2b7e151628aed2a6abf7158809cf4f3c', radix=16, blocksize=32)

# Create encrypter for decimal digits
ffx_obj = ffx.new(key.to_bytes(16), radix=10)

# Encrypt a credit card number
cc_number = ffx.FFXInteger('4111111111111111', radix=10, blocksize=16)
tweak = ffx.FFXInteger('0000000000', radix=10, blocksize=10)

encrypted = ffx_obj.encrypt(tweak, cc_number)
print(f"Encrypted: {encrypted}")  # Another 16-digit number

Running Tests

The test suite validates the implementation against official Voltage Security test vectors.

pytest

Or with verbose output:

pytest -v

Test Vectors

Test vectors from the official NIST submission: aes-ffx-vectors.txt

Vector Radix Input Tweak Expected Output
1 10 0123456789 9876543210 6124200773
2 10 0123456789 (none) 2433477484
3 10 314159 2718281828 535005
4 10 999999999 7777777 658229573
5 36 C4XPWULBM3M863JH TQF9J5QDAGSCSPB1 C8AQ3U846ZWH6QZP

Benchmarks

python benchmark.py --radix 10 --tweaksize 10 --messagesize 16

Example output:

RADIX=10, TWEAKSIZE=10, MESSAGESIZE=16, KEY=0x7fab9cfe5f0b2f4b61fc18fc018e1d66
test #1 SUCCESS: (encrypt_cost=0.5ms, decrypt_cost=0.1ms, tweak=4116892577, plaintext=2673647323700035, ciphertext=0238930243347266)
test #2 SUCCESS: (encrypt_cost=0.1ms, decrypt_cost=0.1ms, tweak=4681498724, plaintext=6915018802668851, ciphertext=4790098135418225)
...

Project Structure

libffx/
├── ffx/
│   └── __init__.py       # FFX implementation
├── tests/
│   └── test_ffx.py       # Test suite
├── pyproject.toml        # Package configuration
├── requirements.txt      # Dependencies
├── example.py            # Usage example
├── benchmark.py          # Performance benchmarks
├── aes-ffx-vectors.txt   # Official NIST test vectors
├── LICENSE
└── README.md

API Reference

ffx.new(key, radix)

Create a new FFX encrypter.

  • key: 16-byte AES-128 key
  • radix: Base for message alphabet (2-36)

FFXInteger(value, radix=2, blocksize=None)

Represent a value in a specific radix.

  • value: Integer, string representation, or another FFXInteger
  • radix: Base (2-36)
  • blocksize: Minimum string length (zero-padded)

FFXEncrypter.encrypt(tweak, plaintext)

Encrypt a plaintext with an optional tweak.

  • tweak: FFXInteger or 0 for no tweak
  • plaintext: FFXInteger to encrypt

FFXEncrypter.decrypt(tweak, ciphertext)

Decrypt a ciphertext with the same tweak used for encryption.

Security Considerations

  • FFX is designed for format-preserving encryption of small domains
  • The security depends on the domain size; very small domains may be vulnerable to brute force
  • Always use cryptographically random keys
  • Tweaks can be used as public "associated data" but should be unique per encryption when possible

License

MIT License - see LICENSE file.

Author

Kevin P. Dyer (kpdyer@gmail.com)

Project details


Download files

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

Source Distribution

libffx-1.0.0.tar.gz (23.9 kB view details)

Uploaded Source

Built Distribution

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

libffx-1.0.0-py3-none-any.whl (21.8 kB view details)

Uploaded Python 3

File details

Details for the file libffx-1.0.0.tar.gz.

File metadata

  • Download URL: libffx-1.0.0.tar.gz
  • Upload date:
  • Size: 23.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.6

File hashes

Hashes for libffx-1.0.0.tar.gz
Algorithm Hash digest
SHA256 bf6e21a8a742ad26285e1ed61b2d7a2fb029d5a558eaa6d886b145212e4f0e50
MD5 5044a79cb1ac06194e556cb2f2467284
BLAKE2b-256 cc8f59f9ea42b848d420804582b9187568e4d2fba19ff56afc93dcadda0f983c

See more details on using hashes here.

File details

Details for the file libffx-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: libffx-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 21.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.6

File hashes

Hashes for libffx-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 46f5b73b9beb7700c4659f97aa9e8ec00558dc5d51c291ddf1d7a0ba8b8b36e6
MD5 a7d2e275341b11627bdd345854fe5646
BLAKE2b-256 32c39191e157949e34bd61804b782bd91720208e2e5e320f2d7fd5cd78902df1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page