NTLM 認証プロトコルとセキュリティサポートプロバイダ

概要

このドキュメントは、NTLM認証プロトコルと関連するセキュリティサポートプロバイダの機能について、実装を行なう際のリファレンスとなるように、上級者向けの詳細を記載したものである。 このドキュメントが NTLM に関する網羅的な記載となっていくことを期待しているが、現在のところ筆者の知識不足と記載不足による抜け洩れがあり、いくつかの誤りも含んだものとなっている。 とはいえ、このドキュメントはさらなる調査を進める上での確実な第一歩を提供していると信じている。 ここに記載している情報は、http://jcifs.samba.org で提供しているオープンソースの jCIFS ライブラリにおける NTLM 認証の実装に用いられている。 このドキュメントは筆者による調査と、Samba における実装の解析に基づいて執筆されている。

目次


NTLMとは

NTLM は、さまざまなマイクロソフト社のネットワークプロトコルの実装に用いられており、NTLM セキュリティサポートプロバイダ (NTLMSSP) においてサポートされている、認証とセッションセキュリティのプロトコル群である。 NTLM は、本来セキュアな DCE/RPC の認証とネゴシエーションのためのものだったが、統合されたシングルサインオン機構として、マイクロソフト社システムの至るところで用いられている。NTLM は、HTTP 認証における「統合Windows認証」の一部として認識されていることが多いと思うが、その他にも、マイクロソフト社の SMTP、POP3、IMAP(これらは 3 つは Exchange Server に実装されている)、CIFS/SMB、Telnet、SIP、その他多くの実装でも用いられている。

NTLM セキュリティサービスプロバイダは認証、整合性、機密性を提供するサービスであり、Windows セキュリティサポートプロバイダインタフェース(SSPI)のフレームワーク中に存在している。 SSPI はプロバイダによって実装されるセキュリティ機能の基本要件を定義したものであり、NTLMSSP もこうしたプロバイダの一つとなっている。SSPI は、以下のような基本要件を定義しており、これらは NTLMSSP も実装している。

  1. 認証 -- NTLM はチャレンジアンドレスポンス認証機構を提供する。これにより、クライアントはサーバにパスワードを送出することなく、自身の身元を証明することができる。
  2. 署名 -- NTLMSSP はメッセージにデジタル署名を行なう方法を提供する。これにより、署名されたメッセージが(明示的にも暗黙のうちにも)改変されていないことを保証するとともに、署名者が共有シークレットの情報を持っていることを保証する。 NTLM は対称型の署名スキーム(Message Authentication Code/MAC)を実装しており、適切な署名は共通の共有鍵を持っている者によってのみ生成可能であり、かつ検証可能である。
  3. 暗号化 -- NTLMSSP は対称鍵暗号機構を実装しており、これによってメッセージが暗号化される。NTLM の場合、暗号化を行なう際には署名も行なわれる(署名のみで暗号化を行なわないことは可能であるが、暗号化されたメッセージは署名も行なわれている)。

ドメイン環境における認証プロトコルとしての NTLM は、多くの場合 Kerberos に取って代わられているが、Kerberos は信頼できる第三者(trusted-third-party)の存在を前提としているため、これが存在しないメンバサーバ(ドメインに参加していないサーバ)やローカルアカウント、信頼されていないドメイン上のリソースの認証に用いることができない。こうした環境においては、NTLM が主要な認証機構としての座を保ち続けている(その地位はもうしばらく続きそうである)。

NTLM の用語

詳細についての記載を行なう前に、さまざまなプロトコルで用いられているいくつかの用語について定義しておく必要がある。

NTLM 認証は、チャンンジレスポンス機構であり、通常 Type 1(ネゴシエーション)、Type 2 (チャレンジ)、Type 3(認証)と呼ばれる 3 つのメッセージからなっている。これらは基本的に以下のように動作する。

  1. クライアントは Type 1 メッセージをサーバに送信する。これには主としてクライアントによってサポートされており、サーバにサポートを要求する機能の一覧が含まれている。
  2. サーバは Type 2 メッセージで応答する。これにはサーバがサポートしており使用を合意した機能の一覧が含まれている。 重要な点として、このメッセージにはサーバ側で生成したチャレンジが含まれている。
  3. クライアントは Type 3 メッセージにより、チャレンジに対する応答を行なう。これには、クライアントユーザのドメインやユーザ名といった、クライアントに関する幾つかの情報が含まれている他、Type 2 チャレンジに対する 1 つ以上のレスポンスも含まれている。

Type 3 メッセージにおけるレスポンスは、サーバに対してクライアントのユーザは自身のアカウントのパスワードを把握しているという情報を提示するという点で非常に重要である。

認証のプロセスは両者の間で共有コンテキストを確立することから始まる。これには引続き行なわれる署名と暗号化処理に用いられる共有セッション鍵が含まれる。

(可能な限り)混乱を避けるため、このドキュメントでは、以下のような使いわけを行なう。

これにより、明確な使いわけが実現するが、「NTLM2 セッションレスポンス」認証(NTLMv1 認証のバリエーション)のような厄介なケースも例外的に存在することはある。ここでの定義が、こうした際の理解の助けになることを期待する。

本ドキュメントでは、「short値」をリトルエンディアンで16ビットの符合なし整数を意味するものとして扱う。 例えば、10進数の「1234」をshortとすると、16進数表記では「0xd204」として格納されている。

long値」とは、リトルエンディアンで32ビットの符合なし整数を意味する。10進数の「1234」をlong値だとすると、16進数表記では「0xd2040000」となる。

Unicode 文字列とは、各文字が16ビットのリトルエンディアンの値(16-bit UCS-2 Transformation Format, little-endian byte order, with no Byte Order Mark and no null-terminator)として格納された文字列を意味する。「hello」という文字列をUnicode表記したものを16進数で表すと、「0x680065006c006c006f00」となる。

OEM 文字列とは、各文字が8ビットのローカルマシンの各国の文字セット(DOS コードページ)として格納された文字列を意味する。NULL終端は存在しない。NTLMメッセージにおいて、OEM 文字列は通常大文字で表記される。「HELLO」という文字列をOEM表記したものを16進数で表すと、「0x48454c4c4f」となる。

「セキュリティバッファ」とはバイナリデータへのバッファを示すために用いられる構造体を意味する。これには、以下のメンバが含まれる。

  1. short値、バイト単位のバッファ長が格納される。
  2. short値、バイト単位でこのバッファに割り当てられている領域長が格納される(通常はバッファ長と同じである)。
  3. long値、バイト単位で、バッファの(NTLMメッセージ開始地点からの)オフセットが格納される。

例えば、「0xd204d204e1100000」というセキュリティバッファは、以下のように解析できる。

バッファ長: 0xd204 (1234 バイト)
割り当てられている領域: 0xd204 (1234 バイト)
オフセット: 0xe1100000 (4321 バイト目)

メッセージの最初のバイトからみると、4321バイトスキップした地点が、データバッファの先頭位置になる。ここから 1234 バイト(バッファ長)のデータを読みとれる。バッファに割り当てられた領域も 1234 バイトであるため、そこがバッファの終端になる。

NTLM メッセージヘッダのレイアウト

ようやく NTLM 認証メッセージの実形式について記載するところまでたどり着いた。

すべてのメッセージは、NULL 終端なしの「NTLMSSP」というASCII文字列(16進数では「0x4e544c4d53535000」)からなる NTLMSSP 署名(signature)が先頭にくる。

ついで、メッセージの Type (1、2、3)を示すlong値が続く。Type 1 メッセージの場合、これは16進数で「0x01000000」となる。

さらににメッセージ固有の情報が続く。これは通常セキュリティバッファとメッセージフラグから構成されている。

NTLM フラグ

メッセージフラグは、ヘッダ内のビットフィールドに格納されている。これは各ビットが特定のフラグを表すlong値である。これらのほとんどは後ほど説明するが、ここでは話を進めやすくするために、概要を説明する。以下の表で「未確認」もしくは「不明」と記載されたフラグは筆者が理解していないものである(もちろん筆者の理解は決して絶対的なものではない)。

フラグ名称詳細
0x00000001Negotiate Unicode セキュリティバッファのデータで、Unicode 文字列がサポートされていることを示す。
0x00000002Negotiate OEM セキュリティバッファのデータで、OEM 文字列がサポートされていることを示す。
0x00000004Request Target サーバの authentication realm が Type 2 メッセージに含まれることを要求する。
0x00000008不明 このフラグの用途は確認されていない。
0x00000010Negotiate Sign クライアントとサーバ間の認証に際して、デジタル署名(メッセージの整合性確保)の実施を指定する。
0x00000020Negotiate Seal クライアントとサーバ間の認証に際して、暗号化(メッセージの機密性確保)の実施を指定する。
0x00000040Negotiate Datagram Style datagram authentication の使用を指定する。
0x00000080Negotiate Lan Manager Key 認証に際して、Lan Manager Session Key の使用を指定する。
0x00000100Negotiate Netware このフラグの用途は確認されていない。
0x00000200Negotiate NTLM NTLM 認証の使用を指定する。
0x00000400不明 このフラグの用途は確認されていない。
0x00000800Negotiate Anonymous クライアントが送出する Type 3 メッセージで指定され、匿名コンテキストによる認証が確立されたことを示す。これはレスポンスフィールドの内容にも影響する(詳細は「匿名レスポンス(Anonymous Response)」セクションで記載している)。
0x00001000Negotiate Domain Supplied クライアントが送出する Type 1 メッセージで指定され、クライアントのワークステーションが所属するドメイン名の情報がメッセージに含まれていることを示す。 これは、サーバがクライアントをローカル認証すべきかどうかを決定する際に用いられる。
0x00002000Negotiate Workstation Supplied クライアントが送出する Type 1 メッセージで指定され、クライアントのワークステーション名がメッセージに含まれていることを示す。 これは、サーバがクライアントをローカル認証すべきかどうかを決定する際に用いられる。
0x00004000Negotiate Local Call サーバによって送出され、クライアントとサーバが同一マシンであることを示す。 これにより、クライアントはチャレンジに対するレスポンスを計算する代わりに、確立済のローカルな資格情報を使用することが可能となる。
0x00008000Negotiate Always Sign クライアントとサーバ間の認証に際して、「dummy」署名による署名を示す。
0x00010000Target Type Domain サーバが送出する Type 2 メッセージで指定され、対象の authentication realm がドメインであることを示す。
0x00020000Target Type Server サーバが送出する Type 2 メッセージで指定され、対象の authentication realm がサーバ(server)であることを示す。
0x00040000Target Type Share サーバが送出する Type 2 メッセージで指定され、対象の authentication realm が共有(share)であることを示す。おそらく、これは共有レベルのセキュリティを意味していると思われるが、用途は明確になっていない。
0x00080000Negotiate NTLM2 Key NTLM2 署名と暗号化のスキームを認証の際の保護に用いることを示す。 これは、セッションセキュリティのスキームに対するものであり、NTLMv2 認証の使用とは関係しない点に注意。 ただし、このフラグは、レスポンスの計算に影響を与える(詳細は「NTLM2 セッションレスポンス(NTLM2 Session Response)」セクションで記載している)。
0x00100000Request Init Response このフラグの用途は確認されていない。
0x00200000Request Accept Response このフラグの用途は確認されていない。
0x00400000Request Non-NT Session Key このフラグの用途は確認されていない。
0x00800000Negotiate Target Info サーバが送出する Type 2 メッセージで指定され、メッセージに Target Information ブロックが含まれることを示す。Target Information ブロックは NTLMv2 レスポンスの計算に用いられる。
0x01000000不明 このフラグの用途は確認されていない。
0x02000000不明 このフラグの用途は確認されていない。
0x04000000不明 このフラグの用途は確認されていない。
0x08000000不明 このフラグの用途は確認されていない。
0x10000000不明 このフラグの用途は確認されていない。
0x20000000Negotiate 128 128 ビットの暗号化がサポートされていることを示す。
0x40000000Negotiate Key Exchange クライアントが Type 3 メッセージの「Session Key」フィールドで暗号化されたマスター鍵を提供することを示す。
0x80000000Negotiate 56 56 ビットの暗号化がサポートされていることを示す。

