Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement to add Format Preserving Encryption (FF3-1) support #601

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 1 addition & 16 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,19 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: [ 2.7, 3.5, 3.6, 3.7, 3.8, 3.9, "3.10" ]
python-version: [ 3.5, 3.6, 3.7, 3.8, 3.9, "3.10" ]
cffi: [ yes, no ]
os: [ ubuntu-latest ]
include:
- python-version: 2.7
cffi: no
os: macos-10.15
- python-version: 2.7
cffi: yes
os: macos-10.15
- python-version: "3.10"
cffi: yes
os: macos-10.15
- python-version: 2.7
cffi: no
os: windows-latest
- python-version: 2.7
cffi: yes
os: windows-latest
- python-version: "3.10"
cffi: no
os: windows-latest
- python-version: "3.10"
cffi: yes
os: windows-latest
- python-version: pypy2
cffi: no
os: ubuntu-latest
- python-version: pypy3
cffi: no
os: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,4 @@ Eric Young
Hannes van Niekerk
Stefan Seering
Koki Takahashi
Joshua Holt
4 changes: 4 additions & 0 deletions Changelog.rst
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
Changelog
=========

<<<<<<< HEAD
3.15.0 (22 June 2022)
>>>>>>> 9a84371eb0edc0bcd667c1bb63a643593a9b6065
++++++++++++++++++++++++++

New features
------------
<<<<<<< HEAD
* Add support for curves Ed25519 and Ed448, including export and import of keys.
* Add support for EdDSA signatures.
* Add support for Asymmetric Key Packages (RFC5958) to import private keys.
Expand All @@ -14,6 +17,7 @@ Resolved issues
---------------
* GH#620: for ``Crypto.Util.number.getPrime`` , do not sequentially
scan numbers searching for a prime.
>>>>>>> 9a84371eb0edc0bcd667c1bb63a643593a9b6065

3.14.1 (5 February 2022)
++++++++++++++++++++++++++
Expand Down
6 changes: 5 additions & 1 deletion Doc/src/cipher/cipher.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ The base API of a cipher is fairly simple:
* For decrypting data, you call the :func:`decrypt` method of the cipher
object with the ciphertext. The method returns the piece of plaintext.
The ``output`` parameter can be passed here too.

For most algorithms, you may call :func:`decrypt` multiple times
(i.e. once for each piece of ciphertext).

Expand Down Expand Up @@ -99,6 +99,10 @@ There are two types of symmetric ciphers:
a variable amount of data. Some modes (like CTR) effectively turn
a block cipher into a stream cipher.

**Format Preserving Encryption** is a symmetric mode of operation which preserves
the length and format of the original plaintext.
See :doc:`ff3`

The widespread consensus is that ciphers that provide
only confidentiality, without any form of authentication, are undesirable.
Instead, primitives have been defined to integrate symmetric encryption and
Expand Down
84 changes: 84 additions & 0 deletions Doc/src/cipher/ff3.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
FF3
===

FF3 `(Format Preserving Encryption)`__ is a a method of encryption which
encrypts a plaintext into a ciphertext while preserving the format of the
plaintext. PyCryptodome implements FF3-1 as outlined in NIST 800-38G NIST_ .

Format Preserving Encryption is useful for legacy systems and other situations
where sensitive data must be protected, but the format and the length must be
retained. Common examples include Social Security Numbers (SSNs) and credit
card numbers.

FF3 uses the AES block cipher under the hood in CBC-MAC mode, and supports
keys lengths of 128, 192, or 256 bits long.

Format Preserving Encryption has a few unique properties which are required
to successfully use the algorithm:

1. **Alphabet**: Alphabets represent the valid characters that can appear in
a plaintext. For SSNs and credit cards, which can contain only digits, the
alphabet would be "0123456789". NIST ACVP defines an alphabet as a minimum of
two characters, and a maximum of 64 (all numbers and upper and lower case
letters, additionally "+" and "/").

2. **Radix**: The radix is simply the length of the alphabet, and represents
the number base. For example, SSNs are decimal digits and are in base 10.

3. **Tweak**: A tweak is a non-secret value that can be used to change part of
the key. Tweaks are necessary in Format Preserving Encryption because the domain
of ciphertexts can be relatively low. FF3-1 tweaks must be 7 bytes in length.
Any information that is available and associated with a plaintext can be used
as a tweak. It's very similar to a salt value in that it doesn't need to be
secret, but should be unique. Tweaks should be used whenever possible to limit
guessing attacks.

FF3-1 example::

>>> from Crypto.Cipher.FF3 import FF3
>>> from Crypto.Random import get_random_bytes
>>>
>>> alphabet = "0123456789"
>>> radix = len(alphabet)
>>> key = get_random_bytes(16)
>>> fpe = FF3(radix, alphabet, key)

You can encrypt a plaintext by passing the plaintext and a tweak to the
encrypt() method::

>>> tweak = get_random_bytes(7)
>>> pt = "123456789"
>>> ct = fpe.encrypt(pt, tweak)
>>> print(ct)
930076983

You can decrypt a ciphertext by passing the ciphertext and a tweak to the
decrypt() method:

>>> pt = fpe.decrypt(ct, tweak)
>>> print(pt)
123456789

FPE is deterministic, and the same plaintext and tweak values will provide the
same ciphertext. However, modifying the tweak value will change the associated
ciphertext:

>>> ct = fpe.encrypt(pt, tweak)
>>> print(ct)
930076983
>>> tweak = get_random_bytes(7)
>>> ct = fpe.encrypt(pt, tweak)
>>> print(ct)
138680525
>>> pt = fpe.decrypt(ct, tweak)
>>> print(pt)
123456789

Note that NIST also defines FF1, which has patent claims and is not implemented
by PyCryptodome.

.. __: https://en.wikipedia.org/wiki/Format-preserving_encryption
.. _NIST: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38Gr1-draft.pdf

.. automodule:: Crypto.Cipher.FF3
:members:
Loading