Credentials are explained in the following example:
/* * There are two kinds of credentials: one in which the client * uses its full network name, and one in which it uses its * “nickname” (just an unsigned integer) given to it by the * server. The client must use its full name in its first * transaction with the server, in which the server returns * to the client its nickname. The client may use its nickname * in all further transactions with the server. There is no * requirement to use the nickname, but it is wise to use it for * performance reasons. */ enum authdes_namekind { ADN_FULLNAME = 0, ADN_NICKNAME = 1 }; /* * A 64-bit block of encrypted DES data */ typedef opaque des_block[8]; /* * Maximum length of a network user's name */ const MAXNETNAMELEN = 255; /* * A fullname contains the network name of the client, an * encrypted conversation key and the window. The window * is actually a lifetime for the credential. If the time * indicated in the verifier timestamp plus the window has * passed, then the server should expire the request and * not grant it. To insure that requests are not replayed, * the server should insist that timestamps be greater * than the previous one seen, unless it is the first transaction. * In the first transaction, the server checks instead that the * window verifier is one less than the window. */ struct authdes_fullname { string name<MAXNETNAMELEN>; /* name of client */ des_block key; /* PK encrypted conversation key */ unsigned int window; /* encrypted window */ }; /* NOTE: PK means "public key" */ /* * A credential is either a fullname or a nickname */ unionauthdes_credswitch(authdes_namekindadc_namekind){ case ADN_FULLNAME: authdes_fullname adc_fullname; case ADN_NICKNAME: unsigned int adc_nickname; }; /* * A timestamp encodes the time since midnight, January 1, 1970. */ struct timestamp { unsigned int seconds; /* seconds */ unsigned int useconds; /* and microseconds */ }; /* * Verifier: client variety */ struct authdes_verf_clnt { timestamp adv_timestamp; /* encrypted timestamp */ unsigned int adv_winverf; /* encrypted window verifier */ }; /* * Verifier: server variety * The server returns (encrypted) the same timestamp the client gave * it minus one second. It also tells the client its nickname to be * used in future transactions (unencrypted). */ struct authdes_verf_svr { timestamp adv_timeverf; /* encrypted verifier */ unsigned int adv_nickname; /* new nickname for clnt */};
In this scheme are two constants, PROOT and HEXMODULUS. The particular values chosen for these constants for the DES authentication protocol are:
const PROOT = 3; const HEXMODULUS = /* hex */ "d4a0ba0250b6fd2ec626e7efd637df76c716e22d0944b88b";
The way this scheme works is best explained by an example. Suppose there are two people, A and B, who want to send encrypted messages to each other. A and B each generate a random secret key that they do not disclose to anyone. Let these keys be represented as SK(A) and SK(B). They also publish in a public directory their public keys. These keys are computed as follows:
PK(A) = (PROOT ** SK(A)) mod HEXMODULUS PK(B) = (PROOT ** SK(B)) mod HEXMODULUS
The ** notation is used here to represent exponentiation.
Now, both A and B can arrive at the common key between them, represented here as CK(A,B), without disclosing their secret keys.
A computes:
CK(A, B) = (PK(B) ** SK(A)) mod HEXMODULUS
while B computes:
CK(A, B) = (PK(A) ** SK(B)) mod HEXMODULUS
These two computations can be shown to be equivalent: (PK(B)**SK(A)) mod HEXMODULUS = (PK(A)**SK(B)) mod HEXMODULUS. Drop the mod HEXMODULUS parts and assume modulo arithmetic to simplify the process:
PK(B) ** SK(A) = PK(A) ** SK(B)
Then replace PK(B) by what B computed earlier and likewise for PK(A).
((PROOT ** SK(B)) ** SK(A) = (PROOT ** SK(A)) ** SK(B)
which leads to:
PROOT ** (SK(A) * SK(B)) = PROOT ** (SK(A) * SK(B))
This common key CK(A,B) is not used to encrypt the timestamps used in the protocol. It is used only to encrypt a conversation key that is then used to encrypt the timestamps. This approach uses the common key as little as possible, to prevent a break. Breaking the conversation key is a far less serious compromise, because conversations are comparatively short lived.
The conversation key is encrypted using 56-bit DES keys, yet the common key is 192 bits. To reduce the number of bits, 56 bits are selected from the common key as follows. The middle-most 8 bytes are selected from the common key, and then parity is added to the lower-order bit of each byte, producing a 56-bit key with 8 bits of parity.