# Secure Channel We have learned how to protect individual messages with cryptography. Now we protect entire conversations. **Threat:** A Dolev–Yao attacker. **Harm:** The contents of a conversation can be learned (violating confidentiality) or changed (violating integrity) by the attacker. **Vulnerability:** The conversation is held over a communication channel that is controlled by principals other than the sender and receiver. **Countermeasure:** Cryptography, including encryption, MACs, and hashes, and some new techniques that we will introduce. What is a "secure channel"? As we're using the term, a *channel* is a means for bidirectional communication between two principals, who we'll call Alice and Bob. We think of Alice as the initiator of the conversation, though of course Alice and Bob might well have multiple conversations going in parallel in which either one of them might be the initiator. So "Alice" and "Bob" here are really roles rather than identities. The communication might occur spatially over a network, or temporally over a storage device. In the latter case, a principal might even have a conversation with itself. As for *secure*, we want to achieve these goals: 1. The channel does not reveal anything about messages except for their timing and size. (Confidentiality) 2. If Alice sends a sequence of messages m1, m2, ... then Bob receives a subsequence of that, and furthermore Bob knows which subsequence. And the same for Bob sending to Alice. (Integrity) We do not attempt to achieve any kind of anonymity or availability. Nor do we attempt to defend against *traffic analysis*: leakage of information based on who is communicating with whom and how much. ### Message numbers To help ensure the integrity property, we use *message numbers* aka *sequence numbers*. Every message that Alice sends is numbered 1, 2, 3, and so forth. Bob keeps state to remember the last message number he received; he accepts only increasing message numbers. The same goes for Bob sending messages to Alice. So each principal keeps two independent counters: one for messages sent, another for messages received. What happens if Bob detects a gap? e.g. he receives 1, 2, 5, but 3 and 4 are missing. Maybe Mallory deleted messages 3 and 4 from network, or maybe she detectably changed 3 and 4, causing Bob to discard them. In either case, the channel is under active attack. This would be a good time to panic. Bob aborts the protocol, produces appropriate information for later auditing, and shuts down the channel. Note that we assume the underlying network transport protocol (e.g., TCP) is itself guaranteeing in-order message delivery and attempting to retry messages that are dropped. If we wanted to build atop an unreliable transport protocol (e.g., UDP), we'd have to make the secure channel itself more complicated. That would be in tension with Economy of Mechanism. Message numbers are usually implemented as a fixed-size unsigned integer, e.g., 32 or 48 or 64 bits. What if that `int` overflows and wraps back around to 0? The conversation must stop at that point, though of course the principals could start a new conversation. Otherwise, Mallory could begin replaying messages from earlier in the conversation. ### Authenticated encryption To help ensure both the confidentiality and integrity properties, we use authenticated encryption. Let's assume that Alice and Bob already share a session key k for this conversation; we'll address how they get that key later. For now, note that a single key isn't good enough: authenticated encryption will require both an encryption key and a MAC key, and under the principle that every key should have a unique purpose we should even have separate keys for sending from Alice to Bob and for sending from Bob to Alice. So we really need four keys: 1. kea: Encrypt Alice to Bob 2. keb: Encrypt Bob to Alice 3. kma: MAC Alice to Bob 4. kmb: MAC Bob to Alice **Key derivation.** How can we *derive* new keys from the one we already have? We use a cryptographic hash function H: 1. kea = H(k, "Enc Alice to Bob") 2. keb = H(k, "Enc Bob to Alice") 3. kma = H(k, "MAC Alice to Bob") 4. kmb = H(k, "MAC Bob to Alice") Hashing destroys any structure in its input, producing random looking output, which can be truncated to the appropriate size for whatever Enc or MAC scheme we're using. Because of the collision-resistance property of H, it's unlikely any of the four keys will turn out to be the same. And because of the one-way property of H, even if one of the four keys ever leaks to the adversary, it's hard to invert the hash to recover k and get the other keys. There is an issue, though, with whether the output of H is compatible with the keys that are produced by the Gen algorithms. For most block ciphers and MACs, this turns out not to be a problem: they happily take any uniformly random sequence of bits of the right length as keys. But for (deprecated) DES, it is a problem, because there are *weak keys* that DES's Gen would reject. It's easy to check for those, though. For many asymmetric algorithms, the output of a hash would not work as a key, because the keys must satisfy certain algebraic properties that depend on the number theory involved. **Algorithms.** We've seen three ways to construct authenticated encryption: Enc and MAC, Enc then MAC, MAC then Enc. Let's unify all with a pair of algorithms: * AuthEnc(m; ke; km): produce an authenticated ciphertext x of message m under encryption key ke and MAC key km. * AuthDec(x; ke; km): recover the plaintext message m from authenticated ciphertext x, and verify that the MAC is valid, using ke and km. Abort if MAC is invalid. We could even provide just a single key argument to both, and use what we know about key derivation to derive separate encryption and MAC keys. **Protocols.** For A to send a message to B: ``` 1. A: increment sent_ctr; if sent_ctr overflows then abort; x = AuthEnc(sent_ctr, m; kea; kma) 2. A -> B: x 3. B: i,m = AuthDec(x; kea; kma); increment rcvd_ctr; if i != rcvd_ctr then abort; output m ``` And for B to send a message to A: ``` 1. B: increment sent_ctr; if sent_ctr overflows then abort; x = AuthEnc(sent_ctr, m; keb; kmb) 2. B -> A: x 3. A: i,m = AuthDec(x; keb; kmb); increment rcvd_ctr; if i != rcvd_ctr then abort; output m ``` ### Key establishment How can Alice and Bob arrange to share a key k? They run a *key establishment protocol*. In these protocols, a *user* is a principal who will use the generated session key for further communication; other principals might be involved but won't learn or use the key. A *key transport protocol* is used to generate a session key by **one** principal then transfer it to all users. A *key agreement protocol*, on the other hand, generates a session key as a function of inputs from **all** users and transfers it to all users. Boyd (1993) proved a theorem showing that it is impossible to establish secure channels between principals who do not already (i) share a key with each other, or (ii) separately share a key with a trusted third party, or (iii) have the means to ascertain a public key for each other. In other words, you can't get something for nothing. Here, let's build a key transport protocol using assumption (ii): Alice and Bob already share a key with a trusted server who gets to pick the session key. This seemingly small task turns out to be remarkably difficult. **Assume:** A trusted server S with whom A shares long-term key kAS and B shares long-term key kBS. **Output:** A new session key kAB generated by S (who can then immediately forget it) **Security goals:** * Goal 1. Only A and B (and S) know kAB. (Confidentiality) * (more to come...) **Protocol 1.** ``` 1. A -> S: A, B 2. S -> A: kAB 3. A -> B: A, kAB ``` Protocol 1 obviously doesn't satisfy the confidentiality goal. The adversary trivially learns kAB. **Protocol 2.** ``` 1. A -> S: A, B 2. S -> A: Enc(kAB;kAS), Enc(kAB;kBS) 3. A -> B: A, Enc(kAB;kBS) ``` Protocol 2 achieves the confidentiality goal. But consider the following abuse of the protocol by Mallory: ``` 1. A -> S: A, B 2. S -> A: Enc(kAB;kAS), Enc(kAB;kBS) 3. A -> M: A, Enc(kAB;kBS) 3'. M -> B: C, Enc(kAB;kBS) ``` In step 3', C is any principal identifier other than A. Now B believes he shares kAB with C rather than A. That's clearly not what we intended to have happen. So let's add a new security goal: * Goal 2: Users associate the shared key with the correct principal identities. (Integrity) Here's another way that Mallory can abuse the protocol: ``` 1. A -> M: A, B 1'. M -> S: A, M 2. S -> M: Enc(kAM;kAS), Enc(kAM;kMS) 2'. M -> S: Enc(kAM;kAS), Enc(kAM;kMS) 3. A -> M: A, Enc(kAM;kMS) ``` Now M knows the shared key, violating Goal 1, and A believes the key is shared with B rather than M, violating Goal 2. **Protocol 3.** Let's use authenticated encryption to prevent the two attacks we just saw. ``` 1. A -> S: A, B 2. S -> A: AuthEnc(B,kAB;kAS), AuthEnc(A,kAB;kBS) 3. A -> B: AuthEnc(A,kAB;kBS) ``` Now M cannot change the messages as she did before. But there are still ways M can abuse the protocol. For example, M could *replay* messages from old executions of the protocol: ``` 1. A -> M: A, B 2. M -> A: AuthEnc(B,old_kAB;kAS), AuthEnc(A,old_kAB;kBS) 3. A -> B: AuthEnc(A,old_kAB;kBS) ``` This attack could result in A and B replaying an old conversation (perhaps causing A to repeat an order for goods, or repeat a payment, etc.). Maybe M has even cracked old_kAB in the meantime, thus allowing her to manipulate the new conversation. Neither of those is desirable. So let's add another security goal: * Goal 3: The session key is *fresh*. (Integrity) We'll achieve that goal by employing nonces as part of a *challenge–response* protocol. **Protocol 4.** [Needham and Schroeder 1978] ``` 1. A -> S: A, B, nA 2. S -> A: AuthEnc(B, nA, kAB, AuthEnc(A, kAB; kBS); kAS) 3. A -> B: AuthEnc(A, kAB; kBS) 4. B -> A: AuthEnc(nB; kAB) 5. A -> B: AuthEnc(nB-1; kAB) ``` Here, nA and nB are unique nonces chosen by A and B. This is a well-known protocol that is the grandfather of many others. One weakness with it is that, if kAB is ever disclosed to M, then M could use it to start a new conversation with B simply by resuming the protocol at step 3. **Protocol 5.** [Denning and Sacco 1981] One way to defend against that attack on Needham–Schroeder is by assuming synchronized clocks and using timestamps: ``` 1. A -> S: A, B 2. S -> A: AuthEnc(B, tS, kAB, AuthEnc(A, tS, kAB; kBS); kAS) 3. A -> B: AuthEnc(A, tS, kAB; kBS) ``` Here, tS is the time at S's local clock when it constructs message 2. A and B must reject any messages that are not within tS±δ for some bound δ, which might be on the order of seconds, minutes, or hours. **Protocol 6.** [Bauer et al. 1983] Another way to defend against the attack on Needham–Schroeder is by using nonces contributed by both users to the server: ``` 1. B -> A: B, nB 2. A -> S: A, B, nA, nB 3. S -> A: AuthEnc(B, nA, kAB; kAS), AuthEnc(A, nB, kAB; kBS) 4. A -> B: AuthEnc(A, nB, kAB; kBS) ``` **Lessons learned:** Designing even a simple cryptographic protocol is hard. The attacks aren't obvious, nor are the security goals. We ended up with three; there are many more contemplated in literature.