Package: tink-fpe
Tink FPE PythonFormat-Preserving Encryption (FPE) is a type of encryption that encrypts data in a way that preserves the format of the original plaintext. This means that after encryption, the encrypted data retains the same format as the original plaintext, such as a specific length or character set.FeaturesTink FPEimplements aPrimitivethat extends the Google Tink framework with support for Format-Preserving Encryption (FPE).The followingNIST compliantalgorithms are currently supported:FF3-1.The implementation of the underlying algorithm is built on top of the excellentMysto FPElibrary.Tink FPE is currently available for Python and Java.Regarding sensitivity for alphabet, FPE is designed to work with a specific alphabet, which is typically defined in the encryption algorithm. If the plaintext data contains characters that are not part of the defined alphabet, Tink FPE supports differentstrategiesfor dealing with the data or substitute the characters with ones that are part of the alphabet.RequirementsGoogle Tink for Python - the bleeding edge version (untilthis issueis resolved)InstallationYou can installTink FPEviapipfromPyPI:$pipinstalltink-fpeUsageimporttinkimporttink_fpe# Register Tink FPE with the Tink runtimetink_fpe.register()# Specify the key template to use. In this example we want a 256 bits FF3-1 key that can handle# alphanumeric characters (ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789)key_template=tink_fpe.fpe_key_templates.FPE_FF31_256_ALPHANUMERIC# Create a keysetkeyset_handle=tink.new_keyset_handle(key_template)# Get the FPE primitivefpe=keyset_handle.primitive(tink_fpe.Fpe)# Encryptciphertext=fpe.encrypt(b'Secret123')print(ciphertext.decode('utf-8'))#-> sN3gt6q0V# Decryptdecrypted=fpe.decrypt(ciphertext)print(decrypted.decode('utf-8'))#-> Secret123Handling non-alphabet charactersA characteristic of Format-Preserving Encryption is that plaintext can only be composed of letters or symbols
from a predefined set of characters called the "alphabet". Tink FPE supports different ways of coping with
texts that contain non-alphabet characters. The approach to use can be expressed via theUnknownCharacterStrategyenum.The followingstragiesare supported:FAIL- Raise an error and bail out if encountering a non-alphabet character.(this is the default)SKIP- Ignore non-alphabet characters, leaving them unencrypted (nested into the ciphertext).DELETE- Remove all characters that are not part of the alphabet prior to processing. _Warning: Using this strategy implies that the length of the plaintext and ciphertext may differ.REDACT- Replace non-alphabet characters with an alphabet-compliant character prior to processing.Warning: Using this strategy means that decryption may not result in the exact same plaintext being restored.fromtink_fpeimportFpeParams,UnknownCharacterStrategy# The following will raise an Errorciphertext=fpe.encrypt(b'Ken sent me...',FpeParams(strategy=UnknownCharacterStrategy.FAIL))# Skipping non-supported characters might reveal too much of the plaintext, but it is currently the only# approach that will handle any plaintext without either failing or irreversibly transforming the plaintext.params=FpeParams(strategy=UnknownCharacterStrategy.SKIP)fpe.encrypt(b'Ken sent me...',params)#-> UEj l1Ns sj...fpe.decrypt(ciphertext,params)#-> Ken sent me...# Notice that using the DELETE strategy implies that the length of the plaintext and ciphertext may differ.# Furthermore, it might be impossible to go back to the original plaintext.params=FpeParams(strategy=UnknownCharacterStrategy.DELETE)ciphertext=fpe.encrypt(b'Ken sent me...',params)#-> EsQPgkE9Ydecrypted=fpe.decrypt(ciphertext,params)#-> Kensentme# Notice that using the REDACT strategy it might be impossible to go back to the original plaintext.# If not specified, the redaction character will be deduced automatically from the alphabet.# For alphanumeric alphabets the 'X' character is used.params=FpeParams(strategy=UnknownCharacterStrategy.REDACT)ciphertext=fpe.encrypt(b'Ken sent me...',params)#-> MMY2HXvLwzIDoYdecrypted=fpe.decrypt(ciphertext,params)#-> KenXsentXmeXXX# It is also possible to specify the redaction character explicitly, like so:params=FpeParams(strategy=UnknownCharacterStrategy.REDACT,redaction_char='Q')ciphertext=fpe.encrypt(b'Ken sent me...',params)#-> 9fVDzAODt2vvdzdecrypted=fpe.decrypt(ciphertext,params)#-> KenQsentQmeQQQLoading predefined key materialIt is easy to initialize key material from a predefined JSON. The following uses a cleartext keyset,
but it will be similar for a wrapped/encrypted key as well.importjsonfromtinkimportJsonKeysetReaderfromtinkimportcleartext_keyset_handleimporttink_fpetink_fpe.register()keyset_json=json.dumps({"primaryKeyId":1382079328,"key":[{"keyData":{"typeUrl":"type.googleapis.com/ssb.crypto.tink.FpeFfxKey","value":"EhD4978shQNRpBNaBjbF4KO4GkIQAho+QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODk=","keyMaterialType":"SYMMETRIC"},"status":"ENABLED","keyId":1382079328,"outputPrefixType":"RAW"}]})keyset_handle=cleartext_keyset_handle.read(JsonKeysetReader(keyset_json))fpe=keyset_handle.primitive(tink_fpe.Fpe)Using key material protected by Google Cloud KMSimportjsonfromtinkimportJsonKeysetReaderfromtinkimportread_keyset_handlefromtink.integrationimportgcpkmsimporttink_fpe# Define uri to key encryption key and path to GCP credentialsgcp_credentials="path/to/sa-key.json"# Register Tink FPE with the Tink runtimetink_fpe.register()# Get hold of a wrapped data encryption key (WDEK)keyset_json={"kekUri":"gcp-kms://projects/<project-id>/locations/<region>/keyRings/my-keyring/cryptoKeys/my-kek","encryptedKeyset":"CiQAp91NBsClBYjw4AS9sOdB65peMwlzY4AiOzyMe+b+dFjSBuIS2QEAZ30rtRcDkuvtUgeENQCt29Vsalf+FtaNZc8wpOXKb3sD2c8hTXKaf34iq2QRMaQUBXxG+YSJPV4PvJZMGydZpjowM9K2eAJFZs5JaVxb3BMfUt0miNaORZmczqZhKlXXHbMoQ71GLwfSnf4jJnIRJK4s38ThnxS2ebm4b5T0qno6PWg84TtUw9eIIieqlUFhIqBjCcMugGTsE+xfWIOct22RDEUI3cAboCew5ppjOREAxzbaH8LaUBct5eLN8wtakY3Vv8KxBoT3Hq6fnNSSGOKmkqMVrK0p","keysetInfo":{"primaryKeyId":593699223,"keyInfo":[{"typeUrl":"type.googleapis.com/ssb.crypto.tink.FpeFfxKey","status":"ENABLED","keyId":593699223,"outputPrefixType":"RAW"}]}}# Extract the kek uri from the keyset jsonkek_uri=keyset_json.pop('kekUri')# Unwrap key using Google Cloud KMSkms_client=gcpkms.GcpKmsClient(kek_uri,gcp_credentials)kms_aead=kms_client.get_aead(kek_uri)keyset_handle=read_keyset_handle(keyset_reader=JsonKeysetReader(json.dumps(keyset_json)),master_key_aead=kms_aead)# Get the FPE primitivefpe=keyset_handle.primitive(tink_fpe.Fpe)DockerfileAs of this writing (27.03.2023), Tink does not yet provide Python wheels for versions>1.6.x.
Thus, in order to use Tink FPE, we need to build Tink, which involves using bazel and and
compiling protobuf sources. The following shows a Dockerfile that demonstrates how this can
be done. Notice that this is for thex86architecture. If you are on another
architecture (e.g.arm), you need to substitute the bazel and protobuf references to match
your system architecture.FROMpython:3.10-bullseyeRUNapt-getupdate&&apt-getupgrade-y# Install curl and gitRUNapt-getinstall-ycurlgit# Install bazelRUNcurl-Lhttps://github.com/bazelbuild/bazelisk/releases/download/v1.16.0/bazelisk-linux-amd64>/usr/local/bin/bazelisk&&chmod+x/usr/local/bin/bazelisk# Install latest protobuf compiler (note: protobuf-compiler from apt is an older non-compliant version)WORKDIR/opt/protobufRUNcurl-Lhttps://github.com/protocolbuffers/protobuf/releases/download/v21.12/protoc-21.12-linux-x86_64.zip>protoc.zipRUNunzipprotoc.zip&&chmod+x./bin/protocRUNln-s/opt/protobuf/bin/protoc/usr/local/bin/protoc# Update pipRUNpipinstall--upgradepip# ...WORKDIR/appKnown issues// TODO: Describe issue about chunking that results in up to last 3 characters not being encrypted.
// TODO: Describe issue with minimum length depending on the alphabet radix (e.g. 4 characters for alphanumeric and 6 for digits)ContributingContributions are very welcome.
To learn more, see theContributor Guide.LicenseDistributed under the terms of theMIT license,Tink FPE Pythonis free and open source software.IssuesIf you encounter any problems,
pleasefile an issuealong with a detailed description.CreditsThis project was generated from@cjolowicz'sHypermodern Python Cookiecuttertemplate.