I have a website issue that I am addressing by storing an encryption key in a cookie. I want to describe my issue and my solution, and see if someone smarter about such things than I thinks this is a reasonable solution to my problem and/or if there is a better way to do it.
My website allows a subscriber's customers take an interview. When the interview is done, the website sends a report to the subscriber. Due to the nature of the interview, the folks taking the interview may take a long time answering the questions. Also, because the interview asks questions about the subject's medical history, I store their answers in session state (which Microsoft Azure claims to be HIPAA-compliant).
My problem is that some subjects apparently take longer than 90 minutes (the session timeout length) to answer a single question. When this happens then the website detects the lack of session state and tells the subject that their data has been lost.
So here's what I'm proposing to do to fix this problem once and for all. When the user starts the interview, I'll create an encryption key and store it in a cookie with an expiration time of 5 days. Each time the user supplies another answer to an interview question, I'll store all of the session info, including the cumulative answers, in a string, encrypt the string with the encryption key (which I'll read from the cookie), and store the encrypted string in the SQL Server database (which Microsoft does not claim to be HIPAA-compliant). Note that I also store the primary key in the user's cookie.
Now, if the website gets an answer from the user but there is no session state (i.e. the user let the session expire) then I should be able to use the information in the cookie to retrieve and decrypt all of the user answers and thus recreate all of the session info.
So, to recap, I'm creating a per-user encryption key and passing it back and forth over the HTTPS connection with each interview question and POSTed answer. I use the encryption key to store the session state in a database, but only for the purpose of rehydrating the session state if the user lets the session expire.
My (totally amateur) analysis of the security implications:
- If the SQL Database is stolen, the sensitive data (the session state blobs) is encrypted. Each blob is encrypted with a different key.
- If a user's connection to the interview is compromised (i.e. a man-in-the-middle attack) then only that user's encryption key can be stolen. But in this case the clear-text of the user's answers is probably also available to the attacker, so it really doesn't matter.
Any other security issues? Is the concept of encrypting session data with a key stored in a cookie (whose lifetime is much longer than that of the session) a reasonable way to reconstitute the session if it times out?
I was thinking about might happen if a user started the interview then closed the browser (leaving the cookie behind), and a second user got hold of the computer. Then Parth Shah posted an answer expressing concern for that very scenario. So here is my analysis:
There are several things the second user might do:
The second user might be able to resurrect the first user's session. (IE allows you to right-click its icon and select "reopen last session".) If this worked then the second user would be able to click the interview's "go back to the previous question" button and see all of the user's questions and answers. But this scenario has nothing to do with the encryption key or the cookie; it's equivalent to looking over the first user's shoulder. As far as I know, there is nothing I can do to mitigate this scenario.
The second user might find the email that contains the link the first user clicked on to start the interview. In this case, the /StartInterview "GET" action first checks to see if the user previously abandoned the interview, i.e. a valid "encryption key" cookie exists on the user's machine, and an associated encrypted "session state" blob exists in the database. If it does then the user's answers are pulled out of the encrypted blob and used to create an "interview was partially completed" report that is sent to the clinician (customer). Then the encrypted blob is deleted from the database and the cookie is removed. So, in this scenario, I go out of my way to deliver the interview results without allowing the user at the computer to see them.
The interview questions are displayed by the /Interview page, whose GET action uses session state data to figure out what page of the interview the user is on and, thus, what to render in the browser. When the user POSTs back to that page, the POST action saves the answer, fiddles with the session state variables to advance to the next question, then redirects back to the /Interview GET action. Both the POST and GET actions have code that checks to see if the session has expired and, if so, if the "encryption key" cookie exists so that the session can be transparently resurrected. So... if the second user opens the web browser and navigates to the /Interview page, the GET action will see that a new session is being created and the cookie exists, and it will dutifully resume the interview where it left off. The only mitigating factor for this "attack" is that the second user need to know to manually navigate to the /Interview page.