Format Preserving Encryption (FPE)
[Back] Format Preserving Encryption (FPE) is a method which allows the basic formatting of a message to stay in a similar format, and where the value itself is encrypted. It can be used to hide credit card details.
What a 20th Century world we live in, and where we still store sensitive identifiers for citizens, and which map to their health record, their social care number, and credit card. The systems we have created think that the IDs are a great secret, but many now can be guessed (or discovered). At the core of any breach is the resolution of the person to the identity, and too often we reveal these identities on our databases.
Format Preserving Encryption (FPE) as a way to protect credit card details. The industry does seem to be worried, but every organisation which stored citizen identifiers need to be worried too.
With FPE we aim to encrypt a value, and then end up with a result which actually still looks valid. So let’s say that your credit card is “4012888888881881” (and where Visa cards start with a “4”). Now if an intruder gets this, they may be able to hack your bank account.
But let’s say we use a secret key to encrypt the value, and then come up with a value which is valid for a Visa card. This could be “4512878189882803”, and where an intruder thinks they have the right credit card details, but it will fail, as the details will not match for the name on the card, the CVV2 number, and so on.
This type of approach can also be used on health care records. In Scotland we define this as the 10-digit CHI number, and which is the basis of the identity of health records). This number is the patient’s date of birth (DDMMYY), and then two random digits and then two digits for their gender at birth (odd for male, and even for female). At the end we have a check digit. Thus the CHI number of a male born on 5 Feb 2016 can be: 0502160510. This number should NEVER be revealed on the database, but we need something that looks like it. In this way FPE can replace the actual CHI number.
If we reveal the CHI number, it is easy for someone to search for our date of birth — which is normally well known — and reveal our records. SQL queries can still be accepted, as the syntax of format is still correct, but the value is actually encrypted. The secret key can then be used away from the database.
And so FPE aims to encrypt, but keep the format of the data. There are many ways to do this, and the core specification (FFX) is based on this paper:
If we now apply FPE a credit card we get (with a pass phrase of “qwerty”):
Input string: 4012888888881881 Password: qwerty Encrypted: 9356030022219797 Decrypted: 4012888888881881
With methods such as Honey encryption, we can even make sure we match to a valid credit card number. For my CHI number we get:
Input string: 0502160510 Password: qwerty Encrypted: 1738184836 Decrypted: 0502160510
Again we could modify this so that it displayed valid looking CHI numbers.
But sometimes we have an SQL check, so where we need to have certain values present in the string. For this we can pick off the elements that are randomised and then encrypt them. So for a Visa card we have 16 digits and where the first digit is a '4'. We could then just process the 15 numbers after the '4' for the encrypted value, and place a '4' at the start:
Visa Bank card detected Processing: 012888888881881 Encrypted: 4969882978727679 Decrypted: 4012888888881881
An outline of the code is:
import pyffx import sys password='hello' value='1234' alpha='abcdefghijklmnopqrstuvwxyz' l=len(value) print "Input string:\t",value print "Password:\t",password print "Alphabet:\t",alpha if (value.isdigit()): e = pyffx.String(password, alphabet='0123456789',length=l) else: e = pyffx.String(password, alphabet=alpha,length=l) enc=e.encrypt(value) print '\nEncrypted:\t',enc dec=e.decrypt(enc) print 'Decrypted:\t',dec print "\n=======\n" if (value.isdigit() and value=='4' and len(value)==16): e = pyffx.String(password, alphabet='0123456789',length=l-1) print "Visa Bank card detected" print 'Processing:\t',value[1:16] enc=e.encrypt(value[1:16]) print 'Encrypted:\t','4'+enc dec=e.decrypt(enc) print 'Decrypted:\t','4'+dec
The following is an outline of a presentation [slides]: