The same question was asked here in 2014, but since a lot has changed since then, I would like to get feedback from experts on the latest best practices.
We have to hash credit card numbers in order to use them as fingerprints. We strive for the same strategy that Braintree uses according to John Downey's answer (the accepted answer), I quote:
“In the Braintree API, we offer a unique number identifier for a
Credit card. This identifier is a random opaque token that is always used
This also applies to the card number stored in our system. The seed for it
The number varies depending on the retailer in our system, so you cannot compare them
them about dealers.
When a new card arrives, we look it up
Compare it to a hash + salted column. If it matches the existing one
Column we know we can return the same unique number identifier. If it
does not match any existing data set, we use a cryptographically secure one
Pseudo random number generator for creating a new unique number
Identifier and make sure that it doesn't conflict with an existing one.
So the hash + salted value never leaves our backend, but we can
They also offer a merchant the opportunity to clearly identify saved loans
The entire PCI compliance side is known and covered. We are currently dealing with the hashing part. Let us make it clear that the hashes must be vendor-specific so that salting with a vendor-specific secret key seems to be a good option (we are also aware of the problem of securely managing secret vendor keys). In this way the merchant can check whether two customers are using the same card number. So far we have considered the following alternatives:
Use HMAC with the vendor-specific key as the secret cryptographic key. For the cryptographic hash function, we have to use a cryptographically secure one, so we chose SHA3. Then, however, we found that SHA3 can act as an HMAC. As explained here, the nested HMAC construction is not required.
Hash the card number with SHA3 using the dealer-specific key. This seemed like a good option as long as you use the strongest version of SHA3 and a sufficiently long key. But then our concern about brute force attacks in the event of the theft of the database went away. After doing some research, we found that re-hashing the resulting hash times would help slow down the attacker's task and, at best, even make it impossible as long as it doesn't slow down our service. So we decided to do it and hashed our hash n times. But then we were curious about exactly how re-hashing increases security, and found that re-hashing itself is a good idea to implement your own non-standard hashing scheme without understanding what functions such a scheme needs, to be sure is not. A better option is a hashing algorithm that does a new hashing like PBKDF2, Scrypt or Argon2id.
Use PBKDF2 to derive a key from the card number salted with the merchant-specific key, set a large number of iterations, and select SHA3 as a hash function.
Use Argon2id, the current winner and recommendation of the password hashing contest, with the right parameters for time, memory, and Threads However, we are not yet sure how to set these parameters correctly. It would be very helpful to read your comments about it.
We are discussing between options 3 and 4, on the one hand PBKDF2 seems to be more widespread, on the other hand there are several resources that claim that it is no longer safe for today's hardware capacity and we strongly recommend the use of Argon2id.
What strategy would you use for this scenario? What shortcomings do you see in the previous options? Would you choose PBKDF2 or Argon2id?