I'm trying to figure out a way to meet multiple business requirements that don't seem to go well in hand in terms of security:

  • store encrypted card data associated with customer accounts for recurring payments
  • store salted hashed card data to facilitate encrypted card lookup
  • store truncated card data to facilitate customer service transaction record searches

As far as I know, I can reasonably do the first 2 securely, but if I add the third item, I suddenly compromise the security of my storage. explaining link

I was wondering if anyone had had this problem before and could point me in the proper direction of a solution. I was thinking a salted hash of the truncated number could maybe do the trick. Any thoughts?

  • 1
    What exactly do you mean by "store truncated card data to facilitate customer service transaction record searches"? I'm having trouble thinking of a situation that couldn't be handled by the previous two, besides something like partial lookup by credit card number. – pdubs Jan 23 '12 at 22:42
  • You could exclude the last four digits from your salted hash an calulate it seperately. i.e. encrypt, hash salt the first part then do the same seperately with the last four digits. You would just need to seperate it when the user enters it and then store it seperate. – Bernie White Jan 23 '12 at 22:46
  • @pdubs, that was my original thought, but after discussions, our customer does not wish for their front line customer service center to be PCI-restricted, so they want to avoid asking for full credit card numbers over the phone. Asking only for part of the number would still allow agents to locate disputed transactions (with some false positives, but they say they can deal with those) while mostly being out of scope of PCI. – François Lamarre Jan 24 '12 at 14:23
  • @Bernie, yes, that makes a lot of sense to me. Thanks for the insight. – François Lamarre Jan 24 '12 at 14:25

On a credit card, you will typically find:

  • the number, typically 16 digits;
  • the expiration date (month and year, usually within the next two years);
  • the card holder name;
  • the security code (3 or 4 digits).

The trouble with publishing hashes of such information is vulnerability to exhaustive search: the attacker tries all possible combinations, until one matches the hash value. There are two ways to defeat such attacks: by keeping a large number of combinations, and by making the hashing process itself slow.

Slowing the hash requires using a salt (to prevent parallel attacks and precomputations), and making many nested iterations (as much as possible, provided that the overhead is tolerable for your system). There is an art to that; a careless handmade construction may be quite weaker. This part is very similar to what is done for passwords, so let's cut it down to the usual recommendation: use bcrypt. A good GPU can evaluate a 32-bit hash function like SHA-1 about one billion times per second; but by configuring the "cost factor" of bcrypt you can bring such a figure down to, say, 100 times per second, without overloading your own system too much.

Now let's see about the number of combinations. The card holder name can often be guessed, because in a merchant site database you will also record the user name and possibly a delivery address. The expiration date is within the next two years (or so), hence 24 possibilities. The card number usually begins with a 4-digit identifier for the bank, and ends with a checksum digit which is deterministically computed from the 15 others. Assuming the attacker will try, say, about ten distinct banks, you end up with about 1011 (aka "a hundred billions") possible card numbers. However, if the "truncated card number" contains, say, 8 digits (the first and last four digits), that number is reduced to 108 (one hundred millions).

If you use bcrypt and attack can go only at a rate of 100 tries per second, then the attacker will need an average of half a million seconds to find the card number (that's about 6 days). This may be viewed as "sufficient" because the resale value of a stolen credit card number is not high (I think it is around 10$ at most). For increased resilience, do not hash the lone number, add the expiration date (x24 on attack cost) and the security code (x1000).

So, to sum up:

  • use bcrypt with a high iteration count (and don't fumble on the salt);
  • put the expiration date and the security code in the hash input;

and you can resist the weakening induced by the storage of truncated card data.

  • does this hash function provide collision resistance? If not, then there could be not different credit card having same hash, and then we thought they are same credit cards? What about SHA256, is it collision free? – Alfred Jan 17 '16 at 7:01

@Tom's answer provides good information on how to protect the full card numbers.

I just wanted to point out that, besides best practices for security, the most important guideline for you is simply: comply with whatever PCI-DSS requires. You dont really have choice in the matter.

That said, PCI does allow you to store truncated card numbers, irrelevant of when and how you save it encrypted (and thats the more complicated part).
If I remember correctly, first 2 digits and last 4 digits are yours for the truncation, free and clear, no worries.

But of course, consult your QSA...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.