一例として、以下を指定したメッセージの場合、

Negotiate Unicode (0x00000001)
Request Target (0x00000004)
Negotiate NTLM (0x00000200)
Negotiate Always Sign (0x00008000)

上記を合計した値は「0x00008205」となる。これは実際には「0x05820000」という形態で格納される(リトルエンディアンのバイト順で表現されるため)。

Type 1 メッセージ

以下、Type 1 メッセージについて見ていこう。

Description Content
0NTLMSSP 署名 NULL 終端された ASCII 文字列「NTLMSSP」 (0x4e544c4d53535000)
8NTLM メッセージのタイプ long (0x01000000)
12フラグlong
(16)Supplied Domain (オプション)セキュリティバッファ
(24)Supplied Workstation (オプション) セキュリティバッファ
(32)データブロックの先頭 (必要な場合)

Type 1 メッセージは、NTLM 認証を開始するにあたってクライアントからサーバに送信される。主要な目的は、フラグによって提示されるサポート可能なオプションを示すことにより、認証を行なう際の「ローカルルール」を確立することにある。 オプションとして、クライアントのワークステーション名と所属するドメイン名をサーバに提示することもできる。この情報は、クライアントのローカル認証が可能かどうかをサーバが確認する際に用いられる。

通常、Type 1 メッセージには、以下のフラグが設定されている。

Negotiate Unicode (0x00000001) Unicode 文字列のサポートを示すため、このフラグを設定する。
Negotiate OEM (0x00000002) OEM 文字列のサポートを示すため、このフラグを設定する。
Request Target (0x00000004) サーバに対して、Type 2 の応答の際に authentication target の送信を要求する。
Negotiate NTLM (0x00000200) NTLM 認証がサポートされていることを示す。
Negotiate Domain Supplied (0x00001000) 設定されている場合、クライアントはワークステーションが所属するドメイン名をメッセージの一部として送信する。
Negotiate Workstation Supplied (0x00002000) クライアントが自身のワークステーション名をメッセージの一部として送信することを示す。
Negotiate Always Sign (0x00008000) 認証後のクライアントとサーバ間の通信が「dummy」署名で行なわれることを示す。
Negotiate NTLM2 Key (0x00080000) クライアントが NTLM2 の認証と暗号化スキームをサポートすることが示される。 ネゴシエーションに成功した場合は、レスポンスの計算にも影響する。
Negotiate 128 (0x20000000) クライアントが強力な(128ビット)暗号をサポートしていることを示す。
Negotiate 56 (0x80000000) クライアントが中程度の(128ビット)暗号をサポートしていることを示す。

supplied domain はクライアントが所属するドメイン名を格納するセキュリティバッファである。これは、クライアントが Unicode をサポートしていても、常に OEM 形式で格納される。

supplied workstation はクライアントのワークステーション名を格納するセキュリティバッファである。これも、常に OEM 形式で格納される。

supplied domain と workstation はオプションのフィールドであることに注意。これらは空である(長さ 0 のセキュリティバッファである)ことや、全く送信されない(セキュリティバッファ自体が存在していない)ことがある。supplied domain や workstation が存在しない場合、Type 1 メッセージにはデータブロックが存在しない(メッセージはフラグフィールドで終了し、16バイト固定長となる)。 「最短の」Type 1 メッセージは以下のようになる。

    4e544c4d535350000100000002020000

このメッセージには、NTLMSSP 署名 、NTLM メッセージのタイプと最低限のフラグ(Negotiate NTLM と Negotiate OEM)だけが含まれている。

Type 1 メッセージのサンプル

16進数で表記した、以下の Type 1 メッセージを解析してみよう。

    4e544c4d535350000100000007320000060006002b0000000b000b0020000000
    574f524b53544154494f4e444f4d41494e

このメッセージは、以下のように解読できる。

0 0x4e544c4d53535000NTLMSSP 署名
8 0x01000000Type 1 を示す識別子
12 0x07320000 Flags:

Negotiate Unicode (0x00000001)
Negotiate OEM (0x00000002)
Request Target (0x00000004)
Negotiate NTLM (0x00000200)
Negotiate Domain Supplied (0x00001000)
Negotiate Workstation Supplied (0x00002000)

16 0x060006002b000000 Supplied Domain セキュリティバッファ:

長さ: 6 bytes (0x0600)
割当済領域: 6 bytes (0x0600)
オフセット: 43 bytes (0x2b000000)

24 0x0b000b0020000000 Supplied Workstation セキュリティバッファ:

長さ: 11 bytes (0x0b00)
割当済領域: 11 bytes (0x0b00)
オフセット: 32 bytes (0x20000000)

32 0x574f524b53544154494f4e Supplied Workstation のデータ ("WORKSTATION")
43 0x444f4d41494e Supplied Domain のデータ ("DOMAIN")

このメッセージから以下の情報を読みとることができる。

supplied workstation および supplied domain は OEM 形式で格納されていることに注意。また、前述した例では、supplied workstation のデータが supplied domain の前に格納されているが、セキュリティバッファのデータの格納順序は重要ではない。

クライアントが Type 1 を作成して、それをサーバに送信すると、サーバは我々が行なったのと同様にメッセージを解析して、応答を作成する。この応答が次のトピックである Type 2 メッセージである。

Type 2 メッセージ

Description Content
0NTLMSSP 署名 NULL 終端された ASCII 文字列「NTLMSSP」 (0x4e544c4d53535000)
8NTLM メッセージのタイプ long (0x02000000)
12Target Nameセキュリティバッファ
20Flagslong
24Challenge8 バイト
(32)Context (オプション) 8 bytes (2つのlong値)
(40)Target Information (オプション) セキュリティバッファ
32 (48) データブロックの先頭

Type 2 メッセージはクライアントの Type 1 メッセージに対する応答の形でサーバから送出される。 このメッセージでは、クライアントとのネゴシエーションのオプションが 提示される他、クライアントに対するチャレンジも提供される。 このメッセージには、 authentication target に関する情報が含まれることもある。

Type 2 メッセージでは、通常以下のフラグが設定される。

Negotiate Unicode (0x00000001) Unicode 文字列の使用を示すため、このフラグを設定する。 クライアントが (Type 1 メッセージで) Unicode のサポートを示した場合のみ、このフラグの設定を行なうこと このフラグか Negotiate OEM フラグのいずれかを設定する必要があるが、両方を設定してはならない。
Negotiate OEM (0x00000002) OEM 文字列の使用を示すため、このフラグを設定する。 クライアントが (Type 1 メッセージで) OEM 文字列のサポートを示した場合のみ、このフラグの設定を行なうこと このフラグか Negotiate Unicode フラグのいずれかを設定する必要があるが、両方を設定してはならない。
Request Target (0x00000004) このフラグは Type 2 メッセージで設定されることも多い。Type 1 メッセージでのフラグの目的は明示されているが、Type 2 メッセージでの目的は不明なままである。
Negotiate NTLM (0x00000200) NTLM 認証がサポートされていることを示す。
Negotiate Local Call (0x00004000) サーバとクライアントが同一マシン上に存在していることをクライアントに提示する。 サーバは、メッセージの一部としてローカルセキュリティコンテキストハンドルを提供する。
Negotiate Always Sign (0x00008000) 認証後のクライアントとサーバ間の通信が「dummy」署名で行なわれることを示す。
Target Type Domain (0x00010000) このフラグにより、メッセージ内で送信される authentication target がドメインを表すことを示す。
Target Type Server (0x00020000) このフラグにより、メッセージ内で送信される authentication target がサーバを表すことを示す。
Target Type Share (0x00040000) このフラグにより、メッセージ内で送信される authentication target がネットワーク共有を示すことが明示される。このフラグの用途は確認されていない。
Negotiate NTLM2 Key (0x00080000) サーバが NTLM2 の認証と暗号化スキームをサポートすることが示される。 ネゴシエーションに成功した場合は、クライアントからのレスポンスの計算にも影響する。
Negotiate Target Info (0x00800000) このフラグにより、Target Information ブロックがメッセージ内で送信されることを示す。
Negotiate 128 (0x20000000) クライアントが強力な(128ビット)暗号をサポートしていることを示す。
Negotiate 56 (0x80000000) クライアントが中程度の(128ビット)暗号をサポートしていることを示す。

target name は authentication target の名称が格納されたセキュリティバッファである。これは (Type 1 メッセージ中の Request Target フラグにより) target を要求したクライアントに対する応答として送信されることが多い。 これには、ドメイン名、サーバ名、もしくは(おそらく)ネットワーク共有名が含まれる。 target type は、Target Type Domain 、Target Type Server 、 Target Type Share フラグで示される。 target name は Type 2 メッセージ中のフラグの設定次第で、Unicode と OEM 文字列のいずれも可能である。

