After much deliberation, I've decided to use a normal Encrypt-then-MAC scheme instead of an authenticated encryption mode such as GCM as authenticated encryption primitive. This is due to the lack of good, clean and reliable implementation across all the platforms I intend to support.
Background
This is for a multiplayer [strategy] game over TCP/IP. In particular, a player will log in and play every day (or every other day) for about 1-2 months (the typical playing time).
The reason why we use encryption at all, is to:
- prevent sabotage (replay another player's login + actions to make him waste his daily turns, modify actions in flight)
- prevent eavesdropping (stealing login credentials, monitoring actions, stealing in-app purchases)
Proposed Authenticated Encryption Primitive
- At a new connection derive the IV, encryption key and signing key using HMAC-SHA512, using a secret (lifetime - max 24 hours, randomly generated) and public client + server nonces and two salts (one for each direction). We simply split the resulting 64 bytes to form 16 byte IV, 16 byte key and 32 byte signing key.
- We set up two AES-CTR ciphers (one in each direction) using the IVs and the encryption keys derived in step 1.
For each packet to encrypt, we perform the following steps:
- Encrypt the packet with the next [plaintext number of] bytes of AES-CTR.
- Calculate HMAC-SHA256(signing key, 4 byte message number || 4 byte message length || ciphertext)
- Increase message number by 1.
- Send ciphertext || first 8 bytes of HMAC
On decryption, checking HMAC is made so that successful matching the HMAC and failing to match the HMAC should execute equally fast.
Example code:
int hmacFail = 0;
int hmacSuccess = 0;
while (hmacPtr < hmacEnd) {
if (*(hmacPtr++) != *(hmacOurPtr++)) {
hmacFail++;
} else {
hmacSuccess++;
}
}
if (hmacFail > 0) -> Raise HMAC invalid error
On failed packet authentication, the connection is dropped. On the consequent reconnect a new server and client nonce will be used, so IV and keys will not be reused. Typically a connection lives for about 5 minutes, but may stretch into 1-2 hours.
Messages are typically small (less than 100 bytes) and infrequent (usually no more than a few messages a second)
Questions
- Is something missing from the MAC?
- I'm fairly confident that reducing the HMAC to 8 bytes should be ok, as the behaviour of dropping a connection (and thus forcing a new signing key). It might be quite ok to reduce it further.
- As far as I can tell from some initial benchmarking, the dominating cost is in calculating the hash. What are my options if I want to reduce this cost while using readily available hash primitives? Going to SHA1 or even MD5 would make it faster (in my particular test, SHA1 was around 50% faster, MD5 100% faster). Any tips and tricks given the circumstances?
- Anything else I've forgotten to take into account?
TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
is probably appropriate... $\endgroup$