沒錯,NTLM就是你據說過的那個NTLM。是微軟應用最普遍的認證協議之一。 NTLM是NT LAN Manager的縮寫,這也說明了協議的來源。NTLM 是 Windows NT 早期版本的標準安全協議。Windows 2000內置三種基本安全協議之一。 NTLM適用範圍很是廣,既可用於域內的認證服務, 也可用於沒有AD的環境,讓兩臺獨立電腦相互認證。 html
你可能天天都用到它而不自知,你也肯可能以爲你很熟悉它了,可是這裏可能還有你所不知道的背後的祕密。 好比,你可能知道NTLM能夠認證用戶身份,可是你可能不知道NTLM能夠提供會話安全服務(NTLM Session security)。 好了, 多的介紹我就很少說了。 網上的介紹很是多了。 咱們直接主角。 java
本文也會分紅幾篇來寫, 如今計劃可能包含下面幾個大方面的內容:git
一. NTLM協議的認證, 包括 NTLMv1 和 NTLMv2 兩個版本。 涉及到NTLM的認證全過程,以及NTLM 的 EPA(Extended Protection for Authentication)實現。 github
二. NTLM Session Security, 即NTLM的會話安全,或者說是NTLM的SSPI實現。 這一部份內容可能不是很經常使用,能找到的資料也比較少。 這裏先簡單的說兩句。 咱們都知道NTLM能夠用於認證,實際上,認證事後,NTLM還能提供安全服務。 好比提供對後續通訊的安全簽名,防篡改,或者對後續通訊進行安全可信賴加密,防止被竊聽。這一部分其實是微軟SSPI服務內容。 SSPI是Security Support Provider Interface(Microsoft安全支持提供器接口)。 SSPI是一個安全框架,不少協議都對它有實現。 好比咱們熟知的Kerberos認證協議,它也有SSPI實現的部分。 數據庫
三, NTLM消息的實例windows
這一部分,咱們會找一個具體的實例來解釋認證的全過程。 瀏覽器
值得注意的是 NTLM 如今已經不光用於windows平臺了,Linux 或者 java 平臺也能夠進行 NTLM 認證,使用 NTLM 的安全服務(可是Java平臺對NTLM的會話安全支持貌似有問題)。 安全
做爲一個開始,咱們先來介紹幾個基礎概念, 概念清楚了,而後再看技術細節。 服務器
1, 什麼是認證。網絡
認證就是認可和證實的意思。 就是你能證實你的身份。 好比你要訪問一個受保護的資源,服務器須要認證你的身份。 你能夠聲稱你是系統管理員, 可是怎麼證實你就是管理員呢。 方法不少,這裏有個簡單直接的方法就是證實你知道管理員的密碼。
認證的問題轉化爲: 「怎麼證實你知道你所聲稱的用戶的密碼?」這個問題了。
一個簡單暴力的證實方法是,讓你直接提供密碼給服務器,而後服務器去數據庫裏面比對,看你提供的密碼對不對。若是對,認證經過。不然失敗。 常見的所謂Windows Forms認證,或者叫作windows basic 認證就是這種方式。 簡單直接。 可是密碼須要在網絡上傳輸,安全問題就不說了,你懂的。
怎樣在不直接提供密碼的狀況下,間接證實你知道密碼呢? NTLM就是幹這個的了。
看起來很神奇,不是嗎。 好比對面有兩我的互相說話(通訊), 說的都是明文,每一句你都能聽懂。他們並無說本身的密碼就相互認證身份了,你聽了半天,殊不知道密碼是什麼。 更神奇的是, 他們認證以後,再說的話你可能就聽不懂了(NTLM Session security,會話安全)。 (若是他們協商會話安全以後,後續的通訊都是安全加密的。)
2. 什麼是NTLM 認證。
前面已經說過了, NTLM是一種在不直接提供密碼的狀況下,間接證實客戶端知道用戶密碼的方法。
NTLM認證最多見的應用場景恐怕就是用在瀏覽器(http協議)上的認證了。 可是實際上,NTLM 只規定了認證的流程,和認證消息格式。 並不跟具體的協議相關。 因此跟http就更沒有必然聯繫了。 瀏覽器只是在http協議頭上攜帶了NTLM的消息而已,經過了認證。 咱們知道http一般是明文的,因此若是直接傳輸密碼很是不安全,NTLM就有效的防止了這個問題。
怎麼理解這個問題呢,舉個誇張點的例子, 若是不嫌煩,客戶端和服務端甚至能夠經過傳遞小紙條的方式傳遞NTLM消息,來認證身份。 而紙條的內容是明文的, 中間傳遞者均可以隨意查看,可是卻沒法知道密碼,也沒法僞造。
如今能夠開始技術細節了, 咱們仍是採起由大及小的方式。 先從總體介紹,再逐步深刻。
1. NTLM 的 認證消息,及認證流程。
前面說過了,NTLM消息並不和任何傳輸協議綁定, 它的認證消息理論上能夠經過任何方式傳遞,因此咱們的討論都集中在協議自己,而不去關心下層的傳輸方式。
咱們先看一個圖:
NTLM認證共須要三個消息完成:
(1). Type1 消息。 Negotiate 協商消息。
客戶端在發起認證時,首先向服務器發送協商消息。 協商須要認證的主體,用戶,機器以及須要使用的安全服務等等信息。 並通知服務器本身支持的協議內容,加密等級等等。
(2). Type2 消息。 Challenge 挑戰消息。
服務器在收到客戶端的協商消息以後, 會讀取其中的內容,並從中選擇出本身所能接受的服務內容,加密等級,安全服務等等。 並生成一個隨機數challenge, 而後生成challenge消息返回給客戶端。
(3). Type3 消息。 Authenticate認證消息。
客戶端在收到服務端發回的Challenge消息以後, 讀取熬服務端所支持的內容,和隨機數challenge。 決定服務端所支持的內容是否知足本身的要求。 若是知足,則使用本身的密碼以及服務器的隨機數challenge經過複雜的運算,期間可能須要本身生成一個客戶端隨機數client challenge也加入運算, 並最終生成一個認證消息。併發回給服務器。
(4). 服務器在收到 Type3的消息以後, 回通過幾乎一樣的運算,並比較本身計算出的認證消息和服務端發回來的認證消息是否匹配。若是匹配,則證實客戶端掌握了正確的密碼,認證成功。 容許客戶端使用後續服務。 若是不匹配,則認證失敗。
2. NTLM 認證消息的結構.
NTLM的消息很簡單, 只有三種, Type1, Type2 和 Type3. 它們都有類似的結構。 認證消息都是二進制的,可是一般咱們見到的都是它們的Base64的編碼格式。 相似這種:
1: TlRMTVNTUAADAAAAGAAYAHAAAACSAJIAiAAAAAAAAAAAAAAAGgAaAEgAAAAOAA4AYgAAAAAAAAAAAAAABYKIogAAAAAAAAAPYQBkAG0AaQBuAGkAcwB0AHIAYQB0AG8AcgBOAEUASQBMAC0AUABDALZLpLeO2n6Sx1s9JjrAfQOqf2QsmfTeP9cjC86k7BsjZEsKzjOoYBcBAQAAAAAAAEDGE3IuR88Bqn9kLJn03j8AAAAAAgAEAEsAQQABAAoARgBTAFcARQBCAAQADABrAGEALgBjAG8AbQADABgAZgBzAHcAZQBiAC4AawBhAC4AYwBvAG0ABQAMAGsAYQAuAGMAbwBtAAcACAC0gtdyLkfPAQAAAAAAAAAA
因此,若是你看到這種形式不要吃驚,把他們用Base64解碼便可。
協議中的數字都是採用小端的方式存儲。
(1). 消息頭
這三種消息都具備類似的消息頭:
(2) Flags 標記。
這三種消息通常都會攜帶一個 4字節的int值, 做爲消息的Flags。 Flags在三種消息中的位置不同,因此沒有當作消息頭來介紹。 不過,這個flags很是重要。這裏先單獨來介紹:
(下面是個人代碼片斷,重點標誌我作了註釋,後面用到的時候會進一步介紹)
1: flagsExps[0x1] = "Unicode";
2: flagsExps[0x2] = "OEM";
3: flagsExps[0x4] = "Request Target";
4: flagsExps[0x8] = "r10(must be zero)";
5:
6: flagsExps[0x10] = "Negotiate Sign"; // 須要協商簽名服務
7: flagsExps[0x20] = "Negotiate Seal"; // 須要協商加密服務
8: flagsExps[0x40] = "Negotiate Datagram Style"; //UDP 非鏈接模式
9: flagsExps[0x80] = "Negotiate Lan Manager Key"; // 在某些特定的NTLMv1下使用 Lan manager key 後面會詳細介紹
10:
11: //================================
12: flagsExps[0x100] = "Negotiate Netware(r9 must be zero)";
13: flagsExps[0x200] = "Negotiate NTLM";
14: flagsExps[0x400] = "r8(should be zero)";
15: flagsExps[0x800] = "Negotiate Anonymous"; //使用匿名登陸
16:
17: flagsExps[0x1000] = "Negotiate Domain Supplied";
18: flagsExps[0x2000] = "Negotiate Workstation Supplied";
19: flagsExps[0x4000] = "Negotiate Local Call(r7)";
20: flagsExps[0x8000] = "Negotiate Always Sign";
21:
22:
23: //===============================
24: flagsExps[0x10000] = "Target Type is a Domain.";
25: flagsExps[0x20000] = "Target Type is a Server.";
26: flagsExps[0x40000] = "Target Type Share(r6)";
27: flagsExps[0x80000] = "Negotiate NTLM2 Key(EXTENDED_SESSIONSECURITY)"; //使用擴展會話安全
28:
29: flagsExps[0x100000] = "Request Init Response(NTLMSSP_NEGOTIATE_IDENTIFY)";
30: flagsExps[0x200000] = "Request Accept Response(r5, must be zero)";
31: flagsExps[0x400000] = "Request Non-NT Session Key";
32: flagsExps[0x800000] = "Negotiate Target Info"; //協商 攜帶 TargetInfo
33:
34: //===============================
35: flagsExps[0x1000000] = "r4(must be zero)";
36: flagsExps[0x2000000] = "NTLMSSP_NEGOTIATE_VERSION(協商攜帶操做系統版本號, 通常會忽略此項,僅供調試用途 )";
37: flagsExps[0x4000000] = "r3(must be zero)";
38: flagsExps[0x8000000] = "r2(must be zero)";
39:
40: flagsExps[0x10000000] = "r1(must be zero)";
41: flagsExps[0x20000000] = "Negotiate 128"; // 協商128位加密
42: flagsExps[0x40000000] = "Negotiate Key Exchange"; //協商交換key, 在會話安全中,使用交換key來加密內容,而不是直接使用會話key
43: flagsExps[0x80000000] = "Negotiate 56"; //協商56位加密
(3). Type 1 消息
我這裏使用下面參考文檔2中的一個圖來介紹:
a . 前面首先是 8個字節+4個字節的 協議頭。前面已經介紹過了。注意,消息類型爲1
b . 而後是四個字節的Flags。 前面也介紹過了。 這個Flags中表達了客戶端想要使用的NTLM的認證服務, 以及客戶端本身所支持的服務。
c. 而後,是若干個可選的security buffer。
注意上圖中的 security buffer, 安全緩衝區。 它是一個8字節固定大小的結構體。它看起來像是這個樣子:
它實際上是一個緩衝區指針。 它指向一個區域, 這個區域相對於Type1消息起始的偏移量爲 offset, 這個區域的大小長度爲 MaxLength, 其中的有效使用大小爲:Length.
若是 Flags 中包含 0×1000 標記, 則須要提供域。把域名寫入到這個緩衝區中。 字符集由Flags中的 OEM或者Unicode決定。
若是Flags中包含0×2000標記,則須要提供workstation的名字。 方法同上。
d. 而後是8字節的操做系統版本號。
若是Flags中包含0×2000000, 則須要提供 操做系統的版本號。
操做系統版本號的寫法,請參考 參考文檔1 的 2.2.2.10 節。
e. 而後,就是攜帶的前面security buffer中所指向的數據了。
在Type1的消息中,只有前面的 a 和 b項,是必須的, 後面的都是可選的。
(4) Type2 消息
服務端在收到 Type1消息以後, 會生成Type2消息返回給客戶端。
這個也比較簡單,咱們仍是用 參考文檔2 中的一個圖來介紹:
a . 前面首先仍是 8個字節+4個字節的 協議頭。前面已經介紹過了。 注意此時的消息類型已是 2了。
b . 而後是四個字節的Flags。 前面也介紹過了。 這個Flags中表達了服務端所能接受的服務。
c. 上圖中的TargetName 是要訪問的機器名。 存儲方法是security buffer. 前面介紹過了,很少說了。
d. Flags 很少說了。
e. Challenge, 這是一個服務端生成的8字節的隨機數, 客戶端會用來計算key. 後面會詳細介紹用法。
f. Context, 這是一個8字節的值,實際上是兩個連續的32位值。 表示一個內部handle。 當服務端發現客戶端就是自身的時候(在用一臺電腦上),就會生成一個內部的安全handle,填充到這個字段。而且在Flags中設置 0×4000標記。 表示這是一個本地認證。
g. TargetInformation 也是一個security buffer。 其存儲方式前面介紹過。 可是它的buffer中的內容須要介紹一下。基本上,TargetInformation是由一系列連續的 AV_PAIR 結構體鏈接起來的。
而AV_PAIR的結構以下:
(請參考下面 參考文檔1 的 2.2.2.1節)
i) 先是兩個字節的int16值, 表示 id, 表示這個AV結構的類型。
目前有以下類型:
ii) 而後是兩個字節的int16值,表示值的長度。
iii) 而後就是值。 它的長度在上面指定的。
這個TargetInformation 結構很是重要。 後面Type3中還會用到。
h. 而後是8字節的操做系統版本號。 Type1中已經介紹過了。 很少說了。
而後後面就是要攜帶的數據了。
到這裏先告一段落吧, 這裏介紹了 NTLM 的概念以及認證流程。 並詳細介紹了Type1和Type2消息, 以及Flags每一個標誌位的意義。 這大概覆蓋了NTLM內容的30%左右的內容。 Type3消息是整個認證中最爲複雜和關鍵的部分,咱們將在下一篇中獨立介紹。
但願你們多多支持。
歡迎訪問個人我的獨立博客: http://byNeil.com , 但願和你們多多交流共同進步。
參考文檔:
1. MS-NLMP-NT-LAN-Manager-NTLM-Authentication-Protocol-Specification
2. The NTLM Authentication Protocol and Security Support Provider
4. Mozilla Firefox source code Developer Guide – MDN-firefox 源碼
6. samba.org-pub-unpacked-samba_3_current-librpc-idl-ntlmssp.idl