challenge は 8 バイトのランダムなデータである。クライアントはこの情報を用いてレスポンスを生成する。

context フィールドは、通常 Negotiate Local Call が設定された場合に存在する。 これには SSPI コンテキストハンドルが格納される。これにより、クライアントはチャレンジに対する応答を行なう代わりに「short-circuit」な認証を行なうことが可能となる。 context は 2 つの long値からなる。詳細は「ローカル認証」セクションで後述する。

target の情報は、Target Information ブロック中のセキュリティバッファに含まれる。これはNTLMv2 レスポンス (後述する)の計算に用いられる。このブロックは、サブブロックの配列から構成されている。各サブブロックは、以下の内容で構成されている。

フィールドContentDescription
Typeshort このサブブロックのデータのタイプを指定する。
1 (0x0100):サーバ名
2 (0x0200):ドメイン名
3 (0x0300): 完全修飾された DNS ホスト名 (server.domain.com形式)
4 (0x0400): DNS ドメイン名 (i.e., domain.com形式)
長さshort このサブブロックの content フィールドの長さ(バイト単位)
ContentUnicode 文字列 type フィールドで指定された情報。メッセージフラグで OEM が指定されていても、常に Unicode 文字列として送信される。

配列は、終端(terminator)サブブロックで終了する。このサブブロックは type が「0」で、長さ 0 である。type が「5」のサブブロックも存在する。これにはサーバが所属するドメインの「親」DNS ドメイン名が含まれているようである。その他にも未知のサブブロック type が存在するかも知れない。

context および target information は省略される場合がある。その場合、データブロックはオフセット 32 (チャレンジの直後)から開始される。最小の Type 2 メッセージは以下のようなバイト列となる。

    4e544c4d53535000020000000000000000000000020200000123456789abcdef

このメッセージには、NTLMSSP 署名 、NTLM メッセージタイプ、空の target namae 、最低限のフラグ(Negotiate NTLM と Negotiate OEM)、チャレンジが含まれている。

Type 2 メッセージのサンプル

16進数で表記した、以下の Type 2 メッセージを解析してみよう。

    4e544c4d53535000020000000c000c003000000001028100
    0123456789abcdef0000000000000000620062003c000000
    44004f004d00410049004e0002000c0044004f004d004100
    49004e0001000c0053004500520056004500520004001400
    64006f006d00610069006e002e0063006f006d0003002200
    7300650072007600650072002e0064006f006d0061006900
    6e002e0063006f006d0000000000

このメッセージは以下のフィールドに分割される。

0 0x4e544c4d53535000NTLMSSP 署名
8 0x02000000Type 2 を示す識別子
12 0x0c000c0030000000 Target Name セキュリティバッファ:

長さ: 12 バイト (0x0c00)
割当済領域: 12 バイト (0x0c00)
オフセット: 48 バイト (0x30000000)

20 0x01028100 フラグ:

Negotiate Unicode (0x00000001)
Negotiate NTLM (0x00000200)
Target Type Domain (0x00010000)
Negotiate Target Info (0x00800000)

24 0x0123456789abcdefchallenge
32 0x0000000000000000context
40 0x620062003c000000 Target Information セキュリティバッファ:

長さ: 98 バイト (0x6200)
割当済領域: 98 バイト (0x6200)
オフセット: 60 バイト (0x3c000000)

48
0x44004f004d004100
  49004e00
Target Name のデータ (「DOMAIN」)
60
0x02000c0044004f00
  4d00410049004e00
  01000c0053004500
  5200560045005200
  0400140064006f00
  6d00610069006e00
  2e0063006f006d00
  0300220073006500
  7200760065007200
  2e0064006f006d00
  610069006e002e00
  63006f006d000000
  0000
Target Information のデータ:

0x02000c0044004f00
  4d00410049004e00
ドメイン名サブブロック:

Type: 2 (ドメイン名, 0x0200)
長さ: 12 bytes (0x0c00)
データ: 「DOMAIN

0x01000c0053004500
  5200560045005200
サーバ名サブブロック:

Type: 1 (サーバ名, 0x0100)
長さ: 12 バイト (0x0c00)
データ: 「SERVER

0x0400140064006f00
  6d00610069006e00
  2e0063006f006d00
DNS ドメイン名サブブロック:

Type: 4 (DNS ドメイン名, 0x0400)
長さ: 20 バイト (0x1400)
データ: 「domain.com

0x0300220073006500
  7200760065007200
  2e0064006f006d00
  610069006e002e00
  63006f006d00
DNS ホスト名サブブロック:

Type: 3 (DNS server name, 0x0300)
長さ: 34 バイト (0x2200)
データ: 「server.domain.com

0x00000000 終端サブブロック:

Type: 0 (終端, 0x0000)
長さ: 0 bytes (0x0000)

このメッセージから以下の情報を読みとることができる。

target name は(Negotiate Unicode フラグで指定されたように)、Unicode 形式である点に注意。

サーバが Type 2 メッセージを作成して、クライアントに送信する。サーバのチャレンジに対するレスポンスは、クライアントが送出する Type 3 メッセージで提示される。

Type 3 メッセージ

Description Content
0NTLMSSP 署名 NULL 終端された ASCII 文字列「NTLMSSP」 (0x4e544c4d53535000)
8NTLM メッセージのタイプ long (0x03000000)
12LM/LMv2 レスポンス セキュリティバッファ
20NTLM/NTLMv2 レスポンス セキュリティバッファ
28ドメイン名 セキュリティバッファ
36ユーザ名 セキュリティバッファ
44ワークステーション名 セキュリティバッファ
(52)セッション鍵(オプション) セキュリティバッファ
(60)フラグ (オプション) long
52 (64) データブロックの先頭

Type 3 メッセージは認証の最終段階である。このメッセージには、Type 2 チャレンジに対するクライアントのレスポンスが含まれている。これにより、クライアントはパスワードを直接送付することなしに、パスワード情報を把握していることを提示できる。 Type 3 メッセージは認証するアカウントのクライアントのワークステーション名と併せて、ドメイン名とユーザ名の情報も提示する。

Type 3 メッセージにおいて、フラグはオプションであることに注意。以前のクライアントでは、メッセージ中にセッション鍵もフラグも含まれない。この場合、データブロックはワークステーション名セキュリティバッファ直後のオフセット 52 から開始される。Type 3 メッセージにフラグが存在していた場合でも、コネクションの認証に関しての情報は格納しないこと、認証にもセッションセキュリティの確立にも何ら影響を及ぼさないことが試験的に(experimentally)決定されている。クライアントがフラグを送信する場合は、通常 Type 2 メッセージで確立された設定のコピーである。このフラグを、サーバがネゴシエーションされた設定をキャッシュしてしまわないように、確立されたオプションの「リマインダ」として用いることが可能である。Type 3 のフラグについては、データグラム形式の認証 と関連している。

LM/LMv2 および NTLM/NTLMv2 レスポンスは Type 2 メッセージのチャレンジに対する応答としてユーザのパスワードから生成されたレスポンスを含むセキュリティバッファである。これらのレスポンスを生成する処理については、次のセクションで概説する。

ドメイン名は、認証されるアカウントが所属する authentication realm を含むセキュリティバッファである。これはネゴシエートされたエンコーディングにより、Unicode もしくは OEM 文字列として格納される。

ユーザ名は、認証されるアカウント名を含むセキュリティバッファである。これもネゴシエートされたエンコーディングにより、Unicode もしくは OEM 文字列として格納される。

ワークステーション名はクライアントのワークステーション名を含むセキュリティバッファである。これもネゴシエートされたエンコーディングにより、Unicode もしくは OEM 文字列として格納される。

セッション鍵の値は、鍵交換の際にセッションセキュリティ機構によって用いられる。詳細については、Session Security セクションにて記載している。

Type 2 メッセージで「Negotiate Local Call」が確立されていた場合、Type 3 メッセージのセキュリティバッファは通常すべて空(0 バイト長)である。クライアントは、レスポンスを計算する代わりに Type 2 メッセージで送付された SSPI context を「採用」する。

チャレンジに対するレスポンス

クライアントは Type 2 メッセージのチャレンジに対して、以下のいずれか 1 つ以上のレスポンスを生成して、Type 3 メッセージで送信する。レスポンスには以下の 6 種類がある。

これらに関する詳細情報を把握した場合は、Christopher Hertel の Implementing CIFS、特に the section on authentication を一読することを強く推奨する。

LM レスポンス

LM レスポンスはほとんどのクライアントから送出される。これは NTLM レスポンスより古いものであり、セキュリティ的にも劣る。新しいクライアントはセキュリティ的により強固な NTLM レスポンスをサポートしているが、通常は古いサーバとの相互接続性維持のため、両方のレスポンスを送出する。そのため、LM レスポンスにはセキュリティ上の欠陥が指摘されているにも関わらず、NTLM レスポンスをサポートするクライアントの多くがいまだに LM レスポンスも送出している。

LM レスポンスの計算方法を以下に記載する (Java における実装については 付録 D を参照のこと)。

  1. ユーザのパスワード(OEM 文字列)を大文字に変換する。
  2. このパスワードが 14 バイトになるように NULL パディングするか切り詰める。
  3. この「確定」パスワードを 2 つの 7 バイトのバイト列に分割する。
  4. このバイト列は DES 鍵として用いられる (7 バイト長のバイト列各々について)。
  5. 各々の鍵を用いて、「KGS!@#$%」という固定の ASCII 文字列を DES 暗号化する。(これにより 2 つの暗号文が生成される。)
  6. 生成された 2 つの暗号文を繋げて、16 バイトのバイト列を生成する。これが LM ハッシュである。
  7. 16 バイトの LM ハッシュを NULL パディングして 21 バイトにする。
  8. この値を 3 つの 7 バイトのバイト列に分割する。
  9. このバイト列は 3 つの DES 鍵として用いられる (7 バイトのバイト列各々について)。
  10. 3 つの鍵の各々が、Type 2 メッセージのチャレンジを DES 暗号化するために用いられる (これにより 3 つの 8 バイトの暗号文が生成される)。
  11. これら 3 つの暗号文を結合して 24 バイトの文字列を生成する。これが LM レスポンスとなる。

