requesting comments and questions. later versions of this draft may be found under:
http://felloff.net/usr/cinap_lenrel/newticket.txt Abstract: The goal of this crypto scheme is to replace DES in the Plan 9 authentication and to augment the authentication server with an authenticated key exchange to prevent offline dictionary attacks on the user's secret key. We propose a new protocol named dp9ik (Diffie-Hellman Plan 9 Intermediate Key) as an alternative to p9sk1 that uses the new authsrv capabilities and can be negotiated in p9any. Problem: The user's secret key is derived from a low entropy password that is prone to dictionary attacks. With the old authserver protocol, it is easy for an attacker to just try to decrypt tickets with a dictionary of DES keys. The dictionary can be precomputed once and will then work forever for all users as the key is only dependent on the password. Tickets contain known plaintext that makes it easy to check if decryption with a guessed key was successfull. Also due to the small 56-bit DES key size it is possible to bruteforce the key with modest computing resources. To address the small key size of DES, we replace the DES cipher with 128-bit AES, introducing a bigger 128-bit key called the aeskey that is derived using an expensive pbkdf2 key derivation function. Ticket encryption is changed to use 128-bit AES-CBC with hmac_sha2_256 for message authentication. We add an authenticated elliptic curve Diffie-Hellman key exchange (AuthPAK) at the beginning of a authsrv session to generate randomized intermediate keys (pakkey) that are used to encrypt/decrypt the tickets instead of using the user's secret keys directly. The public keys are derived in a way that to complete the exchange requires the knowledge of the user's secret leaving the attacker with only one password guess per ticket request making offline bruteforce impossible. aeskey[16]: The 128-bit aeskey is derived from the password using pbkdf2_hmac_sha1 [rfc2898], with the salt string "Plan 9 key derivation" (not including terminating null) and iteration count 9001. In addition to deriving the pakhash (see below), the aeskey is also used for encrypting the keyfs user database on the authentication server, tho details on this is beyond the scope of this document. The aeskey is be stored along with the old DES key in nvram and keyfs. pakhash[32]: The 256-bit pakhash is used as the basepoint G on curve25519 for the elliptic curve Diffie-Hellman exchange. It is derived from the aeskey and the user name (id) so that each user gets a unique pakhash even when different users share the same password. Curve25519 scalars and compressed curve points are represented as 32 byte arrays in little endian order. info = "Plan 9 AuthPAK hash"; salt = sha2_256(user); x = hkdf_hmac_sha2_256(salt, info, aeskey); x[31] = 0x40 | (x[31] & 0x7f); // set bits 254-255 to 01 to prevent timing attacks pakhash = curve25519(x, 9); This calculation only needs to be done once and is reused in all authentication sessions of the same user. The pakhash can be stored instead of the aeskey or precomputed by keyfs to avoid some computational costs on the authentication server. pakkey[32]: With the pakhash, two parties generate a new curve25519 Diffie Hellman shared secret Z in the following way: mkkey(){ // generate 32 random bytes x = random() // curve25519 secret keys are defined as: // n ∈ { 2²⁵⁴ + 8{0,1,2,3,...,2²⁵¹-1} } // - Use a fixed position for the leading 1 in the secret key. // (timing attacks) // - Multiply the secret key by a small power of 2 to account for // cofactors in the curve group and the twist group. // (small subgroup attacks) x[0] &= ~7; x[31] = 0x40 | (x[31] & 0x7f); return x } G = pakhash (base point) xa = mkkey() (secret key) Ya = curve25519(xa, G) (public key) G = pakhash (base point) xb = mkkey() (secret key) Yb = curve25519(xb, G) (public key) Exchange the public keys Ya and Yb, then both sides calculate: Z (shared secret) = curve25519(xb, Ya) = curve25519(xa, Yb) = curve25519(xb, curve25519(xa, G)) = curve25519(xa, curve25519(xb, G)) The shared secret Z is then hashed to get the final pakkey: info = "Plan 9 AuthPAK key" salt = sha2_256(Ya | Yb) pakkey = hkdf_hmac_sha2_256(salt, info, Z) Note that we use a hash of the concatenation of the public keys Ya|Yb as the salt to the key derivation function so that any manipulation on the public keys in the message exchange causes a pakkey mismatch. Client and server both establish new pakkeys with the authentication server before each ticket request. Dp9ik and authsrv protocol: Dp9ik is is mostly the same as p9sk1, but at the beginning there is the new AuthPAK (type 19) message sent to the authentication server to exchange curve25519 public keys YAs/YBs and YAc/YBc and derive the pakkeys. Ks and Kc are replaced by the pakkeys Zs and Zc. When using AuthPAK, aes encrypted tickets are used as described later. The expression K{M} means that message M is encrypted with key K CHc clients challenge CHs servers challenge YAs servers->as AuthPAK public key YBs as->server AuthPAK public key YAc client->as AuthPAK public key YBc as->clinet AuthPAK public key Zs servers pakkey Zc clients pakkey Kn ticket key, as random nonce RNs servers random nonce RNc clients random nonce /* ticket request (dp9ik) */ C->S CHc S->C* AuthPAK, IDs, DN, CHs, -, -, YAs C->A* AuthPAK, IDs, DN, CHs, IDc, IDr, YAc, YAs A->C* YBc, YBs C->A AuthTreq, IDs, DN, CHs, IDc, IDr A->C Zc{AuthTc, CHs, IDc, IDr, Kn}, Zs{AuthTs, CHs, IDc, IDr, Kn} C->S* YBs, Zs{AuthTs, CHs, IDc, IDr, Kn}, Kn{AuthAc, CHs, RNc} S->C Kn{AuthAs, CHc, RNs} The AuthPAK message is used in other authsrv protocols as well, so we give two examples here: /* password change */ C->A* AuthPAK, IDc, DN, -, -, -, YAc A->C* YBc C->A AuthPass, IDc, DN, CHc, IDc, IDc A->C Zc{AuthTp, CHc, IDc, IDc, Kn} C->A Kn{AuthPass, old, new, changesecret, secret} A->C AuthOK or AuthErr, 64-byte error message /* auth chal */ S->A* AuthPAK, IDs, DN, -, -, -, YAs A->S* YBs S->A AuthChal, IDs, DN, CHs, IDs, IDc A->S AuthOK, challenge S->A response A->S AuthOK, Zs{AuthChal, IDs, DN, CHs, IDs, IDc, Kn}, Kn{AuthTs, CHs} When using AuthPAK, tickets and authenticators are generated as follows: info = "Plan 9 ticket encryption" salt = ticket_type (iv[16] | ek[16] | mk[32]) = hkdf_hmac_sha2_256(salt, info, pakkey) /* ticket */ ticket_type[8] = "form1 Tc" / "form1 Ts" / ... ticket_cipher[96] { ticket[96] { chal[8] /* CHs: server challenge */ cuid[28] /* IDc: uid on client */ suid[28] /* IDr: uid on server */ key[32] = random() /* Kn: client/server nonce */ } } = aes_cbc(iv, ek, ticket) ticket_mac[32] = hmac_sha2_256(mk, ticket_cipher) Client/server can exchange authenticators as usual with the random nonce from the ticket to derive encryption and mac keys: info = "Plan 9 ticket encryption" salt = authent_type (iv[16] | ek[16] | mk[32]) = hkdf_hmac_sha2_256(salt, info, ticket.key) /* authenticator */ authent_type[8] = "form1 Ac" / "form1 As" / ... authent_cipher[40] { authent[40] { chal[8] /* CHx: client/server challenge */ rand[32] = random() /* RNy: client/server random */ } } = aes_cbc(iv, ek, authent) authent_mac[32] = hmac_sha2_256(mk, authent_cipher) Instead of using ticket.key directly as the session secret, for dp9ik, we derive a 2048-bit session secret by concatenating the random nonces from the authenticators and hash them with the ticket key. info = "Plan 9 session secret"; salt = RNc | RNs secret[256] = hkdf_hmac_sha2_256(salt, info, ticket.key) This simplifies the implementation of establishing encryption as the new session secret is big enough to be used directly for TLS/SSL encryption, saving the roundtrip of exchanging extra nonces to get encryption keys. Integration and backwards-compatibility: It is not hard to see that there is no security improvement as long as the authentication server keeps sending out old DES encrypted tickets for p9sk1 with the user's deskey. In fact, the DES key and the password that it was derived from sould be considered compromized. However, we think it is neccesary to allow both protocols to run side by side for some grace period until all hostowner keys have been assigned new AES keys and software has been updated. Test vectors: passtokey(password="password") = aeskey = 15D13256344211E56C52F50C539DE223 authpak_hash(aeskey, user="user") = pakhash = C260896F7F69E21C98DDEC21FE0E25367CB3889AC6BC5E9E685B3B756D69F207 xa = C0E8A9AE21FB4D2FE02F2DCB516B6B0BB6A00D3DD78F350E106BDD7C0E4D3C67 Ya = DEC8685819C8B115E13785CD6F281F32DBB08E1FEA9623BA21008091D29CC366 xb = A8F0BCFC3894942A2ECFD63E15AA75900BA1135CB0B322FE617CF951D4344C6E Yb = F7AE88AF3E7374C123D42E9E045CDF05F54F308722535594B03FF42A1BD1A16E Z = B49A33CB1CD0CFE44141E90AA4A79701AB6142C02433FFE77FE8A52C1AC53510 sha2_256(Ya|Yb) = BA82F621BAB1F50967642E992306FAFDE7BC172C25DC2E499668441941B15CE4 pakkey = DFC0AC4AA87D73C186AB7FB4841DAB6532E60BA1C9C7B52DE77A12745414A7B2 References: [curve25519] D.J. Bernstein. Curve25519: new Diffie-Hellman speed records. [rfc2898] PKCS #5 Password-Based Cryptography Specification (PBKDF2) [rfc5869] HMAC-based Extract-and-Expand Key Derivation Function (HKDF) -- cinap