Format-Preserving Encryption (FPE)

AES ciphertext of an SSN looks like a 256-bit blob; a downstream system expecting NNN-NN-NNNN cannot validate it, index it, or route on it. In production systems that is usually fine — decrypt at the boundary. But for non-prod environments (CI, load tests, demos, vendor integration testing) you need realistic-looking data that flows through schema validators, regexes, and join keys without exploding.

Format-preserving encryption (FPE) encrypts a value into a ciphertext with the same format: a 9-digit SSN in, a different 9-digit SSN out; a 16-digit credit-card number in, a Luhn-valid 16-digit number out. NIST standardizes two modes in SP 800-38G: FF1 and FF3-1, both built on AES.



1. When FPE Is the Right Tool


2. FF1 vs FF3-1

Both use AES-128/192/256 as the underlying PRF. Security is tight against known attacks when the domain size and tweak are handled correctly; avoid rolling your own mode.


3. Example: FF1 in Python (pyffx)

import string
from pyffx import String, Integer

KEY = bytes.fromhex("0123456789abcdef" * 2)   # 16-byte AES key


def encrypt_ssn(ssn: str, tweak: bytes = b"ssn-v1") -> str:
    # SSN as a 9-digit integer so the ciphertext is also 9 digits.
    digits = ssn.replace("-", "")
    assert len(digits) == 9 and digits.isdigit()
    cipher = Integer(KEY, length=9)
    enc = cipher.encrypt(int(digits))
    enc_str = f"{enc:09d}"
    return f"{enc_str[:3]}-{enc_str[3:5]}-{enc_str[5:]}"


def encrypt_account(name: str) -> str:
    # Alphabetic handle that must remain alphabetic.
    cipher = String(KEY, alphabet=string.ascii_lowercase, length=len(name))
    return cipher.encrypt(name.lower())


print(encrypt_ssn("123-45-6789"))   # e.g. "482-19-3071"
print(encrypt_account("acmecorp"))  # e.g. "rjkqwhfm"

Because FPE is deterministic for a given (key, tweak), the same SSN always produces the same pseudonym — which is exactly what you want for joins. That also means it leaks equality: if the attacker sees the ciphertext of a known SSN, they can match other ciphertexts of the same value. See section 5 for when this matters.


4. Tweaks & Key Management


5. FPE vs Tokenization vs Deterministic AEAD

Choose FPE only when format preservation is the requirement. For most production redaction, the vault-based tokenizer described on the PII redaction page is the safer default.


6. Gotchas


↑ Back to Top