|All of the information here stems from direct personal experience and knowledge, and extends only briefly out into hypothetical land.|
About two years ago, I took it upon myself to write a secure (encrypted) network chat program. The reasons behind this are varied, but can be summed up neatly as one general reason; I was chatting in various environments where the topic of conversation could have caused me friction in that environment.
I did my fair share of internet research, was active on the Cypherpunks mailing list, and purchased my obligatory copy of Applied Cryptography 2nd ed.
Please take note. There is no substitute for the real thing. Don't think for a second after reading this minor text that you've become a crypto guru. Do yourself a favor and buy the book. Read it. Fifty times. A year.
I wanted to build something that would be provably secure and easy to use, while remaining robust and extendable.
Contrary to what you might be thinking, using provably secure cryptography is easier than the other two goals. There is a wealth of information out there about every major cipher, and most of the minor ones too. The math behind them is solid. Barring a staggering advance in number theory, current ciphers will be as strong a hundred years from now as they are today. Computers will get faster, and so key sizes will increase.
I decided to make the application a peer to peer system, and wrestled with the decision of TCP vs. UDP for the transport. TCP is reliable and predictable. UDP is unreliable, but requires less system resources, and so would better scale in a large p2p environment.
In the end I opted for TCP for several reasons, most of them security related.
First, there are several different "modes" most symmetric (symmetric ciphers are secret-key ciphers. asymmetric ciphers are public-key ciphers.) block ciphers can operate in.
The simplest mode is ECB which stands for Electronic Code Book. ECB is a simple block mode with no feedforward or feedback mechanisms. This means that any block B you encrypt with your key K will always encrypt to the same ciphertext block C. Blocks can be sent and received out of order, there is no need to make sure that you are "in sync" with the other end of the communication. This mode is ideal for a transport like UDP, which can both drop packets without anyone knowing as well as recieve them out of order. That is about as far as the benefits go.
ECB mode does not weaken the cipher per se, but it does weaken the security of the application using it. The more often the same plaintext is encrypted with the same key, the more information a given attacker can gain.
ECB is also vulnerable to the "block replay" attack. Since the same plaintext always encrypts to the same ciphertext when using the same key, ciphertext blocks can be recorded and "replayed."
An example would be a malicious user sniffing his bank network, then depositing a check and watching it travel across the network. If the transaction were encrypted with ECB, it is possible that the user could record the encrypted packet, and then resend it as many times as he likes, thus causing many fraudulent money transfers to take place.
If you decide to use ECB it is important that you never use the same key across different sessions. Once the session is complete, generate a new key for the next one.
The next mode is CBC or Cipher Block
Chaining. In this mode, every plaintext block is XORed with the previous ciphertext block before it is encrypted. This eliminates replay attacks as long as more than one block is sent, and also causes every plaintext block after the first to encrypt to numerous different ciphertext blocks, depending on where they occur in the stream.
CFB or Cipher Feedback Mode is an attempt to resolve some of the problems with CBC. CFB is a stream cipher, meaning it operates on bits instead of blocks. CFB has problems as well, notably, if the attacker knows the plaintext in a given ciphertext block he can change those bits at will to have then decrypt to whatever he wants. The blocks after the compromised one will be garbage, garbled and out of sync, but the damage has been done.
OFB is the last "normal" mode to consider. Output Feedback Mode is used where errors cannot be allowed to propagate. In other modes a single bit error in transmission will cause anything from a single block to the entire stream being damaged and unrecoverable. In OFB, a single bit error affects only that single bit.
Ok enough with that bit of the primer. In my program, I opted to use CBC. It has all the properties of a good strong mode, and while being very sensitive about errors I figured that TCP would cover that end for me.
Now I was ready to make my first mistake. I decided it would be gee-whiz-bang neato to use multiple, randomly-shifting ciphers in my code. Each packet it sent had an unencrypted header with a single number indicating which cipher was being used on that message.
This error was compounded by using the same secret key for each cipher during the session.
If I had stuck with this design instead of rewriting the application, and a weakness was found in any of the ciphers I was using, it would only take one block encrypted with that cipher to bring my whole system to its knees. The weak cipher exploited, my key could be recovered, and every other block in every other cipher would be easily decrypted.
This is a lesson to be learned, one you should never forget. Cryptographic ciphers are not money. Multiplying a bunch of them together in the hopes that if one fails, the other will protect you, is a foolish way to go. The simple fact is, using multiple ciphers opens you to multiple possible weaknesses in both design and implementation.
That said, the collory is also true. Adding a bunch of encryption cycles with a given cipher (encrypting your plaintext, then encrypting that, then encrypting that.. and so on) is equally foolish, usually even if you use different keys for each round. If you use the same key each round, you're sunk. To illustrate, we'll look at the simplest cipher there is; the XOR.
Assume we want to encrypt the value 0x8104 and our key is 0x53. We xor each block of the plaintext by the key, the result is our cyphertext.
0x8104 XOR 0x5353 = 0xD257
Now, we naively mean to double the strength of our system, so we pick a new key 0x27 for example, and run through it again.
0xD257 xor 0x2727 = 0xF570
If you don't already see the problem, it's that our cipher is a group (some are) and since groups are always governed by the rules of arithmetic, most importantly the rules of associative/distributative/multiplicative properties.
Our "doubly secure" system has just created a weakness, in that it gives us a false sense of security. The key 0x74 will now decrypt our message in a single pass.
0xF570 xor 0x7474 = 0x8104
You can imagine, adding even 20 more iterations isn't going to change this situation; there will always be a single key (that we don't even know, being blinded by our
system) that will decrypt the entire system in a single pass.
I said "usually" above because there are methods to make multiple-encryption workable.
To avoid the above problems, you have several options. You can change the block size for each layer, thus skewing the key bits. You can also use an outer-feedback mode where you basically do an encrypt(decrypt(encrypt(message))) before sending. The key is to encrypt the first time (inside to outside here) with key 1, decrypt with key 2, then encrypt with key 3. You must also be using a feedback/stream mode, which means ECB isn't going to cut it here. Use CBC. If you use ECB, there are cases where doing this wrong will open you up to attacks that will run in less time than brute force searches for the key.
Lastly, the same basic warning applies to getting even more clever and encrypting your message three or so times with three different ciphers. The only provable level of security here is that your system is at least as secure as the least secure cipher in the chain. As I said before, this can open you up to weaknesses you are unaware of. If one of the ciphers has a flaw, it can effectively negate the other two by reducing any cryptanalysis attempt to a known-plaintext attack.
In Applied Cryptography, Mr. Schneier says that cryptography is a "black art" and if you don't know what you're doing, you can really buy the farm. Stick to what you know, don't make assumptions.
In recent months I've totally rewritten my application. It uses diffe-hellman key exchange for deciding on a secret key, and uses one and only one cipher; Rijndael, the winner of the AES competition. I still use CBC mode because it makes the most sense for my application, and I still use TCP despite the nay-sayers who think the point is for me to have 50,000 encrypted chat users connecting to my machine.
I have no doubt that this latest version while being simpler and less obfuscated than the previous one, is a great deal more secure.
Maybe next time, I'll delve into the details of the new system.. but this article is already a great deal longer than I intended it to be.
This article was originally written by asymmetric