{"id":23125148,"url":"https://github.com/statisticsnorway/tink-fpe-python","last_synced_at":"2025-06-21T07:41:54.503Z","repository":{"id":65812284,"uuid":"598494026","full_name":"statisticsnorway/tink-fpe-python","owner":"statisticsnorway","description":"Format-Preserving Encryption support for Google Tink (Python version)","archived":false,"fork":false,"pushed_at":"2024-06-24T01:34:44.000Z","size":338,"stargazers_count":3,"open_issues_count":14,"forks_count":0,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-05T00:11:14.272Z","etag":null,"topics":["dapla","tink"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/statisticsnorway.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-02-07T08:19:19.000Z","updated_at":"2025-02-10T10:23:16.000Z","dependencies_parsed_at":"2024-05-06T02:29:45.862Z","dependency_job_id":"3bf0a9b7-a779-407b-aa90-277763abe28f","html_url":"https://github.com/statisticsnorway/tink-fpe-python","commit_stats":{"total_commits":12,"total_committers":4,"mean_commits":3.0,"dds":0.25,"last_synced_commit":"11eb8319840683d75f26b4b158381332a4b5ea43"},"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/statisticsnorway/tink-fpe-python","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/statisticsnorway%2Ftink-fpe-python","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/statisticsnorway%2Ftink-fpe-python/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/statisticsnorway%2Ftink-fpe-python/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/statisticsnorway%2Ftink-fpe-python/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/statisticsnorway","download_url":"https://codeload.github.com/statisticsnorway/tink-fpe-python/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/statisticsnorway%2Ftink-fpe-python/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261087996,"owners_count":23107655,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dapla","tink"],"created_at":"2024-12-17T08:12:41.576Z","updated_at":"2025-06-21T07:41:49.485Z","avatar_url":"https://github.com/statisticsnorway.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Tink FPE Python\n\n[![PyPI](https://img.shields.io/pypi/v/tink-fpe.svg)][pypi_]\n[![Status](https://img.shields.io/pypi/status/tink-fpe.svg)][status]\n[![Python Version](https://img.shields.io/pypi/pyversions/tink-fpe)][python version]\n[![License](https://img.shields.io/pypi/l/tink-fpe)][license]\n\n[![Tests](https://github.com/statisticsnorway/tink-fpe-python/workflows/Tests/badge.svg)][tests]\n[![Codecov](https://codecov.io/gh/statisticsnorway/tink-fpe-python/branch/main/graph/badge.svg)][codecov]\n\n[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit\u0026logoColor=white)][pre-commit]\n[![Black](https://img.shields.io/badge/code%20style-black-000000.svg)][black]\n\n[pypi_]: https://pypi.org/project/tink-fpe/\n[status]: https://pypi.org/project/tink-fpe-python/\n[python version]: https://pypi.org/project/tink-fpe-python\n[tests]: https://github.com/statisticsnorway/tink-fpe-python/actions?workflow=Tests\n[codecov]: https://app.codecov.io/gh/statisticsnorway/tink-fpe-python\n[pre-commit]: https://github.com/pre-commit/pre-commit\n[black]: https://github.com/psf/black\n\nFormat-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.\n\n## Features\n\n- _Tink FPE_ implements a [Primitive](https://developers.google.com/tink/glossary) that extends the Google Tink framework with support for Format-Preserving Encryption (FPE).\n- The following [NIST compliant](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38Gr1-draft.pdf) algorithms are currently supported: `FF3-1`.\n- The implementation of the underlying algorithm is built on top of the excellent [Mysto FPE](https://github.com/mysto/python-fpe) library.\n- Tink FPE is currently available for Python and Java.\n- 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 different _strategies_ for dealing with the data or substitute the characters with ones that are part of the alphabet.\n\n## Requirements\n\n- Google Tink for Python\n\n## Installation\n\nYou can install _Tink FPE_ via [pip] from [PyPI]:\n\n```console\npip install tink-fpe\n```\n\n## Usage\n\n```python\nimport tink\nimport tink_fpe\n\n# Register Tink FPE with the Tink runtime\ntink_fpe.register()\n\n# Specify the key template to use. In this example we want a 256 bits FF3-1 key that can handle\n# alphanumeric characters (ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789)\nkey_template = tink_fpe.fpe_key_templates.FPE_FF31_256_ALPHANUMERIC\n\n# Create a keyset\nkeyset_handle = tink.new_keyset_handle(key_template)\n\n# Get the FPE primitive\nfpe = keyset_handle.primitive(tink_fpe.Fpe)\n\n# Encrypt\nciphertext = fpe.encrypt(b'Secret123')\nprint(ciphertext.decode('utf-8')) #-\u003e sN3gt6q0V\n\n# Decrypt\ndecrypted = fpe.decrypt(ciphertext)\nprint(decrypted.decode('utf-8')) #-\u003e Secret123\n```\n\n### Handling non-alphabet characters\n\nA characteristic of Format-Preserving Encryption is that plaintext can only be composed of letters or symbols\nfrom a predefined set of characters called the \"alphabet\". Tink FPE supports different ways of coping with\ntexts that contain non-alphabet characters. The approach to use can be expressed via the `UnknownCharacterStrategy` enum.\n\nThe following _stragies_ are supported:\n\n- `FAIL` - Raise an error and bail out if encountering a non-alphabet character. **(this is the default)**\n- `SKIP` - Ignore non-alphabet characters, leaving them unencrypted (nested into the ciphertext).\n- `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.\n- `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._\n\n```python\nfrom tink_fpe import FpeParams, UnknownCharacterStrategy\n\n# The following will raise an Error\nciphertext = fpe.encrypt(b'Ken sent me...', FpeParams(strategy=UnknownCharacterStrategy.FAIL))\n\n# Skipping non-supported characters might reveal too much of the plaintext, but it is currently the only\n# approach that will handle any plaintext without either failing or irreversibly transforming the plaintext.\nparams = FpeParams(strategy=UnknownCharacterStrategy.SKIP)\nfpe.encrypt(b'Ken sent me...', params) #-\u003e UEj l1Ns sj...\nfpe.decrypt(ciphertext, params) #-\u003e Ken sent me...\n\n# Notice that using the DELETE strategy implies that the length of the plaintext and ciphertext may differ.\n# Furthermore, it might be impossible to go back to the original plaintext.\nparams = FpeParams(strategy=UnknownCharacterStrategy.DELETE)\nciphertext = fpe.encrypt(b'Ken sent me...', params) #-\u003e EsQPgkE9Y\ndecrypted = fpe.decrypt(ciphertext, params) #-\u003e Kensentme\n\n# Notice that using the REDACT strategy it might be impossible to go back to the original plaintext.\n# If not specified, the redaction character will be deduced automatically from the alphabet.\n# For alphanumeric alphabets the 'X' character is used.\nparams = FpeParams(strategy=UnknownCharacterStrategy.REDACT)\nciphertext = fpe.encrypt(b'Ken sent me...', params) #-\u003e MMY2HXvLwzIDoY\ndecrypted = fpe.decrypt(ciphertext, params) #-\u003e KenXsentXmeXXX\n\n# It is also possible to specify the redaction character explicitly, like so:\nparams = FpeParams(strategy=UnknownCharacterStrategy.REDACT, redaction_char='Q')\nciphertext = fpe.encrypt(b'Ken sent me...', params) #-\u003e 9fVDzAODt2vvdz\ndecrypted = fpe.decrypt(ciphertext, params) #-\u003e KenQsentQmeQQQ\n```\n\n### Loading predefined key material\n\nIt is easy to initialize key material from a predefined JSON. The following uses a cleartext keyset,\nbut it will be similar for a wrapped/encrypted key as well.\n\n```python\nimport json\nfrom tink import JsonKeysetReader\nfrom tink import cleartext_keyset_handle\nimport tink_fpe\n\ntink_fpe.register()\n\nkeyset_json = json.dumps({\n    \"primaryKeyId\": 1382079328,\n    \"key\": [\n        {\n            \"keyData\": {\n                \"typeUrl\": \"type.googleapis.com/ssb.crypto.tink.FpeFfxKey\",\n                \"value\": \"EhD4978shQNRpBNaBjbF4KO4GkIQAho+QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODk=\",\n                \"keyMaterialType\": \"SYMMETRIC\"\n            },\n            \"status\": \"ENABLED\",\n            \"keyId\": 1382079328,\n            \"outputPrefixType\": \"RAW\"\n        }\n    ]\n})\n\nkeyset_handle = cleartext_keyset_handle.read(JsonKeysetReader(keyset_json))\nfpe = keyset_handle.primitive(tink_fpe.Fpe)\n```\n\n### Using key material protected by Google Cloud KMS\n\n```python\nimport json\n\nfrom tink import JsonKeysetReader\nfrom tink import read_keyset_handle\nfrom tink.integration import gcpkms\n\nimport tink_fpe\n\n\n# Define uri to key encryption key and path to GCP credentials\ngcp_credentials = \"path/to/sa-key.json\"\n\n# Register Tink FPE with the Tink runtime\ntink_fpe.register()\n\n# Get hold of a wrapped data encryption key (WDEK)\nkeyset_json = {\n    \"kekUri\": \"gcp-kms://projects/\u003cproject-id\u003e/locations/\u003cregion\u003e/keyRings/my-keyring/cryptoKeys/my-kek\",\n    \"encryptedKeyset\": \"CiQAp91NBsClBYjw4AS9sOdB65peMwlzY4AiOzyMe+b+dFjSBuIS2QEAZ30rtRcDkuvtUgeENQCt29Vsalf+FtaNZc8wpOXKb3sD2c8hTXKaf34iq2QRMaQUBXxG+YSJPV4PvJZMGydZpjowM9K2eAJFZs5JaVxb3BMfUt0miNaORZmczqZhKlXXHbMoQ71GLwfSnf4jJnIRJK4s38ThnxS2ebm4b5T0qno6PWg84TtUw9eIIieqlUFhIqBjCcMugGTsE+xfWIOct22RDEUI3cAboCew5ppjOREAxzbaH8LaUBct5eLN8wtakY3Vv8KxBoT3Hq6fnNSSGOKmkqMVrK0p\",\n    \"keysetInfo\":\n        {\n            \"primaryKeyId\": 593699223,\n            \"keyInfo\":\n                [\n                    {\n                        \"typeUrl\": \"type.googleapis.com/ssb.crypto.tink.FpeFfxKey\",\n                        \"status\": \"ENABLED\",\n                        \"keyId\": 593699223,\n                        \"outputPrefixType\": \"RAW\"\n                    }\n                ]\n        }\n}\n\n# Extract the kek uri from the keyset json\nkek_uri = keyset_json.pop('kekUri')\n\n# Unwrap key using Google Cloud KMS\nkms_client = gcpkms.GcpKmsClient(kek_uri, gcp_credentials)\nkms_aead = kms_client.get_aead(kek_uri)\nkeyset_handle = read_keyset_handle(keyset_reader=JsonKeysetReader(json.dumps(keyset_json)),\n                                   master_key_aead=kms_aead)\n\n# Get the FPE primitive\nfpe = keyset_handle.primitive(tink_fpe.Fpe)\n```\n\n## Known issues\n\n// TODO: Describe issue about chunking that results in up to last 3 characters not being encrypted.\n// TODO: Describe issue with minimum length depending on the alphabet radix (e.g. 4 characters for alphanumeric and 6 for digits)\n\n## Contributing\n\nContributions are very welcome.\nTo learn more, see the [Contributor Guide].\n\n## License\n\nDistributed under the terms of the [MIT license][license],\n_Tink FPE Python_ is free and open source software.\n\n## Issues\n\nIf you encounter any problems,\nplease [file an issue] along with a detailed description.\n\n## Credits\n\nThis project was generated from [@cjolowicz]'s [Hypermodern Python Cookiecutter] template.\n\n[@cjolowicz]: https://github.com/cjolowicz\n[pypi]: https://pypi.org/\n[hypermodern python cookiecutter]: https://github.com/cjolowicz/cookiecutter-hypermodern-python\n[file an issue]: https://github.com/statisticsnorway/tink-fpe/issues\n[pip]: https://pip.pypa.io/\n\n\u003c!-- github-only --\u003e\n\n[license]: https://github.com/statisticsnorway/tink-fpe-python/blob/main/LICENSE\n[contributor guide]: https://github.com/statisticsnorway/tink-fpe-python/blob/main/CONTRIBUTING.md\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstatisticsnorway%2Ftink-fpe-python","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstatisticsnorway%2Ftink-fpe-python","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstatisticsnorway%2Ftink-fpe-python/lists"}