As others have said, HMAC should be a way.
An HMAC-SHA-256 with the appropriate key should:
- Avoid collisions.
- Avoid getting a credit card number from a saved value.
- Prevent execution by the same attacker calculation (on all possible credit card numbers to find the appropriate value).
But there is another very important thing:
Sincerely, you do not store credit card numbers. Even if you can be 100% sure that you are using the right encryption, you probably will not store credit card numbers anyway. What for? Firstly, because the key may be leaked.
This way you store hashes so that your credit card number cannot be recovered .... Right?
Well, if you use a simple hash, a simple rainbow table with hashes of all possible credit card numbers gives all the source data that you apparently did not store. Unfortunately. But you already knew that.
So, we try to do better. Let's say using individual salts is better, and using HMAC is the best approach we know.
Consider the following scenario:
- Take the 16-digit card number.
- The first 6 digits (bank identification number) are guessed by using several common BINs.
- The last 4 digits are displayed in the masked card number, which you can save. (You may not have saved this, which helps.)
- 1 rank is calculated (Luhn).
This results in 5 digits being rude. This is a meager 100,000 attempts.
If we used individual salts, the game is over. We can simply transfer each individual card number to an average of 50,000 attempts.
If we used HMAC, we were safe. But remember ... we prefer not to store the encrypted card numbers, because even with perfect encryption, the key may be leaked. Guess what. The HMAC key may be skipped the same way. Using the key, again, we can adjust each card number on average for 50,000 attempts. Thus, the leaked key gives us credit card numbers, just as if we saved the numbers of encrypted cards.
Thus, due to the low entropy of credit card numbers, storing hashes does not increase significant security compared to encrypted values (but PCI limits the key rotation requirements for encryption).
A bit of perspective:
Well, we assume that there is a missing key here. Extreme But then again, just like PCI, part of their arguments forbids you to store credit card numbers, so we should at least consider them.
True, I did not take into account many guesses to find the BIN. However, this should be a small constant. Or we can limit ourselves to one BIN.
Definitely a PCI auditor may be more forgiving than me.
Yes, if you do not store the masked card number, you are 10'000 points safer. It helps a lot. Use it to your advantage. However, if 50K attempts are feasible, 500M may also be feasible. This is not enough to make me consider the data safe in the context of a compromised key.
Output:
Use the HMAC-SHA-256. Understand the risk. Keep as little as possible. Protect your keys vigilantly. Spend a fortune on a hardware security module :-)