You need to encrypt a set of clients' credit card numbers (or social security numbers, or tax file numbers, or some other field that needs to be kept secret) in a database along with their other details.
Here's how not to do it, followed by some better methods.
Let's start with a simple database table of clients with their credit card numbers.
| ClientID | ClientName | CreditCardNum |
|---|---|---|
| 1 | John Doe | 1234-5678-9789-0124 |
| 2 | Fred Bloggs | 1234-5671-9988-7766 |
| 3 | Erika Mustermann | 3456-7898-9789-0124 |
Well, you could just store it like that. Frighteningly, lots of big stores seem to do that. They may have a secure SSL connection for you to submit your credit card number, and, yes, it is impossible for anyone monitoring your internet connection at that time to get your credit card number, but then the silly idiots go and store it on their database with your number in the clear for any disgruntled employee or hacker to find. DO NOT DO THIS!.
Database systems like Microsoft Access have an option to format a particular field in "password mode", so, when viewed, the table looks like
| ClientID | ClientName | CreditCardNum |
|---|---|---|
| 1 | John Doe | ******************* |
| 2 | Fred Bloggs | ******************* |
| 3 | Erika Mustermann | ******************* |
This might look good to a casual onlooker, but anyone with any knowledge of the database can extract the underlying data without a problem. DO NOT DO THIS!.
000102030405060708090A0B0C0D0E0F1011121314151617and we will store the encrypted ciphertext in hexadecimal-encoded format - this can easily be stored in a text string.
Let's use the simplest mode of symmetric encryption, the Electronic Code Book (ECB) mode. This does not require any initialization vector and is probably the default mode in your encryption package. We do, however, need to pad our credit card input data to make it an exact multiple of the Triple DES block length of 8 bytes. In the examples that follow, we use the PKCS#5 method of padding.
Our first encryption operation in ECB mode is:
PT="1234-5678-9789-0124" PT(hex) =313233342D353637382D393738392D30313234 PT(padded)=313233342D353637382D393738392D303132340505050505 CT(hex) =0BDC16E6A777C535C49F67688C6D4E21D3F36088C206C85A
Completing all the encryptions, our database table, with encrypted credit card number in hexadecimal (hex) format, becomes
| ClientID | ClientName | CreditCardNum |
|---|---|---|
| 1 | John Doe | 0BDC16E6A777C535C49F67688C6D4E21D3F36088C206C85A |
| 2 | Fred Bloggs | 0BDC16E6A777C535264AF5FD1E8BD570DDD44E842A72C00B |
| 3 | Erika Mustermann | 5408551E9C4A0F8FC49F67688C6D4E21D3F36088C206C85A |
OK, we have just used the FIPS-approved Triple DES encryption algorithm with a secret 192-bit key to encrypt our clients' credit card data. Couldn't be safer, could it? But, er, look at the results a bit closer here.
0BDC16E6A777C535C49F67688C6D4E21D3F36088C206C85A 0BDC16E6A777C535264AF5FD1E8BD570DDD44E842A72C00B 5408551E9C4A0F8FC49F67688C6D4E21D3F36088C206C85A
An encrypted form of a given input is meant to be indistinguishable from a random value. Do these results look random to you? No, they are not. Simply put, ECB mode is not secure - DO NOT USE IT!
The best modes to use when encrypting are either Cipher Block Chaining (CBC) or Counter mode (CTR). These modes require that we use a unique Initialization Vector (IV) each time for a given key. Counter mode has the advantage that no padding is needed, but you need to be especially careful never to re-use an IV.
Let's add a new field to our database, IV, which will contain an 8-byte number randomly-generated each time the record changes. The chances of such an 8-byte IV value repeating with a proper random-number generator are infinitesimal. So now we'll generate a fresh, random 8-byte IV for each record, store this separately in the clear (which is OK), and produce a new set of encrypted credit card numbers using CBC mode.
| ClientID | ClientName | IV | CreditCardNum |
|---|---|---|---|
| 1 | John Doe | F113709191C4259F | 8C2787E652FE8C37057E30A0139381FD4FE4635680E92324 |
| 2 | Fred Bloggs | 18DF733256D44E32 | 2874919B17EFEDFCCC0206723C26A003087D10A271449323 |
| 3 | Erika Mustermann | D23AB952DC37BFCA | 98185AC577517C1813CCC3FFF5C9FF86DD2E6A2D7A014DC3 |
This method is secure, providing you
So now your database administrator complains about having to add this new "IV" field. Well, why not join the IV value to the encrypted credit card number and store the concatenated value in the form
[IV] || [Encrypted credit card number]
So the same database can be stored like this
| ClientID | ClientName | CreditCardNum |
|---|---|---|
| 1 | John Doe | F113709191C4259F8C2787E652FE8C37057E30A0139381FD4FE4635680E92324 |
| 2 | Fred Bloggs | 18DF733256D44E322874919B17EFEDFCCC0206723C26A003087D10A271449323 |
| 3 | Erika Mustermann | D23AB952DC37BFCA98185AC577517C1813CCC3FFF5C9FF86DD2E6A2D7A014DC3 |
And when you need to decrypt the card number, you need to parse the value first to split off the IV. For a given block cipher algorithm, you always know exactly how long the IV will be. For Triple DES it is 8 bytes; for AES it is 16 bytes. So, taking the second example above, the concatenated encrypted value is
18DF733256D44E322874919B17EFEDFCCC0206723C26A003087D10A271449323We split this into two parts, where the first 8 bytes is the IV
IV=18DF733256D44E32 CT=2874919B17EFEDFCCC0206723C26A003087D10A271449323then we use the IV together with the secret key to decrypt the ciphertext and obtain
PT=313233342D353637312D393938382D373736360505050505We need to strip off the padding, in this case, five bytes each of value 0x05 (this also provides a convenient check that the decryption was correct)
313233342D353637312D393938382D37373636and then decode this from hex format into a string
1234-5671-9988-7766
To shorten the length of the encrypted string before storing, we could use base64 encoding instead of hexadecimal.
| ClientID | ClientName | CreditCardNum |
|---|---|---|
| 1 | John Doe | jCeH5lL+jDcFfjCgE5OB/U/kY1aA6SMk |
| 2 | Fred Bloggs | KHSRmxfv7fzMAgZyPCagAwh9EKJxRJMj |
| 3 | Erika Mustermann | mBhaxXdRfBgTzMP/9cn/ht0uai16AU3D |
000102030405060708090A0B0C0D0E0F
Our input data is the same as before.
| ClientID | ClientName | CreditCardNum |
|---|---|---|
| 1 | John Doe | 1234-5678-9789-0124 |
| 2 | Fred Bloggs | 1234-5671-9988-7766 |
| 3 | Erika Mustermann | 3456-7898-9789-0124 |
For AES we need to use a 16-byte IV, so we generate a new value for each record
| ClientID | IV |
|---|---|
| 1 | 7AD3C3BF888C9E88AA5F44773FAEB42E |
| 2 | 4CC0646AACFAAF8D9E2B3D7BEC1E1232 |
| 3 | A9043A577688BD354FCD6CDC85DBD7CB |
To encrypt the first record, we have
KEY=000102030405060708090A0B0C0D0E0F IV =7AD3C3BF888C9E88AA5F44773FAEB42E PT="1234-5678-9789-0124" PT(hex) =313233342D353637382D393738392D30313234 CT(hex) =612B4B355C9874F920AC346BD0F8C5C7614165
Note that padding is not needed with CTR mode and the output ciphertext is the same length as the input. This has the advantage that there is no padding to remove when decrypting and less data to store, but you lose the check for correct decryption provided by CBC mode with PKCS#5 padding.
Repeating this procedure gives the following ciphertext values
| Plaintext | Ciphertext |
|---|---|
| 1234-5678-9789-0124 | 612B4B355C9874F920AC346BD0F8C5C7614165 |
| 1234-5671-9988-7766 | D51615107F3B298596E9170083EE0F093E0C69 |
| 3456-7898-9789-0124 | 24C2FE415F3C160AC7106114C9E0DAC31FCA60 |
The resulting table is as follows:
| ClientID | ClientName | CreditCardNum |
|---|---|---|
| 1 | John Doe | 7AD3C3BF888C9E88AA5F44773FAEB42E612B4B355C9874F920AC346BD0F8C5C7614165 |
| 2 | Fred Bloggs | 4CC0646AACFAAF8D9E2B3D7BEC1E1232D51615107F3B298596E9170083EE0F093E0C69 |
| 3 | Erika Mustermann | A9043A577688BD354FCD6CDC85DBD7CB24C2FE415F3C160AC7106114C9E0DAC31FCA60 |
In a database with an automatically-generated ID, each new record is guaranteed to have a unique ID value, so you could, in principle, use just the ID number as the IV, suitably padded to the required block length.
| ClientID | IV |
|---|---|
| 1 | 0000000000000001 |
| 2 | 0000000000000002 |
| 3 | 0000000000000003 |
The problem with this is that, if a client changes their credit card number, then you are using the same IV to encrypt a different value - DO NOT DO THIS!.
A more secure approach is to randomly generate, say, the first eight bytes and then append the ID number to make the required 16-byte IV.
| ClientID | Random | IV |
|---|---|---|
| 1 | 7AD3C3BF888C9E88 | 7AD3C3BF888C9E880000000000000001 |
| 2 | 4CC0646AACFAAF8D | 4CC0646AACFAAF8D0000000000000002 |
| 3 | A9043A577688BD35 | A9043A577688BD350000000000000003 |
So you only need to store the 8-byte random component instead of full 16-byte value. The chances of getting the same IV is still impossible, provided your random-number generator is OK. Appending the unique record ID makes it even less likely you will get the same value. Don't worry about all the zero padding bytes in the IV values here; an IV just has to be unique. In this case, you save 16 hex characters in the output string, with the minor downside that you now need to reconstruct the full IV when decrypting. (You could go even shorter and just generate and store, say, 4 random bytes - the odds of getting the same 32-bit value are still 4 billion to one. But don't go any shorter than 4 bytes.)
Using this method, the table would be
| ClientID | ClientName | CreditCardNum |
|---|---|---|
| 1 | John Doe | 7AD3C3BF888C9E885CC206D5822ABE44D665F8A47CCB1A02FF069A |
| 2 | Fred Bloggs | 4CC0646AACFAAF8D68CAACCACE1D3EE96D7DC7EB7C0F936E89829B |
| 3 | Erika Mustermann | A9043A577688BD35BD99442B4265B7E8E0AF9E3B700665E37A1CCC |
To decrypt, say, the third example above, we read the fields from the database
ID=3 CreditCardNum=A9043A577688BD35BD99442B4265B7E8E0AF9E3B700665E37A1CCCWe construct the IV as follows
IV = [random 8 bytes] || [ID padded to 8 bytes] = A9043A577688BD350000000000000003and the ciphertext is
CT=BD99442B4265B7E8E0AF9E3B700665E37A1CCC
so the input to the decrypt function is
KEY=000102030405060708090A0B0C0D0E0F IV =A9043A577688BD350000000000000003 CT =BD99442B4265B7E8E0AF9E3B700665E37A1CCCproducing the output in hex
PT =313233342D353637382D393738392D30313234which when decoded to ASCII text gives the credit card number:
3456-7898-9789-0124
Any comments, feedback, questions to our email or use our Contact Page.
This page last updated: 26 May 2008
Copyright © 2008 D.I. Management Services Pty Limited ABN 78 083 210 584,
Sydney, Australia.
All rights reserved.
<www.di-mgt.com.au>
<Cryptography Page>