この処理を詳細に記載したのが以下の例である。ユーザが「SecREt01」というパスワードを用いており、Type 2 チャレンジ「0x0123456789abcdef」に応答しようとしていると仮定する。

  1. パスワード(OEM 文字列である)が大文字に変換され、「SECRET01」となる (16進数では「0x5345435245543031」となる)。
  2. パスワードは 14 バイト長となるように NULL パディングされ、「0x5345435245543031000000000000」となる。
  3. この値が、2 つの 7 バイトのバイト列、「0x53454352455430」と「0x31000000000000」に分割される。
  4. これら 2 つのバイト列から 2 つの DES 鍵が生成される。DES 鍵は 8 バイト長であり、各バイトのうち 7 ビット文が鍵そのものであり、1 ビットが奇数パリティビットである (パリティビットが実際に使用されるかどうかは、DES の実装に依存する)。今回のサンプルの先頭 7 バイトである「0x53454352455430」は、ビット列で以下のように表現される。

    01010011 01000101 01000011 01010010 01000101 01010100 00110000

    これを DES 鍵として使えるようにパリティビットの値を考慮せず、単にビットを挿入すると、以下になる。

    01010010 10100010 01010000 01101010 00100100 00101010 01010000 01100000

    (パリティビットは赤文字で表示している. これを 16 進数表記すると「0x52a2506a242a5060」となる。このバイト列に対して奇数パリティの条件を満たすように各オクテット毎にビットを設定したものが以下となる。

    01010010 10100010 01010001 01101011 00100101 00101010 01010001 01100001

    これが 1 つめの DES 鍵となる (16 進数で「0x52a2516b252a5161」)。引続き、同様の処理を 2 つ目のバイト列である「0x31000000000000」に対して行なう。このバイト列をビット列で表すと以下となる。

    00110001 00000000 00000000 00000000 00000000 00000000 00000000

    これを DES 鍵として使えるようにパリティビットの値を考慮せず、単にビットを挿入すると、以下となる

    00110000 10000000 00000000 00000000 00000000 00000000 00000000 00000000

    (16進数では「0x3080000000000000」) 。奇数パリティの設定を行なったものは以下となる。

    00110001 10000000 00000001 00000001 00000001 00000001 00000001 00000001

    これが 2 つ目の DES 鍵であり、16 進数では「0x3180010101010101」となる。DES の実装によっては(実際のところ多くの実装では)パリティを使用しないため、この処理は省略できる。この場合 DES 鍵として使えるように単にビットを挿入したバイト列が DES 鍵として用いられる。多くの場合、パリティビットは暗号化処理に影響を及ぼさない。

  5. 各々の鍵は、固定の ASCII 文字列「KGS!@#$%」(16進数で「0x4b47532140232425」)を暗号化するために用いられる。 これにより、「0xff3750bcc2b22412」(1 つ目の鍵を使用)と「0xc2265b23734e0dac」(2 つ目を使用)という暗号文が生成される。
  6. これらの暗号文を結合したものが、16 バイトのLM ハッシュ - 「0xff3750bcc2b22412c2265b23734e0dac」となる。
  7. これを 21 バイトになるように NULL パディングしたものが「0xff3750bcc2b22412c2265b23734e0dac0000000000」となる。
  8. この値を 3 つの 7 バイトのバイト列、「0xff3750bcc2b224」、「0x12c2265b23734e」、「0x0dac0000000000」に分割する。
  9. これらの 3 つのバイト列を用いて 3 つの DES 鍵を生成する。先に概要を示した作業が行なわれ、最初のバイト列

    11111111 00110111 01010000 10111100 11000010 10110010 00100100

    からは、パリティ処理を行なった DES 鍵として以下が生成される

    11111110 10011011 11010101 00010110 11001101 00010101 11001000 01001001

    (16 進数では「0xfe9bd516cd15c849」)。2 つ目のバイト列である

    00010010 11000010 00100110 01011011 00100011 01110011 01001110

    からは、以下のバイト列が生成される

    00010011 01100001 10001001 11001011 10110011 00011010 11001101 10011101

    (16 進数では「0x136189cbb31acd9d」)。最後に、3 つ目のバイト列である

    00001101 10101100 00000000 00000000 00000000 00000000 00000000

    からは、以下のバイト列が生成される。

    00001101 11010110 00000001 00000001 00000001 00000001 00000001 00000001

    これが 3 つ目の DES 鍵(「0x0dd6010101010101」)となる。

  10. 各々の鍵が、Type 2 メッセージに含まれるチャレンジの値(今回の例では「0x0123456789abcdef」) の DES 暗号化に用いられる。これにより、以下のような暗号文、 「0xc337cd5cbd44fc97」 (1 つ目の鍵を使用)、「0x82a667af6d427c6d」 (2 つ目を使用) 、「0xe67c20c2d3e77c56」(3 つ目を使用) が生成される。
  11. これら 3 つの暗号文を結合して 24 バイトのバイト列としたものが LM レスポンスとなる。

    0xc337cd5cbd44fc9782a667af6d427c6de67c20c2d3e77c56

このアルゴリズムには平文を推測可能である脆弱性がいくつか存在する。脆弱性についての詳細は、Hertel のドキュメントで記載されているが、最も顕著な問題は、以下のようなものである。

NTLM レスポンス

NTLM レスポンスは最近のクライアントが送出する。 これは、LM レスポンスの欠陥に対応したものであるが、以前として強力なものであるとはいい難い。 加えて、ほとんどの場合 NTLM レスポンスは LM レスポンスとともに送出される。 LM レスポンスのアルゴリズムの脆弱性により、大文字小文字が不明な状態でのパスワードが取得されてしまい、ここからはトライアンドエラーで、 NTLM レスポンスが用いる大文字小文字を区別した形式のパスワードを確認することができてしまう。

NTLM レスポンスは以下のようにして計算される (Java における実装例については 付録 D を参照のこと)。

  1. MD4 メッセージダイジェストアルゴリズム (RFC 1320 にて記載されている) が Unicode ベースの大文字小文字が混在したパスワードに対して適用される。 この結果、16 バイトのバイト列が生成される。これが NTLM ハッシュである。
  2. 16 バイトの NTLM ハッシュが 21 バイトになるように NULL パディングされる。
  3. この値が 3 つの 7 バイトのバイト列に分割される。
  4. これら 7 バイトの値の各々が DES 鍵として用いられる。
  5. これらの鍵の各々を用いて、Type 2 メッセージのチャレンジが DES 暗号化される (この結果、3 つの 8 バイトの暗号文が生成される)。
  6. これら 3 つの暗号文を結合して 24 バイトの値を生成する。これが NTLM レスポンスである。

LM レスポンスと異なるのはハッシュ値の生成部分だけであり、レスポンスの計算部分は同一である。 詳細に記載するために、先ほど用いたサンプル(ユーザのパスワードが「SecREt01」で、Type 2 チャレンジ「0x0123456789abcdef」に応答しようとしている)にこの処理を適用してみよう。

  1. Unicode ベースの大文字小文字が混在したパスワードは 16 進数で「0x53006500630052004500740030003100」となる。この値の MD4 ハッシュを計算することで、「0xcd06ca7c7e10c99b1d33b7485a2ed808」という値が生成される。これが NTLM ハッシュである。
  2. これを 21 バイトになるように NULL パディングして、「0xcd06ca7c7e10c99b1d33b7485a2ed8080000000000」という値が生成される。
  3. この値を 3 つの 7 バイトの値に分割し、「0xcd06ca7c7e10c9」、「0x9b1d33b7485a2e」、「0xd8080000000000」という値が生成される。
  4. この 3 つの値は 3 つの DES 鍵を生成するのに用いられる。最初の値

    11001101 00000110 11001010 01111100 01111110 00010000 11001001

    からは、パリティ処理を行なった DES 鍵として

    11001101 10000011 10110011 01001111 11000111 11110001 01000011 10010010

    (16 進数で「0xcd83b34fc7f14392」)が生成される。2 つ目の値

    10011011 00011101 00110011 10110111 01001000 01011010 00101110

    からは、

    10011011 10001111 01001100 01110110 01110101 01000011 01101000 01011101

    (16進数で「0x9b8f4c767543685d」)が生成され、3 つ目の値

    11011000 00001000 00000000 00000000 00000000 00000000 00000000

    からは、

    11011001 00000100 00000001 00000001 00000001 00000001 00000001 00000001

    (16 進数で「0xd904010101010101」)が生成される。

  5. これら 3 つの鍵が、Type 2 メッセージのチャレンジ(「0x0123456789abcdef」)の DES 暗号化に用いられる。これにより、以下の値、 「0x25a98c1c31e81847」 (1 つ目の鍵を使用)、 「0x466b29b2df4680f3」 (2 つ目を使用)、 「0x9958fb8c213a9cc6」 (3 つ目の鍵を使用) が生成される。
  6. これら 3 つの暗号文を結合して、24 バイトのバイト列としたものが、以下の NTLM レスポンスである。

    0x25a98c1c31e81847466b29b2df4680f39958fb8c213a9cc6

NTLMv2 レスポンス

NTLM version 2 (「NTLMv2」) は NTLM に存在したセキュリティの問題を解決するために作られた。その実効性については疑問の余地はあるものの、これが LM レスポンスに対する最もセキュアな代替策である。NTLMv2 が有効な場合、NTLM レスポンスが NTLMv2 レスポンスに置き換えられ、LM レスポンスは (次で説明する) LMv2 レスポンスに置き換えられる。

NTLMv2 レスポンスは以下のようにして計算される (Java による実装例については 付録 D を参照のこと)。

  1. NTLM パスワードハッシュが生成される (前述したように、これは Unicode ベースの大文字小文字の混在したパスワードの MD4 ダイジェストである)。
  2. 大文字化したユーザ名の Unicode 文字列が大文字化した authentication target (ドメイン名もしくはサーバ名)の Unicode 文字列と結合される。HMAC-MD5 メッセージ認証コードアルゴリズム (RFC 2104 で記載) が 16 バイトの NTLM ハッシュを鍵として、このバイト列に適用される。この結果、16 バイトの値が生成される。これが NTLMv2 ハッシュである。
  3. 「blob」という名称で呼ばれているデータブロックが構築される。Hertel のドキュメントでは、この構造体のフォーマットが詳細に記載されているが、ここでは簡単に説明する。
    Description Content
    0Blob 署名 0x01010000
    4予約 long (0x00000000)
    8Timestamp 1601 年 1 月 1 日から 10 マイクロ秒単位の時刻を格納した 64 ビットの符合付き、リトルエンディアンの数値
    16Client Nonce8 bytes
    24不明4 bytes
    28Target Information Target Information ブロック (Type 2 メッセージより)
    (可変)不明 4 bytes
  4. Type 2 メッセージのチャレンジにこの blob が結合される。これに HMAC-MD5 メッセージ認証コードアルゴリズムが (ステップ 2 で計算された) 16 バイトの NTLMv2 ハッシュを鍵として適用される。これにより 16 バイトのバイト列が生成される。
  5. このバイト列を先ほどの blob と結合したものが NTLMv2 レスポンスである。

以下に例を示す。NTLMv2 レスポンスを計算するためにはいくつか必要な情報があるが、今回はここまでの例にならい、以下の値を用いる。

ドメイン名: DOMAIN
ユーザ名: user
パスワードd: SecREt01
チャレンジ: 0x0123456789abcdef
Target Information:
0x02000c0044004f00
  4d00410049004e00
  01000c0053004500
  5200560045005200
  0400140064006f00
  6d00610069006e00
  2e0063006f006d00
  0300220073006500
  7200760065007200
  2e0064006f006d00
  610069006e002e00
  63006f006d000000
  0000

  1. Unicode ベースの大文字小文字が混在したパスワードは、16 進数で「0x53006500630052004500740030003100」となる。このバイト列の MD4 ハッシュを計算すると、「0xcd06ca7c7e10c99b1d33b7485a2ed808」となる。これが NTLM ハッシュである。
  2. 大文字化したユーザの Unicode 文字列が大文字化した authentication target の Unicode 文字列と結合され、「USERDOMAIN (16進数では「0x550053004500520044004f004d00410049004e00」)というUnicode 文字列が生成される。 この文字列に対して、先に生成した 16 バイトの NTLM ハッシュを鍵として HMAC-MD5 が適用される。これにより「0x04b8e0ba74289cc540826bab1dee63ae」という値が生成される。これが NTLMv2 ハッシュである。
  3. 引続き、blob を生成する。タイムスタンプは長いだけでなんの変哲もない。デスクの時計をみると、東部夏時間(EDT)で 2003 年 6 月 17 日の 6:00 ちょうどだったとする。UNIX 時間において、これは始点から 1055844000 秒経過した時刻となる。さらに 11644473600 を加えることで、1601 年 1 月 1 日からの秒単位の経過時刻 (12700317600) を取得できる。これを 10 の 7 乗 (10000000) したものがマイクロ秒単位の経過時刻 (127003176000000000) となる。これをリトルエンディアンの 64 ビットの値として表すと (16 進数で)「0x0090d336b734c301」となる。

    次に、ランダムな 8 バイトの「client nonce」を生成する必要がある。ここでは乱雑性に低いが「0xffffff0011223344」という値を用いることとする。blob の残りの部分は簡単に生成できる。これらを結合しよう。

    0x01010000(Blob 署名)
    0x00000000(予約値)
    0x0090d336b734c301(タイムスタンプ)
    0xffffff0011223344(ランダムなクライアントの値)
    0x00000000(不明だが、0 にしておけば動作する)
    0x02000c0044004f00
      4d00410049004e00
      01000c0053004500
      5200560045005200
      0400140064006f00
      6d00610069006e00
      2e0063006f006d00
      0300220073006500
      7200760065007200
      2e0064006f006d00
      610069006e002e00
      63006f006d000000
      0000
    (target information ブロック)
    0x00000000(不明だが、0 にしておけば動作する)

  4. ここで Type 2 チャレンジと blob を結合して、以下のバイト列を生成する。
    0x0123456789abcdef0101000000000000
      0090d336b734c301ffffff0011223344
      0000000002000c0044004f004d004100
      49004e0001000c005300450052005600
      450052000400140064006f006d006100
      69006e002e0063006f006d0003002200
      7300650072007600650072002e006400
      6f006d00610069006e002e0063006f00
      6d000000000000000000
    

    これに ステップ 2 で生成した NTLMv2 ハッシュを鍵として用いて、HMAC-MD5 を適用することで、16 ビットの値「0xcbabbca713eb795d04c97abc01ee4983」が生成される。

  5. この値を blob と結合したものが NTLMv2 レスポンスである。
    0xcbabbca713eb795d04c97abc01ee4983
      01010000000000000090d336b734c301
      ffffff00112233440000000002000c00
      44004f004d00410049004e0001000c00
      53004500520056004500520004001400
      64006f006d00610069006e002e006300
      6f006d00030022007300650072007600
      650072002e0064006f006d0061006900
      6e002e0063006f006d00000000000000
      0000
    

LMv2 レスポンス

LMv2 レスポンスは、古いサーバとのパススルー認証の互換性を維持するために用いられる。クライアントの通信先のサーバが実際の認証を行なわず、ドメインコントローラにレスポンスを提示する形態はごく一般的なことである。古いサーバでは、LM レスポンスのみを提示していた。これは 24 バイトのバイト列であった。LMv2 レスポンスはこれらのサーバを適切に動作させるために設計された。実際のところ、LMv2 レスポンスは「最低限の」NTLMv2 レスポンスである。以下に計算例を示す (Java による実装例については 付録 D を参照のこと)。

  1. NTLM パスワードハッシュが計算される (Unicode ベースの大文字小文字が混在したパスワードの MD4 ダイジェストである)。
  2. 大文字化したユーザ名の Unicode 文字列と、大文字化した authentication target (ドメイン名もしくはサーバ名) の Unicode 文字列が結合される。HMAC-MD5 メッセージ認証コードアルゴリズムが、16 バイトの NTLM ハッシュを鍵として、このバイト列に適用される。この結果生成される 16 バイトの値が NTLMv2 ハッシュである。
  3. ランダムな 8 バイトの client nonce が生成される (これは NTLMv2 の blog で用いられているものと同じである)。
  4. Type 2 メッセージからのチャレンジが client nonce と結合される。HMAC-MD5 メッセージ認証アルゴリズムが (ステップ 2 で計算された) 16 バイトの NTLMv2 ハッシュを鍵として、このバイト列に適用される。これにより 16 バイトのバイト列が出力される。
  5. この値を 8 バイトの client nonce と結合したものが、 24 バイトの LM2 レスポンスである。

この処理を、前述した値を用いた例で簡単に説明しよう。

ドメイン名: DOMAIN
ユーザ名: user
パスワード: SecREt01
チャレンジ: 0x0123456789abcdef

  1. Unicode ベースの大文字小文字の混在したパスワードは、16 進数で「0x53006500630052004500740030003100」である。このバイト列の MD4 ハッシュを計算すると、「0xcd06ca7c7e10c99b1d33b7485a2ed808」となる。これが NTLM ハッシュである。
  2. 大文字化したユーザ名の Unicode 文字列を大文字化した authentication target の Unicode 文字列と結合して、「USERDOMAIN (16進数で「0x550053004500520044004f004d00410049004e00」)」という文字列を得る。 以前に生成した 16 バイトの NTLM ハッシュを鍵として、この文字列に HMAC-MD5 を適用する。この結果、「0x04b8e0ba74289cc540826bab1dee63ae」というバイト列が生成される。これが NTLMv2 ハッシュである。
  3. ランダムな 8 バイトの client nonce が生成される。NTLMv2 のサンプルと同様に、ここでは「0xffffff0011223344」というバイト列を用いることとする。
  4. ここで Type 2 チャレンジに client nonce を結合する。

    0x0123456789abcdefffffff0011223344

    ステップ 2 で生成した NTLMv2 ハッシュを鍵として、上記のバイト列に HMAC-MD5 を適用する。これにより「0xd6e6152ea25d03b7c6ba6629c2d6aaf0」という 16 バイトのバイト列が生成される。

  5. この値を client nonce と結合した以下の 24 バイトのバイト列が LMv2 レスポンスである。

    0xd6e6152ea25d03b7c6ba6629c2d6aaf0ffffff0011223344

NTLM2 セッションレスポンス(NTLM2 Session Response)

NTLM2 セッションレスポンスは(「Negotiate NTLM2 Key」フラグを有効にすることで使用できる) NTLM2 セッションセキュリティと共に用いることができる。 これにより、NTLMv2 認証がサポートされない環境において、計算済の辞書攻撃(特に Rainbow Table ベースの攻撃)に対する耐性が強化される。

NTLM2 セッションレスポンスは、以下で記載するように、LM レスポンスと NTLM レスポンスの両方を置き換える (Java による実装については 付録 D を参照のこと)。

  1. ランダムな 8 バイトの client nonce が生成される。
  2. client nonce が 24 バイト長になるように NULL パディングされる。この値は、Type 3 メッセージの LM レスポンス欄に格納される。
  3. session nonce を生成するために Type 2 メッセージに含まれるチャレンジが 8 バイトの client nonce と結合される。
  4. MD5 メッセージダイジェストアルゴリズム (RFC 1321 で記載) が session nonce に適用され、16 バイトのバイト列が生成される。
  5. NTLM2 セッションハッシュを生成するため、このバイト列が 8 バイトに切り詰められる。
  6. NTLM パスワードハッシュが生成される (前述したように、Unicode ベースの大文字小文字が混在したパスワードの MD4 ダイジェストである)。
  7. 16 バイトの NTLM ハッシュが 21 バイトになるように NULL パディングされる。
  8. このバイト列を 3 つの 7 バイトの値に分割する。
  9. これらの値を用いて、3 つの DES 鍵が生成される。
  10. 鍵の各々が NTLM2 セッションハッシュを DES 暗号化するために用いられる(その結果、 3 つの 8 バイト長の暗号文が生成される)。
  11. この 3 つの暗号文を結合して 24 バイトのバイト列とする。これが NTLM2 セッションレスポンスであり、Type 3 メッセージの NTLM レスポンスフィールドに格納されるものである。

先ほど用いたサンプル(ユーザのパスワードが「SecREt01」で、Type 2 チャレンジ「0x0123456789abcdef」に応答しようとしている)にこの処理を適用してみよう。

  1. A ランダムな 8 バイトの client nonce が生成される。ここでは先ほどの例と同様に「0xffffff0011223344」という値を用いることとする。
  2. チャレンジが 24 バイトとなるように NULL パディングされる。

    0xffffff001122334400000000000000000000000000000000

    この値は Type 3 メッセージの LM レスポンスフィールドに格納される。

  3. Type 2 メッセージのチャレンジが client nonce に結合される。このバイト列(「0x0123456789abcdefffffff0011223344」)が session nonce となる。
  4. この nonce に MD5 ダイジェストが適用され、16 バイトのバイト列「0xbeac9a1bc5a9867c15192b3105d5beb1」が生成される。
  5. NTLM2 セッションハッシュを取得するために、このバイト列が 8 バイトに切り詰められる( 「0xbeac9a1bc5a9867c」)。
  6. 大文字小文字の混在する Unicode 文字列のパスワード「0x53006500630052004500740030003100」に対して MD4 ダイジェストが適用され、NTLM ハッシュ(「0xcd06ca7c7e10c99b1d33b7485a2ed808」)が生成される。
  7. この値が 21 バイトとなるように NULL パディングされ、「0xcd06ca7c7e10c99b1d33b7485a2ed8080000000000」というバイト列が生成される。
  8. この値が 3 つの 7 バイト長のバイト列、「0xcd06ca7c7e10c9」、「0x9b1d33b7485a2e」、「0xd8080000000000」に分割される。
  9. このバイト列から 3 つの DES 鍵が生成される (先の NTLM レスポンスの例で計算したのと同様にして、「0xcd83b34fc7f14392」、「0x9b8f4c767543685d」、「0xd904010101010101」という 3 つの鍵が生成される)。
  10. 3 つの鍵の各々が NTLM2 セッションハッシュ(「0xbeac9a1bc5a9867c」)を暗号化するために用いられる。これにより、「0x10d550832d12b2cc」(1 つ目の鍵から)、「0xb79d5ad1f4eed3df」(2つ目から)、「0x82aca4c3681dd455」 (3 つ目の鍵から)というバイト列が生成される。
  11. これら 3 つの暗号文を結合したものが 24 バイトの NTLM2 セッションレスポンス

    0x10d550832d12b2ccb79d5ad1f4eed3df82aca4c3681dd455

    であり、Type 3 メッセージの NTLM レスポンスフィールドに格納される。

匿名レスポンス(Anonymous Response)

匿名レスポンスは、クライアントが実在するユーザのコンテキストでなく、匿名コンテキストでの通信を確立したことを意味する。これは認証ユーザを必要としない処理のための「placeholder」が必要な場合に返却される。匿名コネクションは Windows の「Guest」ユーザと同一ではない(後者は実在するユーザアカウントであるが、匿名コネクションには如何なるアカウントも割り当てられない)。

匿名の Type 3 メッセージは、クライアントにより「Negotiate Anonymous」フラグが設定され、NTLM レスポンスフィールドが空(0 バイト長)、LM レスポンスフィールドには 1 バイトの NULL 文字(「0x00」)が格納される。

Type 3 Message の例

Type 3 レスポンスについての知識を深めるため、以下の Type 3 メッセージを解析してみよう。

    4e544c4d5353500003000000180018006a00000018001800
    820000000c000c0040000000080008004c00000016001600
    54000000000000009a0000000102000044004f004d004100
    49004e00750073006500720057004f0052004b0053005400
    4100540049004f004e00c337cd5cbd44fc9782a667af6d42
    7c6de67c20c2d3e77c5625a98c1c31e81847466b29b2df46
    80f39958fb8c213a9cc6

This message is decomposed as:

0 0x4e544c4d53535000NTLMSSP 署名
8 0x03000000Type 3 識別子
12 0x180018006a000000 LM Response セキュリティバッファ:

長さ: 24 バイト (0x1800)
割当済領域: 24 バイト (0x1800)
オフセット: 106 バイト (0x6a000000)

20 0x1800180082000000 NTLM Response セキュリティバッファ:

長さ: 24 bytes (0x1800)
割当済領域: 24 バイト (0x1800)
オフセット: 130 バイト (0x82000000)

28 0x0c000c0040000000 ドメイン名セキュリティバッファ:

長さ: 12 バイト (0x0c00)
割当済領域: 12 バイト (0x0c00)
オフセット: 64 バイト (0x40000000)

36 0x080008004c000000 ユーザ名セキュリティバッファ:

長さ: 8 バイト (0x0800)
割当済領域: 8 バイト (0x0800)
オフセット: 76 バイト (0x4c000000)

44 0x1600160054000000 ワークステーション名セキュリティバッファ:

長さ: 22 バイト (0x1600)
割当済領域: 22 バイト (0x1600)
オフセット: 84 バイト (0x54000000)

52 0x000000009a000000 セッション鍵セキュリティバッファ:

長さ: 0 バイト (0x0000)
割当済領域: 0 バイト (0x0000)
オフセット: 154 バイト (0x9a000000)

60 0x01020000 フラグ:

Negotiate Unicode (0x00000001)
Negotiate NTLM (0x00000200)

64
0x44004f004d004100
  49004e00
ドメイン名データ (「DOMAIN」)
76 0x7500730065007200 ユーザ名データ (「user」)
84
0x57004f0052004b00
  5300540041005400
  49004f004e00
ワークステーション名データ (「WORKSTATION」)
106
0xc337cd5cbd44fc97
  82a667af6d427c6d
  e67c20c2d3e77c56
LM Response データ
130
0x25a98c1c31e81847
  466b29b2df4680f3
  9958fb8c213a9cc6
NTLM Response データ

この解析結果から、以下のことが確認できる。

Type 3 メッセージを受信したサーバは LM レスポンスと NTLM レスポンスを計算し、これらをクライアントから提供された値と比較する。これらが合致した場合、ユーザの認証は成功する。

NTLM Version 2

NTLM version 2 は 3 つの新しいレスポンスアルゴリズム(前述した NTLMv2、LMv2、NTLM2 session レスポンス)と 1 つの新しい署名および暗号化のスキーム(NTLM2 セッションセキュリティ)からなっている。 NTLM2 セッションセキュリティは「Negotiate NTLM2 Key」フラグが設定されていた場合にネゴシエートされる。ただし、NTLMv2 認証を有効にするには、レジストリの修正が必要である。更に、認証を成功させるためには、クライアントとドメインコントローラの両方で互換性のあるレジストリ設定を行なう必要がある(NTLMv2 認証を、この認証をサポートしていない古いサーバから NTLMv2 をサポートしたドメインコントローラにパススルーさせることは可能)。 NTLMv2 を用いるには、設定と計画が必須となることが、ほとんどの場合デフォルトの設定(NTLMv1)が用いられ、NTLMv2 認証が普及していない原因となっている。

NTLM Version 2 を有効にする方法の詳細については、 マイクロソフト社の技術情報 239869を参照のこと。一言でいうと、以下のレジストリ registry value:

    HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\LSA\LMCompatibilityLevel

(Windows 9x ベースのシステムでは LMCompatibility) の修正が必要となる。 これは REG_DWORD のエントリであり、以下の値のいずれかを設定できる。

レベルクライアントとして送信するレスポンスサーバとして受信するレスポンス
0 LM
NTLM
LM
NTLM
LMv2
NTLMv2
1 LM
NTLM
LM
NTLM
LMv2
NTLMv2
2 NTLM LM
NTLM
LMv2
NTLMv2
3 LMv2
NTLMv2
LM
NTLM
LMv2
NTLMv2
4 LMv2
NTLMv2
NTLM
LMv2
NTLMv2
5 LMv2
NTLMv2
LMv2
NTLMv2

NTLM2 セッションセキュリティは、すべてのレベルでサポートされており、利用可能な場合はネゴシエートされる(ほとんどのドキュメントでは、NTLM2 セッションセキュリティはレベル 1 以上でのみ有効であるという記載があるが、確認した限りレベル 0 でも同様に有効であった。 Windows 95 および Windows 98 プラットフォームがデフォルトでサポートするのは LM レスポンスのみであるが、ディレクトリサービスクライアントをインストールすることで、NTLMv2 もサポートされるようになる (加えて LMCompatibility の設定が有効となるが、設定可能な値は 0 と 3 のみとなる)。

レベル 2 の場合、クライアントは NTLM レスポンスを 2 重に (LM レスポンスと NTLM レスポンスフィールドの両方で) 送信する。レベル 3 以上では、LM と NTLM レスポンスが LMv2 と NTLMv2 レスポンスに置き換えられる。

NTLM2 セッションセキュリティがネゴシエートされた場合 (「Negotiate NTLM Key」フラグで確認可能)、レベル 0、1、2 では、脆弱な LM および NTLM レスポンスが NTLM2 セッションレスポンスに置き換えられる。 これにより、サーバ上での辞書攻撃に対する NTLMv1 の耐性が向上する。与えられたチャレンジに対するクライアントのレスポンスは、ランダムな client nonce を計算に加えることで、乱雑性の高いものとなる。

NTLM2 セッションレスポンスは、これをサポートしない古いドメインコントローラが存在していた場合でも、クライアントとサーバが新しいスキームをサポートしていればネゴシエートすることが可能となる点に注目したい。 典型的な例として、認証に関連するサーバはユーザのパスワードハッシュを持っておらず、ドメインコントローラのみが保持している場合を想定しよう。マシンが NT ドメインに参加した場合、暗号化された相互認証されたドメインコントローラとの間のチャネル(通常「NetLogon pipe」と呼んでいる) が確立される。クライアントが「本来の」NTLMv1 ハンドシェイクを用いてサーバに対して認証を行なう場合、背後では以下の処理が実行される。

  1. クライアントは Type 1 メッセージを送出する。これには前述したフラグその他の情報が含まれる。
  2. サーバはクライアントに対するチャレンジを生成し、negotiated フラグを設定した Type 2 メッセージで送信する。
  3. クライアントは LM/NTLM レスポンスによりチャレンジに対するレスポンスを行なう。
  4. サーバはチャレンジとクライアントのレスポンスを NetLogon pipe 経由でドメインコントローラに送信する。
  5. ドメインコントローラは認証の際の計算を軽減するため、格納されているハッシュとサーバから提供されたチャレンジを用いる。これらがレスポンスと合致すれば、認証は成功する。

NTLM2 セッションレスポンスが用いられる場合、クライアントとサーバの両方が新しいプロトコルを用いることが可能な状態である必要があるが、ドメインコントローラは古いままでよい。 上記を可能とするため、前述したハンドシェイクが以下のように若干修正される。

  1. クライアントは Type 1 メッセージを送出する。これには「Negotiate NTLM Key」フラグが設定されている。
  2. サーバはクライアントに対するチャレンジを生成し、negotiated フラグを設定した Type 2 メッセージで送信する。(「Negotiate NTLM2 Key」フラグも設定されている)
  3. クライアントはチャレンジに対するレスポンスを行なう。この際 client nonce が LM フィールドで、NTLM2 セッションレスポンスが NTLM フィールドで提供される。 NTLM2 セッションレスポンスでは、NTLM レスポンスと同じ計算が行なわれるが、サーバのチャレンジを暗号化する代わりに、クライアントはサーバのチャレンジと client nonce を結合したバイト列の MD5 ハッシュを暗号化する点が異なる。
  4. チャレンジを NetLogon pipe を経由して直接サーバからドメインコントローラに送出する代わりに、サーバはサーバのチャレンジと client nonce を結合したものの MD5 ハッシュを(LM レスポンスフィールドに格納して)送出する。 加えて、(通常通り)クライアントからのレスポンスも送出する。
  5. ドメインコントローラはサーバから送出されたチャレンジフィールドの内容を格納されているハッシュを鍵として暗号化し、それが NTLM レスポンスフィールドと合致していることを確認する。このため、クライアントの認証は成功する。
  6. ドメインコントローラは通常の NTLM ユーザセッション鍵を生成の上、サーバに送信する。サーバはそれを用いてNTLM2 セッションレスポンスのユーザセッション鍵(subsequent sectionで説明する)を生成するために、再度計算を行なう。

重要なのは、ドメインコントローラが古いままで NTLMv2 をサポートしていない(もしくはネットワーク管理者が LMCompatibilityLevel レジストリを設定して NTLMv2 を用いるようにしていない) ネットワークにおいても、クライアントとサーバが NTLM2 セッションレスポンスを用いることが可能な点である。

LMCompatibilityLevel の設定に関連する設定として、NtlmMinClientSecNtlmMinServerSec という設定がある。これらは NTLMSSP が確立する NTLM コンテキストで最低限必要とされる設定を指定するものである。 いずれも REG_WORD のエントリとなっており、以下の NTLM フラグを組み合わせて指定可能なビットフィールドとなっている。

これらのフラグを設定するよりは、NTLM2 の署名と暗号化を行なった方がより適切であろう。ただし、「Negotiate NTLM2 Key」だけは NTLM2 セッションセキュリティのネゴシエートが行なえないホストとの通信の確立を抑止するという意味で、認証において重要な役割を持つ。このフラグを設定すると、LM および NTLM レスポンスが送信されない(認証に際しては、最低でも NTLM2 セッションセキュリティを常に用いることを要求する)。

NTLMSSP と SSPI

ここで NTLM の認証機構全体の中での位置付けについて解説する。

Windows は SSPI - Security Support Provider interface という名称のセキュリティフレームワークを提供している。これは GSS-API (Generic Security Service Application Program Interface、RFC 2743) のマイクロソフト版であり、非常に高レベルで下位の認証機構に依存しない認証、整合性、秘匿性確保のフレームワークを提供する。 SSPI は幾つかの下位プロバイダをサポートしている。その一つが NTLMSSP (NTLM Security Support Provider) であり、ここまで解説してきた NTLM 認証機構を提供するものである。 SSPI は柔軟な opaque やプロバイダ固有の認証トークンを扱うための柔軟な API を提供する。NTLM Type 1、Type 2、Type 3 メッセージもこうしたトークンの一つであり、NTLMSSP によって定義され、処理されている。 SSPI によって提供される API の概要については、NTLM の詳細とはほとんど関係がない。 アプリケーションの開発者はNTLM が用いられているかことを意識する必要はなく、アプリケーションレベルでは無修正もしくは微小な修正で、(Kerberos など)別の認証機構に変更することが可能である。

ここでは SSPI のフレームワークについて深くは言及しないが、SSPI 認証のハンドシェイクが、どのように NTLM に対して適用されているかを俯瞰したい。

  1. クライアントは SSPI の AcquireCredentialsHandle 関数により、ユーザの資格情報に対応づけられた情報を取得する。
  2. クライアントは SSPI の InitializeSecurityContext 関数を呼び出して、認証リクエストトークン(ここでは Type 1 メッセージ)を取得する。 クライアントはこのトークンをサーバに送出する。関数からの返却値は認証が二段ステップであることを示す。
  3. サーバはクライアントからのトークンを受けとり、それを AcceptSecurityContext という SSPI 関数への入力値として用いる。これにより、サーバ上でクライアントに提示するローカルセキュリティコンテキストが生成され、クライアントに応答するための認証レスポンストークン(Type 2 メッセージ)が作成される。 関数からの返却値は、クライアントから更に情報が必要かどうかを示す。
  4. クライアントはサーバからのレスポンストークンを受けとり、再度 InitializeSecurityContext 関数を呼び出す。この時サーバのトークンを入力値としてそのまま渡す。これにより、先ほどとは異なる認証リクエストトークン (Type 3 メッセージ)が生成される。 この関数の返却値はセキュリティコンテキストが適切に初期化されたことを示す。このトークンがサーバに送出される。
  5. サーバはクライアントからのトークンを受けとり、再度 AcceptSecurityContext 関数を呼び出す。この際入力値として Type 3 メッセージを用いる。 返却値はコンテキストが正常に受け入れられたことを示す。さらなるトークンは生成されず、認証は完了する。

ローカル認証(Local Authentication)

ここまでの解説では、ローカル認証の手順について、さまざまな視点から言及してきた。SSPI の基本を理解することで、この詳細に確認できる。

ローカル認証は NTLM メッセージ中の情報に基づいて、クライアントとサーバ間で一連の決定が適切に行なわれた場合にネゴシエートされる。これは以下のように動作する。

  1. クライアントは AcquireCredentialsHandle 関数を 「pAuthData」パラメータに NULL を指定することでデフォルトの資格情報を指定した形で呼び出す。これにより、ログオンしているユーザの資格情報をシングルサインオン用途で扱うためのハンドルを取得する。
  2. クライナトは SSPI の InitializeSecurityContext 関数を発行して、Type 1 メッセージを生成する。デフォルトの資格情報のハンドルが提示された場合、Type 1 メッセージにはクライアントのワークステーションとドメイン名が格納される。これは、「Negotiate Doman Supplied」 および「Negotiate Workstation Supplied」フラグが設定され、メッセージにも supplied domain および supplied workstation セキュリティバッファが存在することで識別される。
  3. サーバはクライアントからの Type 1 メッセージを受けとり、AcceptSecurityContext を呼び出す。これにより、サーバ上にはクライアントに対応づけられたローカルセキュリティコンテキストが生成される。 サーバはクライアントから送付されたドメイン名とワークステーション名の情報を確認し、クライアントとサーバが同一マシンかどうかを確定する。 該当する場合、サーバは返却する Type 2 メッセージに「Negotiate Local Call」フラグを設定することで、ローカル認証を開始する。 Type 2 メッセージのコンテキストフィールドの最初の long は新規に取得された SSPI コンテキストハンドルの「上位」(SSPI の CtxtHandle 構造体の「dwUpper」フィールド)が格納される。 コンテキストフィールドの次の long は常に空となる (論理的には、コンテキストハンドルの「下位」が格納されると考えることもできるが)。
  4. クライアントはサーバから Type 2 メッセージを受けとり、それを InitializeSecurityContext に引き渡す。前述した「Negotiate Local Call」フラグのところで記載したように、クライアントはサーバのコンテキストハンドルを確認して、正当なローカルセキュリティコンテキストからのものであるかを判断する。 そのコンテキストが正当なものであることを確認できなかった場合、通常の認証が継続される。すなわち適切なレスポンスが計算され、ドメイン、ワークステーション、ユーザ名が Type 3 メッセージに格納される。 Type 2 メッセージのセキュリティコンテキストハンドルが正当なものであると確認できた場合は、こうしたレスポンスは生成されない。代わりにデフォルトの資格情報が、サーバのコンテキストに内部的に割り当てられる。その結果、Type 3 メッセージは実質的に空となり、ユーザ名、ドメイン名、ワークステーション名を示すセキュリティバッファは 0 バイト長となる。
  5. サーバは Type 3 メッセージを受けとり、それを AcceptSecurityContext 関数の入力値として用いる。サーバはセキュリティコンテキストがユーザに割り当てられたことを確認する。割り当てが行なわれた場合、認証は成功する。割り当てが行なわれなかった場合、認証は失敗する。

データグラム認証

データグラム形式の認証は、NTLM をコネクションレスの伝送路でネゴシエートする際に用いられる。メッセージの大半の部分は同一であるが、顕著な差異も幾つか存在する。

「通常の(コネクション型の)」認証の場合、すべてのオプションはクライアントとサーバ間で行なわれる初回のやりとり、Type 1 メッセージと Type 2 メッセージのやりとりの中でネゴシエートされる。 ネゴシエートされた設定はサーバ側で「記憶」されると共に、クライアントの Type 3 メッセージで適用される。 ほとんどのクライアントがネゴシエート済のフラグを Type 3 メッセージで送出するが、これらはコネクション型の認証では用いられない。

一方、データグラム認証においては、このやりかたに少し変更がある。サーバ側でネゴシエートされたオプションを記録する(コネクションレスの状態でこれを行なうのは困難である)必要性を低減するため、Type 1 メッセージは完全に削除される。サーバは Type 2 メッセージに(もちろんチャレンジと一緒に)サポート可能なすべてのフラグを格納して送出する。 クライアントはどのオプションをサポートするかを決定して、Type 3 メッセージにチャレンジに対するレスポンスと選択したフラグの情報を格納して応答する。データグラムにおける SSPI のハンドシェイク手順は以下のようになる。

  1. クライアントは AcquireCredentialsHandle を呼び出して、ユーザの資格情報セットの情報を取得する。
  2. クライアントは InitializeSecurityContext を呼び出し、fContextReq パラメータに必須フラグである ISC_REQ_DATAGRAM を渡す。 これにより、クライアントのセキュリティコンテキストが生成されるが、リクエストトークン(Type 1 メッセージ)は生成されない
  3. サーバは、必須フラグである ASC_REQ_DATAGRAM フラグを設定して、入力トークンを NULL として AcceptSecurityContext 関数を呼び出す。これにより、ローカルセキュリティコンテキストが生成され、認証レスポンスのトークン(Type 2 メッセージ)が生成される。この Type 2 メッセージにはサーバがサポートするさまざまなフラグと共に「Negotiate Datagram Style」フラグが設定される。これが通常通りクライアントに送出される。
  4. クライアントは受信した Type 2 メッセージを InitializeSecurityContext に渡す。 クライアントはサーバから提示された(設定が必須の「Negotiate Datagram Style」を含む)オプションからから適切なものを選択し、チャレンジに対するレスポンスを生成し、それらを Type 3 メッセージに格納する。このメッセージがサーバに返却される。
  5. サーバは Type 3 メッセージを AcceptSecurityContext 関数に渡す。メッセージはクライアントが選択したフラグに応じて処理され、コンテキストが適切に選択される。

SSPI の範疇では、データグラム形式の Type 1 メッセージを生成する方法は提供されていない。しかし、データグラムの Type 1 トークンを生成するために、低レベルな操作でNTLMSSP トークンを巧みに変更することにより、データグラム形式のメッセージを「作成」することが可能な点については記録しておきたい。

これは、コネクション型の SSPI ハンドシェイクにおける最初の InitializeSecurityContext 呼び出しにより生成された Type 1 メッセージに対して、トークンがサーバに渡される前に「Negotiate Datagram Style」フラグを設定することで実現される。 この修正された Type 1 メッセージを AcceptSecurityContext 関数に渡すと、サーバは (ASC_REQ_DATAGRAM が設定されていなくても)、データグラム型の認証を適用する。 これにより、「Negotiate Datagram Style」フラグが設定された Type 2 メッセージが生成されるが、それ以外の点は通常に生成されたコネクション型のメッセージと同一である。 これは、クライアントから送出された Type 1 メッセージ中のフラグが Type 2 メッセージを生成する際に考慮され、単純にサポートするすべてのオプションが送出されるわけではないということである。

クライアントは Type 2 のトークンを用いて InitializeSecurityContext を呼び出すことが可能である。この時点では、クライアントはコネクション型モードのままであることに注意。生成された Type 3 メッセージは Type 2 メッセージで適用されている「Negotiate Datagram Style」フラグを無視したままである。一方、サーバ側は強制的にデータグラム認証が適用されているため、Type 3 メッセージにこのフラグが明示的に設定されている必要がある。「Type 3 メッセージがサーバに送出される前に、手動で「Negotiate Datagram Style」フラグを追加することで、サーバはこの修正されたトークンを用いて、 AcceptSecurityContext の呼び出しを成功させることができる。

これにより認証が成功となる。「人工の」 Type 1 メッセージがサーバ側をデータグラム形式の認証モードに変更させたことが、Type 3 メッセージのフラグから確認できる。現実的な用途があるわけではないが、NTLM メッセージを意図的に操作することで、興味深い想定外の挙動が確認できる。

セッションセキュリティ - 署名と暗号化のコンセプト

SSPI では認証サービスに加えて、メッセージの整合性と機密性の機能が提供されている。これらも NTLM Security Support Provider に組み込まれている。「署名」は SSPI の MakeSignature 関数により実行され、これにより Message Authentication Code (MAC) がメッセージに付加される。 これにより受信者がメッセージの正当性を検証することが可能となり、メッセージが伝送路上で改竄されていないことが確実に保証される。署名は、送信者と受信者の両方が把握している秘密鍵を用いて生成される。MAC は鍵を所有していない限り検証ができない(これにより、署名が送信者によって行なわれたものであることが保証できる)。 「暗号化(Sealing)」は SSPI の EncryptMessage 関数によって行なわれる。これにより、伝送路上で第三者から閲覧されないようにメッセージが暗号化される。NTLMSSP は幾つかの対象鍵の暗号機構(暗号化と復号化に同じ鍵が用いられる)を用いている。

署名と暗号化に用いられる鍵は NTLM の認証プロセス中にプロダクト固有の方法で確立される。クライアントの身元の確認に加え、認証のハンドシェイク中には、送受信者間でのメッセージの署名と暗号化に必要な鍵の情報を含むクライアント、サーバ間のコンテキストの確立も行なわれる。 ここでは、こうした鍵の生成方法と、NTLMSSP による署名や暗号化における使用形態について記載する。

署名と暗号化には、さまざまな鍵のスキームが適用できる。ここでは鍵の形式とセッションセキュリティの中核について概説することから始めたい。

ユーザセッション鍵

これはセッションセキュリティを実現する上で、基本となる鍵である。これには以下のようにさまざまな形式が存在する。

使用されるメソッドは、Type 3 メッセージで送出されたレスポンスに依存する。これらのメソッドと計算方法については、以下に概説する。

LM User Session Key

LM レスポンスのみが提供される場合(Windows 9x クライアント)に使用される。LM User Session Key は以下のようにして生成される。

  1. 16バイト LM ハッシュ (計算済のもの) を 8 バイトに切り詰める
  2. これを 16 バイトになるように NULL パディングする。このバイト列が LM User Session Key である。

NTLM User Session Key

この形式は、クライアントが NTLM レスポンスを送出する際に用いられる。鍵の計算方式は、非常に単純である。

  1. NTLM ハッシュが取得される(大文字小文字を区別する Unicode ベースのパスワードの MD4 ダイジェスト、計算済のもの)
  2. MD4 メッセージダイジェストのアルゴリズムが NTLM ハッシュに適用される。この結果、16 バイトのバイト列が生成される。これが NTLM User Session Key である。

LMv2 User Session Key

LMv2 レスポンスが送出される(しかし、NTLMv2 レスポンスは送出されない) 場合に用いられる。この鍵の生成方法は若干複雑であるが、複雑過ぎるというレベルではない。

  1. NTLMv2 ハッシュが取得される(計算済のもの)。
  2. LMv2 client nonce が取得される (LMv2 レスポンスで用いられたもの)。
  3. Type 2 メッセージのチャレンジが client nonce と結合される。HMAC-MD5 メッセージ認証コードのアルゴリズムが NTLMv2 ハッシュを鍵として、このバイト列に適用される。これにより 16 バイトのバイト列が出力される。
  4. HMAC-MD5 アルゴリズムが NTLMv2 ハッシュを鍵として、このバイト列に再度適用される。この結果生成された 16 バイトのバイト列が LMv2 User Session Key となる。

NTLMv2 User Session Key

NTLMv2 レスポンスが送出される際に用いられる。この鍵の計算方法は、LMv2 User Session Key と非常に類似している。

  1. NTLMv2 ハッシュが取得される (計算済のもの)。
  2. NTLMv2 の「blob」が取得される(NTLMv2 レスポンスで用いられたもの)。
  3. Type 2 メッセージのチャレンジがこの blob と結合される。HMAC-MD5 メッセージ認証コードのアルゴリズムが NTLMv2 ハッシュを鍵として、このバイト列に対して適用される。この結果、16 バイトのバイト列が生成される。
  4. HMAC-MD5 アルゴリズムが NTLMv2 ハッシュを鍵として、このバイト列に再度適用される。この結果生成された 16 バイトのバイト列が NTLMv2 User Session Key となる。

NTLM2 Session Response User Session Key

NTLM2 セッションレキュリティを用いて NTLMv2 認証が行なわれる際に用いられる。この鍵は、NTLM2 セッションレスポンスの情報から以下のようにして生成される。

  1. NTLM User Session Key が前述したようにして取得される。
  2. session nonce が取得される (これは、前述したように Type 2 チャレンジと NTLM2 セッションレスポンスの nonce を結合したものである)。
  3. HMAC-MD5 アルゴリズムが NTLM User session Key を鍵として、この session nonce に適用される。この結果、生成される 16 バイトの値が NTLM2 Session Response User Session Key となる。

Null User Session Key

Null User Session Key は匿名認証が実施される際に用いられる。これは単純に 16 バイトの NULL バイト (「0x00000000000000000000000000000000」) となる。

Lan Manager Session Key

Lan Manager Session Key は、NTLM フラグの「Negotiate Lan Manager Key」が設定された際に、User Session Key の代わりとして NTLM1 署名と暗号化の際の鍵に用いられる。 Lan Manager Session Key の計算方法は以下のとおりである。

  1. 16 バイトの LM ハッシュ (計算済のもの) が 8 バイトに切り詰められる。
  2. これが 14 バイトになるように「0xbdbdbdbdbdbd」の値でパディングされる。
  3. この値が 2 つの 7 バイトのバイト列に分割される。
  4. これら 2 つのバイト列から 2 つの DES 鍵が生成される。
  5. これらの鍵の各々が LM レスポンスの最初の 8 バイトを DES 暗号化する際に用いられる (これにより 8 バイトの暗号文が 2 つ生成される)。
  6. これら 2 つの暗号文の値を結合して 16 バイトのバイト列としたものが、 Lan Manager Session Key となる。

Lan Manager Session Key は LM レスポンスを元にしたものである(LM ハッシュそのものではない)ことに注意。これは、サーバのチャレンジによって値が変化することを意味する。 これはパスワードハッシュのみに依存する LM User Session Key に対する優位点となる。 LM/NTLM User Session Keys は、ユーザがパスワードを変更しない限り、変わることがないが、Lan Manager Session Key は認証を実施する毎に変化する。 このため、Lan Manager Session Key は LM User Session Key より強力である。 とはいえ、これは NTLM User Session Key (認証毎に変化はしないものの 128 ビットの鍵空間を持つ。LM User Session Key と Lan Manager Session Key は両方とも有効な鍵空間が 64 ビットである) よりは脆弱であると考えて良いだろう。

鍵交換

「Negotiate Key Exchange」フラグがネゴシエートされた 場合、クライアントとサーバは、「第二の」鍵について合意したことになる。これは session key の代わりに認証と署名に用いられる。この処理は以下のようにして行なわれる。

  1. クライアントはランダムな 16 バイトの鍵を選択する(第二の鍵)。
  2. セッション鍵(User Session Key か Lan Manager Session Key のいずれか。「Negotiate Lan Manager Key」の設定に依存する) を用いて、第二の鍵が RC4 暗号化される。この結果、16 バイトの暗号文が生成される。
  3. この値が Type 3 メッセージの「Session Key」フィールドに格納されてサーバに送出される。