3

So, like any reasonably competent web development shop, we wear cotton gloves when we touch credit cards, and we use Braintree SecureVault to store them so we are clear of PCI Compliance issues.

However now we want to offer a free trial for our service, which pretty much relies on being able to guarantee that a given credit card is only used once for a free trial. Ideally we would be able to hash the credit card number itself to guarantee uniqueness. The problem there is that set of valid credit card numbers is small, so it's going to be easy to brute force the credit card numbers. Salting tactics are useless as far as I can see, because if someone has access to the database of hashes, they will most likely have the code as well, and thus the salting algorithm.

The best two ideas so far are:

A) Keeping the hashes isolated in a set, with no relation to their billing information. Therefore if the hashes are brute-forced, all they have is a list of credit card numbers that were used at some point in time, with no personal information or knowledge of whether it's even still valid. The main weakness here is that we do have record of the last-4 which could potentially be used to match them up to some extent.

B) Hash without the full number and deal with the false positives and negatives. Hashing on name, last-4 and expiration ought to be fairly unique. False positive is like winning the lottery, we can deal with it at customer support. False negative could be induced by modifying the name, we are not clear on what assurances we have about the precision of name matching (potentially affected both by the gateway and merchant account is my understanding), so this could open a loophole.

Thoughts? Suggestions? Battle-tested Wisdom?

gtd
  • 16,956
  • 6
  • 49
  • 65
  • Why can't you just use that Brainfree Secure Vault thingy for your trial also? – President James K. Polk Feb 10 '11 at 00:11
  • Doesn't guarantee uniqueness, ie multiple accounts can use the same credit card, which basically sinks the whole idea from a business perspective because we have real liabilities. Although this would definitely be an interesting service for their API to offer. – gtd Feb 10 '11 at 03:42

2 Answers2

1

High-level: Use Existing Payment Systems
I think that this approach -- using credit card numbers to determine if a user already has taken advantage of a free trial and should be ineligible for a subsequent free trial -- is misguided. Firstly, you will drive away potential customers by requiring a credit card upfront (which many users don't give out unless they are actually ready to buy), instead of requiring it only after the trial period has ended.

Secondly, you are reinventing the wheel. There are a plethora of "app stores" (the Chrome webstore, the Android marketplace, the iTunes app store, etc.) which provide builtin mechanisms for payment and trial periods. Using these systems will give your product increased visiblity to consumers, will offer your potential customers multiple different payment methods (making them more inclined to buy), and will also save you the hassle of implementing this mechanism yourself. Plus, users generally prefer to give out their credit card to the least number of companies possible; not only would you have to implement this complex mechanism yourself, but you would also have to get users to trust you enough to use it.

Lower-level: Implementation Details
Any hash mechanism can have collisions, hence you would still need to deal with this problem. You should obviously use full disk encryption and other best security practices with your servers. The risk of having both the database and the salting algorithm compromised at the same time can be reduced by hosting the backend database service on a separate machine from the one that hosts this code. The main vulnerability of hashing is brute force attacks. And the best way to deal with them is to just make brute forcing expensive enough that it isn't worth the attacker's while. Using a separate salt for each entry (e.g. the customer's name, the customer's zip code, etc. as part of the salt) will make using rainbow tables ineffective. Of course making the data, itself, less valuable to attackers (e.g. not including the full credit card number) is also a good way to discourage these kinds of attacks. In any case, I again advise you to take advantage of the many app stores instead of implementing this yourself.

Michael Aaron Safyan
  • 93,612
  • 16
  • 138
  • 200
  • You're being a bit too presumptuous on the high level bit. You know nothing about the business, so you're literally pulling this out of thin air. Without a credit card we can not guarantee that people don't sign up for multiple trials, and if they sign up for multiple trials then we are open to unlimited downside. I'm not sitting in a garage here. We already have millions of users and thousands of subscribers and taking payments in 12 currencies. – gtd Feb 10 '11 at 03:35
  • 1
    I like the idea of hosting the database of hashes on a separate hardened server that only accepts API requests for a single hash lookup, that's food for though, thank you. – gtd Feb 10 '11 at 03:50
0

Forgive me if I missed something, but why can't you just have a table of "UsedCreditCards" that just has a single column, which is a SHA512 hash of the credit card number and maybe the expiration date. This could not be reversed, and by keeping it in another table and not storing any other data about the code, you could easily check to see if a credit card number has been used before.

I am not sure if this would violate PCI or anything (I don't think so, but I could be wrong)

Mitch Dempsey
  • 38,725
  • 6
  • 68
  • 74
  • It can't be reversed, but it can easily be brute forced (try all combinations of credit cards and expiration dates that are in the next two or three years, hash them, and see what they collide with). If they've kept a record that associates some of the digits of the credit card number with other information, then there is still the possibility of getting a full credit card number plus relevant personal information. – Michael Aaron Safyan Feb 10 '11 at 01:00
  • 1
    @Michael - So, they would have to find the correct 16-digit credit card number, and then the correct expiration date, and that would then allow them to know that "this credit card has registered for a trail before". I Don't see how that is an issue? ... or what a person would do with that information. – Mitch Dempsey Feb 10 '11 at 01:03
  • @webdestroya, no, then they would lookup the personal info based on the credit card number, and now they have a credit card number that they can charge with. – Michael Aaron Safyan Feb 10 '11 at 01:04
  • @webdestroya, I imagine that there are rainbow tables out there that can find collisions on plain credit card numbers awfully quickly. – Michael Aaron Safyan Feb 10 '11 at 01:05
  • @Michael - A collision does not give them the exact number. A collision is "another string that has the same hash" – Mitch Dempsey Feb 10 '11 at 01:23
  • 1
    @webdestroya - you should brush up on your crypto basics. The chances that there are two valid credit card number + exp date strings that hash to the same value is infinitesimally small. The set of valid credit cards plus plausible expiration dates is pathetically small, and thus brute forcing is effectively the same as reversal, requiring a tiny amount of computing power. The right salting algorithm might mitigate slightly, but it's an uphill battle with such a small set. – gtd Feb 10 '11 at 03:47
  • this is perfectly allowable under PCI compliance. –  Jun 29 '11 at 16:43
  • This is a good idea, but it is important to include the expiration data. With just the credit card number (and not the expiration date), it will be insecure. I would also advise you to use PBKDF2 with some reasonable number of iterations instead of plain SHA512, to make attacks harder. – D.W. Aug 09 '11 at 02:08