NTLM认证协议及SSPI的NTLM实现

没错,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消息并不和任何传输协议绑定, 它的认证消息理论上能够经过任何方式传递,因此咱们的讨论都集中在协议自己,而不去关心下层的传输方式。

咱们先看一个图:

image

 

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).  消息头

这三种消息都具备类似的消息头:

image

 

 

(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中的一个图来介绍:

image

 

a . 前面首先是 8个字节+4个字节的 协议头。前面已经介绍过了。注意,消息类型为1

b . 而后是四个字节的Flags。 前面也介绍过了。  这个Flags中表达了客户端想要使用的NTLM的认证服务, 以及客户端本身所支持的服务。

 

c. 而后,是若干个可选的security buffer。

注意上图中的 security buffer, 安全缓冲区。 它是一个8字节固定大小的结构体。它看起来像是这个样子:

image

它实际上是一个缓冲区指针。 它指向一个区域, 这个区域相对于Type1消息起始的偏移量为 offset, 这个区域的大小长度为 MaxLength, 其中的有效使用大小为:Length.

 

若是 Flags 中包含 0×1000 标记, 则须要提供域。把域名写入到这个缓冲区中。 字符集由Flags中的 OEM或者Unicode决定。

 

若是Flags中包含0×2000标记,则须要提供workstation的名字。  方法同上。

 

d. 而后是8字节的操做系统版本号。

若是Flags中包含0×2000000, 则须要提供 操做系统的版本号。

操做系统版本号的写法,请参考 参考文档1 的 2.2.2.10 节。

image

 

e. 而后,就是携带的前面security buffer中所指向的数据了。

 

在Type1的消息中,只有前面的 a 和 b项,是必须的, 后面的都是可选的。 

 

(4) Type2 消息

服务端在收到 Type1消息以后, 会生成Type2消息返回给客户端。

这个也比较简单,咱们仍是用 参考文档2 中的一个图来介绍:

 

image

 

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节)

image

 

i) 先是两个字节的int16值, 表示 id, 表示这个AV结构的类型。

目前有以下类型:

image

image

 

 

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

3. freebsd-freebsd  源码实现

4. Mozilla Firefox source code Developer Guide – MDN-firefox 源码

5. Heimdal NTLM 开源工程 

6. samba.org-pub-unpacked-samba_3_current-librpc-idl-ntlmssp.idl


byNeil
byNeil.com

相关文章
相关标签/搜索