/* * 認証証明には 2 種類あります。1 つは完全なネットワーク名を使用する方法で、 * もう 1 つはサーバがクライアントに割り当てたニックネーム (符号なし整数) を * 使用する方法です。クライアントからサーバへの初めてのトランザクションでは * 完全なネットワーク名を使用しなければなりません。それに対して、サーバは * クライアントにニックネームを返します。クライアントはそれ以降、サーバへの * トランザクションでニックネームを使用できます。必ずニックネームを * 使用しなければならないわけではありませんが、パフォーマンスから考えても、 * ニックネームを使用する方がよいでしょう。 */ enum authdes_namekind { ADN_FULLNAME = 0, ADN_NICKNAME = 1 }; /* * 暗号化した DES データのための 64 ビットブロック */ typedef opaque des_block[8]; /* * ユーザのネットワーク名の最大長 */ const MAXNETNAMELEN = 255; /* * クライアントのネットワーク名、暗号化された会話キー、 * ウィンドウの含まれたフルネーム。 * ウィンドウは認証証明の存在時間を示します。 * ベリファイアに示されたタイムスタンプにウィンドウを加えた時刻が * 過ぎている場合は、サーバは要求を無効として許可しません。要求を * 再送信しないために、最初のトランザクション以外は、 * タイムスタンプが以前の値より大きくないと、サーバはそれを受け入れません。 * 最初のトランザクションの場合は、サーバはウィンドウベリファイアが * ウィンドウより 1 小さい値であることを確認します。 */ struct authdes_fullname { string name<MAXNETNAMELEN>; /* クライアント名 */ des_block key; /* PK で暗号化された会話キー */ unsigned int window; /* 暗号化されたウィンドウ */ }; /* 注: PK は公開キーの略 */ /* * 認証証明はフルネームかニックネーム */ unionauthdes_credswitch(authdes_namekindadc_namekind){ case ADN_FULLNAME: authdes_fullname adc_fullname; case ADN_NICKNAME: unsigned int adc_nickname; }; /* * タイムスタンプには、1970 年 1 月 1 日の午前 0 時からの秒数を符号化 */ struct timestamp { unsigned int seconds; /* 秒数 */ unsigned int useconds; /* マイクロ秒 */ }; /* * ベリファイア: クライアント側 */ struct authdes_verf_clnt { timestamp adv_timestamp; /* 暗号化されたタイムスタンプ */ unsigned int adv_winverf; /* 暗号化されたウィンドウベリファイア */ }; /* * ベリファイア: サーバ側 * サーバは、クライアントから渡されたタイムスタンプから 1 秒少ない値を * 暗号化して返します。また、クライアントが以降のトランザクションで使用できる * ニックネームを暗号化せずに渡します。 */ struct authdes_verf_svr { timestamp adv_timeverf; /* 暗号化されたベリファイア */ unsigned int adv_nickname; /* クライアントの新しいニックネーム */ };
この暗号化手法では、2 つの定数 PROOT と HEXMODULUSを使用します。DES 認証プロトコルでは、この 2 つの定数として次の値を使用します。
const PROOT = 3; const HEXMODULUS = /* 16 進 */ "d4a0ba0250b6fd2ec626e7efd637df76c716e22d0944b88b";
この暗号化手法は次の例で説明するとわかりやすいでしょう。ここに A と B という 2 人の人が互いに暗号化したメッセージを送信するとします。Aと B はそれぞれランダムに秘密キーを生成し、このキーは誰にも教えません。秘密キーをそれぞれ SK(A) と SK(B) とします。また、2 人は公開ディレクトリにそれぞれ公開キーを示します。公開キーは次のように計算されます。
PK(A) = (PROOT ** SK(A)) mod HEXMODULUS PK(B) = (PROOT ** SK(B)) mod HEXMODULUS
** という記号はべき乗を表します。
ここで A と B は、互いに秘密キーを知らせ合うことなく 2 人の間の共通キー CK(A,B) を求めることができます。
A は次のように計算します。
CK(A, B) = (PK(B) ** SK(A)) mod HEXMODULUS
B は次のように計算します。
CK(A, B) = (PK(A) ** SK(B)) mod HEXMODULUS
上の 2 つの式から次の等式が得られます。
(PK(B)**SK(A)) mod HEXMODULUS = (PK(A)**SK(B)) mod HEXMODULUS
ここで、mod HEXMODULUS という部分を両辺から取り除いてモジュロ計算を省略し、プロセスを簡単にします。
PK(B) ** SK(A) = PK(A) ** SK(B)
次に、PK(B) を先に B が計算した値で置き換えます。PK(A) も同様に置き換えます。
((PROOT ** SK(B)) ** SK(A) = (PROOT ** SK(A)) ** SK(B)
この式は次のように書き換えられます。
PROOT ** (SK(A) * SK(B)) = PROOT ** (SK(A) * SK(B))
共通キー CK(A,B) は、プロトコルで使用されるタイムスタンプの暗号化には使用しません。共通キーは会話キーの暗号化にだけ使用し、タイムスタンプの暗号化には会話キーを使用します。これは、共通キーを使用する回数をできるだけ少なくして、共通キーが破られないようにするためです。会話時間は比較的短いため、会話キーの方が破られる心配がずっと少ないからです。
会話キーは、56 ビットの DES キーを使用して暗号化します。共通キーは 192 ビットですので、共通キーから次のようにして 56 ビットを選択し、ビット数を減らします。共通キーから中央の 8 バイトを選択し、各バイトの下位ビットにパリティを加えます。こうして、8 ビットのパリティの付いた 56 ビットキーが生成されます。