Any successful e-commerce company invariably has to become PCI compliant. The Payment Card Industry (PCI) is a credit card industry consortium that sets standards and protocols for dealing with credit cards. One of these standards targeted at merchants is called the PCI-DSS, or PCI Data Security Standards. It is a set of rules for how credit card data must be handled by a merchant in order to prevent that data from getting into the wrong hands.
Groupon has designed a PCI solution called Tokenizer – a purpose-built system, built with performance, scalability, and fault-tolerance in mind as a way to streamline this process and we are seeing great results.
The PCI Security Standards Council certifies independent auditors to assess merchants’ compliance with the PCI-DSS. Companies that handle more than six million credit card transactions in a year are held to the highest standard, known as PCI Level 1.
When faced with becoming PCI-compliant, most e-commerce businesses that need access to the original credit card number usually follow one of two typical paths:
- Put all of their code and systems under PCI scope. Development velocity grinds to a halt due to the order-of-magnitude increase in overhead compared with typical agile processes.
- Carve up their app into in-scope (checkout page, credit card number storage, etc.) and out-of-scope (everything else) portions. The once-monolithic application becomes a spaghetti mess of separation and interdependency between very different systems. The “checkout” page stagnates and becomes visually inconsistent with the rest of the site due to the order-of-magnitude increase in overhead noted above.
Groupon faced this dilemma a few years back. Prior to designing a solution, we set out our design goals:
- Have as few devices/services in PCI scope as possible.
- Make as few changes to existing services as possible.
- Maximize protection of our customers’ credit card data.
After many hours meditating at the Deer Head Center for Excellence (see the end of this post) we arrived at our solution.
Groupon’s PCI Solution
For the core tenet of the solution we would focus on the credit card data that we had to protect. Rather than re-implement our checkout application, we would shield the application from the credit card data. How would we do this? Read on…
Prior to becoming PCI compliant, users would submit their credit card information directly to the checkout application. With the PCI-compliant solution in place, there is an HTTP proxy layer that transforms the customer’s web request, replacing the credit card number with an innocuous token. The modified request is forwarded to the checkout app, who processes the request and responds to the user via the proxy. Since the proxy’s job is to replace sensitive data with a token, we call it the Tokenizer.
Enter the Tokenizer
With this architecture, our PCI footprint is minimal, flexible, and rarely needs to change. The Tokenizer is a purpose-built system, built with performance, scalability, and fault-tolerance in mind. Its only job is to find credit card numbers in specific kinds of requests, replace the credit card numbers with something else, store the encrypted credit card number, and forward the request to another system.
The Tokenizer does not render any HTML or error messages — all of that comes from the application that it is proxying to. Therefore, the user interface components are under complete control of systems that are not in PCI scope, thereby preserving the normal high iteration velocity of a modern e-commerce company.
“Yes, But…” or All About Format-Preserving Tokens
You may be asking, “So the credit card number is replaced with something else right? What is it replaced with? How can you satisfy your design goal of having minimal changes to the rest of the system?”
A credit card is defined as a numeric sequence of 13 to 19 digits. Credit card companies are assigned a number space in which they may issue cards. You may have noticed that most VISA cards start with 4, MasterCard with 5, etc.
There is a special number space that begins with “99” that is by definition unassigned. The tokens that the Tokenizer generates are 19 digits long, and begin with “99”. Because the token follows the official definition of a credit card, it is called a Format-Preserving Token.
Within the 19 digit number, we encode a token version, the original card type, datacenter that created the token, a checksum digit, a sequence number, and the original last four digits of the customer-entered credit card number.
Groupon Token Breakdown:
9911211234567891234 | || || lastfour | || |seqno(9) | || luhn | |card type(2) | version/colo cardspace
The only modification required of other systems at Groupon is that they need to recognize these tokens as valid credit cards, and look at the card type field to know the original card type, rather than running their normal pattern matching code for Visa, Mastercard, Discover, AmEx, etc. Usually this is a minor change within a CreditCard class.
Database schemas can stay the same, masked card display routines (only showing the card type and last four digits) may stay the same, even error cases can stay the same.
Since the Tokenizer has pattern matching rules for detecting card types, it can detect invalid card numbers as well. Rather than encrypting and storing invalid card numbers, the Tokenizer will generate a token that is invalid in the same way that the original input was invalid and replace the invalid card number with the generated invalid token.
For instance, if the credit card number is too short (e.g. a VISA with 15 digits instead of the typical 16) the Tokenizer will generate a token that is too short (e.g. 18 digits instead 19). If the card number the customer typed in does not pass the Luhn checksum test, then the token will not pass the Luhn checksum test. If the card is an unrecognizable type, then the token will be an unrecognizable type.
This allows all end-user messaging around card errors to be generated by the out-of-scope systems that the Tokenizer is proxying to. The checkout app will see an invalid card number, and return an appropriate error message via the Tokenizer back to the user. Less PCI-scoped code means that development teams may iterate on the product with less friction.
Most similar tokenizing systems will use a cryptographic hashing algorithm like SHA-256 to transform the card number into something unrecognizable. The issue with hashed card numbers is that the plaintext space is relatively small (numbers with well known prefixes having 13-19 digits that pass the Luhn checksum test). Therefore, reverse-engineering of hashed credit card numbers is fairly simple with today’s computers. If the hashed tokens fall into the wrong hands, the assumption needs to be that the card numbers have been exposed.
This risk can be mitigated by using a salted hash or HMAC algorithm instead, with the salt treated as an encryption key and split between layers of the system, but the downside of having a token in a very different form than the original card number remains.
Aside from the card type and last four digits, the Groupon token is not derived from the card number at all. There is no way, given any number of tokens, to generate any number of credit card numbers. The information simply isn’t there for the bad guys to use. Less information is more secure.
Closing The Loop
At this point we have described how card numbers are replaced with tokens by the Tokenizer. Now we have many backend systems that have tokens stored instead of credit card numbers. You may be wondering how we collect money from tokens. Seems like squeezing water from a rock, right?
There is a complementary, internal-only system called the Detokenizer. The Detokenizer functions in a very similar way to the tokenizer, just in reverse.
We use our payment processor’s client library to construct a request back to the processor. Usually these requests are SOAP over HTTP. In the credit card number field in the SOAP payload, the out-of-scope system will insert the token. Naturally, we cannot send this request as-is to the payment processor, since the token is meaningless to them.
“Bzzzzt! That’s not a credit card number!”
Rather than sending the request directly to the payment processor, it is sent via the Detokenizer proxy. The Detokenizer recognizes the destination of the request and looks in a specific place in the payload (the card number field) for the token. If it’s found, the token is used to look up the original encrypted card number from the database, the card number is decrypted, and it replaces the token in the original request payload.
The Detokenizer then forwards the HTTP request to the payment processor and Groupon gets paid. Make it rain, Groupon.
Groupon’s current production footprint is on the order of thousands of servers. Our global PCI scope extends to one small source code repository, a dozen application servers, four database servers, and some network gear. Development, code deploys, and configuration changes follow the regimented rules of the PCI-DSS. But since the in-scope footprint is so small, the impact on the rest of the organization is minimal to nil.
There is a very small group of developers and operations staff who maintain these systems on a part-time basis. The rest of the time, these employees enjoy working on more high-velocity projects for the company. This design has meant that Groupon can still run at the speed of a startup, even though it is compliant with these strict industry rules.
And finally, The Deer Head Center For Excellence is Groupon’s in-office cocktail bar. Put up a monitor and serve up your own Deer Head.