views:

1075

answers:

3

By default, Ruby on Rails stores session data in cookies. This has many advantages, such as the lack of need to setup any persistence layers on the server side. However, the session data is not encrypted, and the Rails app that I'm writing puts potentially sensitive data in the session. I'd like to avoid storing session data server-side if possible, and the only existing encrypted cookie store implementation for Rails seems to be abandoned, so I'm writing my own encrypted cookie store.

The Rails cookie session store works as follows:

  1. It serializes the session data into a byte string.
  2. The serialized data is converted to base64.
  3. The base64 data is appended by an HMAC. Rails requires the HMAC secret key to be at least 30 bytes; by default Rails generates a 128 byte random string as secret key, obtained from /dev/urandom. The default hashing algorithm used for the HMAC is SHA-1.
  4. The base64 data + HMAC is sent to the HTTP client as a cookie.
  5. When the client performs a request, Rails first checks whether HMAC verification succeeds. If not then it will silently discard the session data in the cookie, as if the user didn't sent any session data at all. This also means that if the administrator changes the HMAC secret key, then all old sessions are automatically invalidated.
  6. The base64 data is de-base64'ed and unmarshalled into Ruby data structures.

The encrypted cookie session store that I'm writing subclasses the normal cookie store class. It works as follows:

  • It inserts a step 3.5: after step 3 it will encrypt the data.
  • It modifies step 5: before checking for the HMAC, it decrypts the data.
  • The encryption algorithm is AES-256 in CFB mode. I understand that EBC will expose repeating patterns.
  • The code requires the administrator to specify an encryption key of exactly 32 bytes. The encryption key is not hashed. By default it suggests a randomly generated encryption key of exactly 32 bytes, obtained from /dev/urandom.
  • It also requires the administrator to specify an initialization vector of exactly 16 bytes. The IV is not hashed, and by default it suggests a randomly generated IV, obtained from /dev/urandom.

The reason why I HMAC before encrypting (instead of HMAC after encrypting) is because I want to be able to detect changes to the encryption key or the IV. I want the software to automatically invalidate old sessions if the encryption key or IV is changed. If I HMAC after encrypting then HMAC verification will pass if I change the encryption key or IV, which is undesirable.

Is my approach secure? If not, then what's lacking?

A few notes:

  • I want to use CTR, as recommended by http://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html, but unfortunately OpenSSL (which is included in the Ruby standard library) doesn't support CTR, and I don't want to require my users to install separate third party crypto libraries.
  • I want to use SHA-256 for the HMAC. daemonology.net recommends against SHA-512 because of potential "side-channel attacks on 32-bit systems" (whatever that means). However, SHA-256 as hashing algorithm for HMAC is not supported on every platform. Most notably, Ruby as shipped on OS X doesn't support HMAC+SHA256. I want my code to work out of the box on OS X as well.
+1  A: 

I'm not sure I'm reading you 100% correctly, but if I am, you seem to be missing the point of using an IV. You seem to plan to have a secret IV which you will use for every cookie.

There's no need for IVs to be secret. Furthermore, you should never reuse the same IV with the same key.

Other than that, I don't see any significant flaws. Which doesn't imply that they don't exist.

oggy
As far as I know the IV is meant to be a seed for CFB data randomization process. And yes, the current approach keeps the IV secret because there's no need to reveal it. The encryption key and IV are used to encrypt every cookie. This shouldn't be a problem, right?
Hongli
@Hongli I think What oggy means is that you should generate an unique IV for every cookie and prepend it (unencrypted) to that cookie.
Inshallah
@Hongli Your IV should not be generated by /dev/urandom because that can lead to the same IV being used for two cookies.
Inshallah
@Hongli: If you use a fixed IV you've essentially turned your CFB into ECB. It's even worse with CTR which would be rendered completely useless by using a fixed IV.
oggy
@oggy: Ah, right. Is using /dev/urandom to generate a new IV acceptable? Inshalla says that it's possible that two cookies get the same IV, but the IV is 16 bytes so I don't think that's very likely. If /dev/urandom is not a good choice then is a global counter a good idea?
Hongli
@Hongli:Sorry, I got CFB and CBC mixed up there. Reusing IVs would turn CBC into a glorified EBC, but would seem to *break* CFB under adaptive chosen-plaintext attacks. /dev/urandom would probably be more practical for IVs than /dev/random. Since the size of the IV is finite, eventually you're going to run into collisions anyhow, and /dev/urandom should be OK unless NSA wants your head. Global counter (asumming you mean a simple incremental counter) is a terrible idea.I'm hoping you're going to use this in conjunction with secure (HTTPS) cookies?
oggy
A: 

As oggy already pointed out, you should generate a unique IV and add it to each cookie. To generate a unique IV, /dev/urandom is not good enough since it is possible that it will generate the same IV for two separate cookies.

Although I'm not an expert I think one valid method to generate a unique IV is to encrypt a counter, that is incremented for each cookie, with a key that is not the same as the key used to encrypt the cookie data with.


Added much later:

There is another reason, which I have only thought of later, why it may not be such a good idea to use /dev/(u)random exclusively for IVs in the scenario that the OP outlined. In the manpage random(4), it says that users should be economical in their use of /dev/urandom, or they might degrade the quality of randomness for other users of the device. Since the quantity of data read is proportional to client-requests, you have to assume that a large amount of randomness will eventually be read in the OP's scenario.

Key-generators will probably be reading from /dev/random, which blocks, but other more legitimate users of these devices (more interested in randomness) will have their randomness degraded. Well, it's not so good to block key generators either.

So, since the OP doesn't want to depend on 3rd party libraries so much, he can reuse the AES encryption functions, using them in counter mode as I've outlined above (also in the wikipedia article under the heading Designs based on cryptographic primitives.

Inshallah
What do you mean you say /dev/urandom isn't good enough since "its possible" to generate the same 2 IVs. Can you quantify its possible into actual odds? OpenSSL uses /dev/urandom by default to seed its random number generator. So where everyone in this question is getting the notion that /dev/urandom is not safe is beyond me...
James
@James: I can see that /dev/urandom is a practical way to do it, and that it's *probably* also very secure. However, there is a conceptual difference between a cryptographic nonce and a *very* random number(s). Both have their use, and although a cryptographic nonce is best suited as an IV, you wouldn't want to use it where *true* random numbers are necessary (the same number never appearing twice isn't very random :). You said that OpenSSL uses /dev/urandom to seed its random numer generator, not that it uses it as source for its IVs; do you know whether OpenSSL actually uses non-nonce IVs?
Inshallah
+1  A: 

OpenSSL doesn't need a seperate CTR mode. To implement CTR mode, you keep a block-sized counter and encrypt that counter in ECB mode. You then XOR the encrypted counter with the plaintext block and increment the counter. Decryption is the same process (so the cookie must contain its initial counter value). You must never re-use the same counter value with the same key, so ensure that the counter is only ever reset if the key is changed.

caf