views:

623

answers:

3

I have a checkout process for a shopping cart that is currently storing credit card data in the session for retrieval once the user finalizes the purchase. The purchase process is set up such that the user inputs the credit card, views a confirmation page, and then finalizes the order. The confirmation and finalization actions are the only two actions that need access to the credit card data and to be safe all other actions should discard it.

Short of doing reflection in a base controller to check the current action the user is calling, I cannot think of an elegant way to discard the data on the disallowed requests. Additionally, if the user fails to make another request after entering the data it will linger in the session until they come back to the website- whenever that happens. One suggestion I was offered was encrypting the data into a hidden field and relying on the SSL ticket to prevent caching the markup. This seems like a fairly safe approach, but I don't much like the idea of placing the credit card data in a user-accessible location encrypted or not. Storing in the database is out because the client does not want credit card data saved.

What is the ideal approach to temporarily persisting sensitive data like credit card information across more than one page request?


Perhaps someone can tell me if this is a sufficient approach. I have set my Shopping Cart which is stored in the session to have a unique Guid generated every time the object is newed and that Guid is used as a key to encrypt and decrypt the credit card data which i am serializing with the Rijndael algorithm. The encrypted card data is then passed to the user in a hidden field and deserialized after finalize is clicked. The end result is a string much like this:

VREZ%2bWRPsfxhNuOMVUBnWpE%2f0AaX4hPgppO4hHpCvvwt%2fMQu0hxqA%2fCJO%2faOEi%2bX3n9%2fP923mVestb7r8%2bjkSVZDVccd2AJzCr6ak7bbZg8%3d

public static string EncryptQueryString(object queryString, Guid encryptionKey)
{
    try
    {
        byte[] key = Encoding.UTF8.GetBytes(ShortGuid.Encode(encryptionKey).Truncate(16));//must be 16 chars
        var rijndael = new RijndaelManaged
                           {
                               BlockSize = 128,
                               IV = key,
                               KeySize = 128,
                               Key = key
                           };

        ICryptoTransform transform = rijndael.CreateEncryptor();

        using (var ms = new MemoryStream())
        {
            using (var cs = new CryptoStream(ms, transform, CryptoStreamMode.Write))
            {
                byte[] buffer = Encoding.UTF8.GetBytes(queryString.ToString());

                cs.Write(buffer, 0, buffer.Length);
                cs.FlushFinalBlock();
                cs.Close();
            }
            ms.Close();
            return HttpUtility.UrlEncode(Convert.ToBase64String(ms.ToArray()));
        }
    }
    catch
    {
        return null;
    }
}
+2  A: 

What about using TempData? You'd need to put the value back into TempData between the confirmation and finalization actions, but at least it will be discarded with each request. Note that TempData uses the Session for storage so it's no more secure while it's being stored, but it does have the automatic removal feature. I, too, would resist storing the number on the page. I suspect that this violates the PCI rules.

Another alternative would be to store the card info in a database. If you keep it at all in your application you're probably already subject to the PCI rules anyway. Storing it in the DB makes it easier as then you only need to put the billing card id in subsequent requests. This could easily be in a hidden field.

tvanfosson
It was my understanding that TempData only stored data during redirects. If I set it in my Controlller, serve an Action, then process a request for a second action TempData is gone by that point I thought?
Nathan Taylor
TempData is stored between any two requests in the same session. It's best used for redirects but there is no technical restriction on it. The framework won't know that an incoming request is the result of a redirect or some other action.
tvanfosson
If that's the case then that does seem like a clean approach.
Nathan Taylor
Okay so I tried that and it doesn't seem to work. The TempData doesn't persist after the user posts the form back.
Nathan Taylor
It won't work if there is a redirect in between unless you repopulate the TempData.
tvanfosson
+5  A: 

The best way to handle this scenario is to use a payment service that supports two things:

  1. Authorization -> Completion semantics.
  2. Tokenization

Authorization allows you to reserve the designated charge amount at the time the payment information is received, and then Completion allows you to commit the charge to the payment batch once the payment/order is confirmed. If the order is canceled, you don't have to issue a Completion and you can also attempt to delete the authorization as well.

Regarding tokenization, most gateways that support the aforementioned method of handling payments will return a token, typically a numeric id, for the pending transaction. The token may then be handled however you wish, as it has no value to anyone without access to your authentication credentials at the gateway. This transfers the burden of safety to the gateway as well.

Storing the actual credit card information in any way other than relaying a charge to a gateway/processor is a bad idea. Beyond the problems of securing the data, this will also put your application into card information storage scope for PCI/PABP, which entails a lot of rules and regulations that you won't want to deal with in your application. I believe there is also a regulatory fee that will be imposed in the coming year for compliant applications, reputedly $10k USD. All of this is likely not worth the trouble to you or your client.

Last, during intermediate processing (in-page/event/route handlers), you can use a SecureString to hold the contents of the data until you no longer need them.

SecureString class (System.Security) @ MSDN

meklarian
A: 

What country is this based in and what credit card companies are involved? The entire approach of actually sending full credit card numbers back to the client (in whatever form) makes this sound like you have not dealt with professional credit card handling (please don't take that as an insult).

Is your client willing to run afoul of Visa/MasterCard/AMC/Discover's collective rules for online credit card processing (PCI DSS)? Your client could end being barred by the major credit card companies from doing transactions with them. In general it is a very bad idea to try rolling your own online credit card handling solution - it's worse then rolling your own cryptographic algorithm, as there can be serious fines applied to your client (fine print in their merchant agreement). A true PCI DSS solution requires tens of thousands of dollars in certifications and audits to ensure it handles credit card data in a truly secure fashion - this is why almost everyone uses an existing online processor.

David
No offense taken, you are absolutely correct. The customer is in the United States. Obviously we don't want to have to worry about complying with the PCI DSS at all so storing any data is not ideal.
Nathan Taylor
The easiest way to avoid storing any card numbers is to use a hosted pay page and let the provider worry about handling the card info.
tvanfosson
meklarian covers the specifics of using an existing online processor - in general the more you are paying, the more the processors will allow for customization so that their page appears seamless with the rest of your site.
David