# Tokens We've talked about authentication of humans as involving something you know, have, or are. The prototypical example of "have" is *authentication tokens*: the human is issued a machine called a *token*, that machine becomes an attribute of the human's identity, and authentication of the human reduces to authentication of the machine. So you can think of authentication tokens as being an intermediate step between authenticating humans and authenticating machines. User convenience is an important issue. The machines being used as tokens should be small, light, require no maintenance (especially of a battery), and should cost very little. So issuing laptops isn't realistic. Rather, real-world examples include ATM cards, prox cards (like your Cornell id card), RSA SecurID tokens, and IronKey tokens. ### Local authentication **Goal:** Authenticate a human Hu to a local system L using a token T. **Threat model:** The adversary might read and replay messages but can't change them in the middle of a protocol execution. So we're considering only a limited form of man-in-the-middle (MitM) attacks. This matches well with real-world scenarios, in which wired keyboards or short-range radios (e.g., RFID) are used. **Enrollment:** At enrollment, associate identifier id_T with identifier id_Hu. The protocol we give next is an example of a *challenge&ndash;response* protocol. These are well-known from old spy movies: two agents who haven't met before authenticate each other by speaking an appropriate challenge and response, for example, > - Bond: Can I borrow a match? > - Driver: I use a lighter. > - Bond: That's better still. > - Driver: Until they go wrong. > &mdash;*From Russia with Love* Here's our first attempt at a protocol. We assume that T is preprogrammed with a fixed response to a fixed challenge: ``` 1. Hu->T: I want to authenticate to L 2. T->L: id_T 3. L: look up challenge question for id_T 4. L->T: challenge 5. T->L: response 6. L: look up id_Hu and response associated with id_T; id_Hu is authenticated if response is correct ``` Note that the human never declares her identity to either T or L. Instead, the token sends its identifier to L, and L is responsible for looking up the association between id_Hu and id_T. Also note that, although the protocol is written such that L and T send messages directly to one another over a channel, Hu might in fact be involved in implementing that channel. For example, T might display a message on a screen, and Hu might type that message into L's keyboard. But that protocol is vulnerable to replay attacks; an attacker need only eavesdrop and learn the response. Let's fix that by using unpredictable challenges and digital signatures. **Authentication with Token and Asymmetric-key Cryptography** We assume that T stores a signing key k_T for which L knows the verification key K_T: ``` 1. Hu->L: I want to authenticate with T 2. L: invent unique nonce N_L 3. L->T: N_L 4. T: compute s=Sign(N_L; k_T) 5. T->L: id_T, s 6. L: lookup id_Hu and K_T associated with id_T; id_Hu is authenticated if Ver(N_L; s; K_T) ``` This protocol fixes the replay vulnerability, because nonce N_L is different every execution. But historically, tokens don't use this protocol, in part because asymmetric-key cryptography hasn't always been available on mobile devices. There's no security reason you couldn't use it now with modern devices&mdash;for example, Android or USB flash drive. But for performance (time and power) reasons, less expensive symmetric-key cryptography seems to be preferred. **Authentication with Token and Symmetric-key Cryptography** Assume that T and L share a secret MAC key k_T: ``` 1. Hu->L: I want to authenticate with T 2. L: invent unique nonce N_L 3. L->T: N_L 4. T: compute t=MAC(N_L; kT) 5. T->L: id_T, t 6. L: lookup id_Hu and kT associated with id_T; consider id_Hu authenticated if t=MAC(N_L; kT) ``` We just introduced a key distribution problem, in that we now have to arrange for T and L to share a key. But we already have to solve the problem of physically issuing tokens; piggy-backing installation of a shared key isn't hard. A disadvantage of symmetric cryptography is that L must now store the shared key k_T. Hashing and salting that key wouldn't work, because L actually needs to know the plaintext key. Alternative methods of storage can include using secure co-processors (e.g., trusted platform modules, hardware security modules) to protect the secrecy of those keys on disk. ### Two-factor authentication There's a risk that T might be stolen. With the protocols above, a stolen T could be used by anyone to authenticate as id_Hu. We can use a second factor, a PIN (something you know), as a countermeasure: ``` 1. Hu->L: I want to authenticate with T 2. L: invent unpredictable nonce N_L 3. L->T: N_L 4. T->Hu: Enter PIN on my keyboard 5. Hu->T: pin 6. T: compute t=MAC(N_L, pin; kT) 7. T->L: id_T, t 8. L: lookup id_Hu, pin, and kT associated with id_T; id_Hu is authenticated if t=MAC(N_L, pin; kT) ``` The PINs stored by L could be salted and hashed, as long as T can also be configured to store the salt. Then the tag would be computed as `MAC(N_L, H(pin,salt); k_T)`, and L would store H(pin, salt) for each user. Alternatively, the PIN could be stored locally on the token, and the token would authenticate the human, followed by the local system authenticating the token. In this design (as well as the original), the token must be careful not to enable online guessing attacks that reveal the PIN. ### Remote Authentication **Goal:** Authenticate a human Hu to a remote system S using a token T and local system L. **Threat model:** On the channel between T and L, the adversary might read and replay messages but can't change them in the middle of a protocol execution. But on the channel between L and S, the adversary is Dolev-Yao. In this new threat model, we have to worry about the channel between L and S. So, first, we can establish a secure channel between L and S. Then we can run an adaptation of the two-factor authentication protocol. Any messages sent from T to S in the original protocol now are sent from T to L to S. **Remote Two-factor Authentication with Token and PIN** ``` 1. Hu->L: I want to authenticate with T to S 2. L and S: establish secure channel 3. S: invent unpredictable nonce N_S 4. S->L->T: N_S 5. T->Hu: Enter PIN on my keyboard 6. Hu->T: pin 7. T: compute t=MAC(N_S, pin; kT) 8. T->L->S: id_T, t 9. S: lookup id_Hu, pin, and kT associated with id_T; consider id_Hu authenticated if t=MAC(N_S, pin; kT) ``` ### Case Study: RSA SecurID Tokens <img width="25%" src="rsa_token.gif"/> The RSA SecurID token is a commercial product with an LCD display and internal clock, but with no means for input. It can compute hashes, has a secret stored on it, and is *tamper resistant*, such that it's hard to physically extract the secret. The LCD displays a *code* that changes every 60 seconds. Consider what follows to be a hypothetical protocol that could work with hardware similar to the SecurID token. The main ideas behind this protocol are (i) to replace the nonce used in remote two-factor authentication by the time at the clocks of T and S, and (ii) to use L rather than T to input the PIN. ``` 1. Hu->L: I want to authenticate as id_Hu to S 2. L and S: establish secure channel 3. L->Hu: Enter PIN and code on my keyboard 4. T->Hu: code = MAC(current time at T, id_T; kT) 5. Hu->L: pin, code 6. L: compute h = H(pin, code) 7. L->S: id_Hu, h 8. S: lookup pin, id_T, and kT associated with id_Hu; id_Hu is authenticated if h=H(pin, MAC(current time at S, id_T; kT)) ``` The clocks on T and S might not be perfectly synchronized (and messages might get delayed, etc.). So S might need to check a few different values of t in the final step, as well as store additional information about the last time T was successfully used to authenticate, and about the observed drift of T's clock; Schneider [section 5.2] provides details.