The NTLM Authentication Protocol and Security Support Provider (中文翻译)

来源:互联网 发布:c语言long是什么意思 编辑:程序博客网 时间:2024/04/30 11:39

正在学习Windows身份验证方面的东西,觉得这篇文章写的比较深入。没有找到中文版的,就花些时间把它翻译一下,希望对大家有所帮助。仅仅出于个人兴趣进行翻译,限于个人水平,难免有错误纰漏,希望大家指正。文章比较长,我边翻译边更新。

英文版的资料可以在这里找到:  http://davenport.sourceforge.net/ntlm.html

NTLM身份验证协议和安全支持提供程序

摘要

        本文试图为读者讲述NTLM身份验证协议及相关的安全支持程序。我们会在中到高级的层次阐述技术细节,适合被作为开发者的参考文献。我们希望本文档能够逐步发展成为描述NTLM协议的综合资料;限于作者水平,本文当前还存在着一些纰漏,甚至可以说不甚准确。然而,阅读本文应该至少可以使读者为将来研究NTLM打下坚实的基础。文中的信息是开源jCIFS项目中实现NTLM身份验证库的基础,读者可以在http://jcifs.samba.org中获得相关的信息。本文是作者基于对NTLM的独立研究以及分析Samba套件中相关功能实现的基础上撰写的。

目录:

。何为NTLM

。NTLM相关术语

。NTLM消息头结构

    。NTLM标志

。NTLM消息类型1

        。消息类型1的一个例子

。NTLM消息类型2

        。消息类型2的一个例子

。NTLM消息类型3

        。名称变种

        。对挑战信息的响应

                。LM响应

                。NTLM响应

                。NTLMv2响应

                。LMv2响应

                。NTLM2会话响应

                。匿名响应

        。消息类型3的一个例子

。NTLM版本2

。NTLMSSP和SSPI

        。本地身份验证

        。基于数据报的身份验证

。会话安全– 签名和密封

        。用户会话密钥

                。LM用户会话密钥

                。NTLM用户会话密钥

                。LMv2用户会话密钥

                。NTLMv2用户会话密钥

                。NTLM2会话响应用户会话密钥

                。空(用户)用户会话密钥

        。Lan Manager会话密钥

        。密钥交换

        。密钥削弱

。NTLM1会话安全

        。NTLM1密钥的获取

                。主密钥协商

                。密钥交换

                。密钥安全降低

        。签名

        。密封

。NTLM2会话安全

        。NTLM2密钥的获取

                。主密钥协商

                。密钥交换

                。密钥安全降低

                。子密钥的生成

        。签名

        。密封

。其他会话安全相关话题

        。数据报签名和封装

        。伪签名

。附录A:相关链接和参考书

。附录B:NTLM在应用层协议中的应用?

        。HTTP中NTLM身份验证的应用

        。POP3中NTLM身份验证的应用

        。IMAP中NTLM身份验证的应用

        。SMTP中NTLM身份验证的应用

。附录C:举例说明NTLMSSP操作分解

        。NTLMv1身份验证;应用NTLM用户会话密钥进行NTLM1签名和密封

        。NTLMv1身份验证;应用LM用户会话密钥进行NTLM1签名和密封

        。NTLMv1身份验证;应用56位长的Lan Manager会话密钥进行NTLM1签名和密封

        。NTLMv1身份验证;应用40位长的Lan Manager会话密钥进行NTLM1签名和密封

        。NTLMv1数据报类型的身份验证;应用40位长的Lan Manager会话密钥进行NTLM1签名和密封。启用密钥交换功能

        。NTLMv1身份验证;应用NTLM用户会话密钥进行NTLM1伪签名和密封

        。NTLM2会话响应身份验证;应用128位NTLM2会话响应用户会话密钥进行NTLM2签名和密封。启用密钥交换协商功能

        。NTLM2会话响应身份验证;应用40位NTLM2会话响应用户会话密钥进行NTLM2签名和密封。

        。NTLMv2身份验证;应用40位NTLMv2用户会话密钥进行NTLM1签名和密封

        。NTLMv2身份验证;应用56位NTLMv2用户会话密钥进行NTLM2签名和密封

        。匿名NTLMv1身份验证;应用128位空用户会话密钥进行NTLM2签名和密封,启用密钥交换协商

        。本地NTLMv1身份验证;应用未知会话密钥进行NTLM2签名和密封,启用密钥交换协商(尚未分析完成)

。附录D:用Java实现对类型3响应的计算

什么是NTLM

        NTLM是一个身份验证和会话安全协议套件,主要应用于各种Microsoft网络协议,并由NTLM安全支持程序(NTLMSSP)提供支持(API)。NTLM最初被用来认证和协商安全DCE/RPC通道,后被应用于整个Microsoft系统,成为微软独占的一套认证机制。NTLM最被人熟知的应用大概是HTTP身份验证,它是集成Windows身份验证实现中的一部分。然而,NTLM也被应用于其他的Microsoft实现,比如SMTP,POP3,IMAP(都隶属于Exchange),CIFS/SMB,Telnet,SIP,以及可能的更多实现。

        Windows安全支持程序接口 (SSPI) 为NTLM安全支持程序 (NTLMSSP) 提供了诸如身份验证,集成和保密服务。SSPI指定了一个核心安全功能集,这些功能需要在支持程序中实现。NTLMSSP就是这样一套程序。SSPI指定,并由NTLMSSP实现了如下一些核心操作:

  1. 身份验证 --  NTLM提供了一个挑战-响应身份认证机制,客户端不用向服务器发送密码就可以证明自己的身份。
  2. 签名 – NTLMSSP提供了一种为消息进行数字签名的方法。这样可以确保一份被签过名的消息没有被修改过 (不管是有意的还是无意的),通信双方拥有共享的秘密信息。NTLM实现了一个对等签名机制 (消息验证码,MAC);也就是说,一个合法的签名只能由拥有共享密钥的参与者生成和验证。
  3. 密封 – NTLMSSP实现了一个对等密钥加密机制,可以确保消息保密。对于NTLM,密封同样意味着签名 (一份签过名的消息不一定需要密封,但所有密封的消息必须是签过名的) 。

        在域认证的情况下,NTLM已经很大程度上被Kerbeors取代。然而Kerberos是一个需要受信第三方的方案,如果没有第三方参与,Kerberos将无法工作。比如,成员服务器 (不隶属于域的服务器),本地帐户,以及对属于非信任域的资源进行身份验证。在这些情况下,NTLM依旧是首选的身份验证机制 (很可能在相当长的时间内都是这样)。

 

NTLM相关术语

        在我们进行深入讨论之前,让我们来熟悉一些协议中的相关术语。

        NTLM身份验证是一种挑战-响应机制,其中包含三种类型的消息,一般被称为类型1消息 (协商) ,类型2消息 (挑战) 和类型3消息 (身份验证) 。一般的工作机制如下所示: 

  1. 客户端发送类型1消息给服务器。此消息主要包含客户端支持服务器所需要的特性列表。
  2. 服务器返回一个类型2消息。此消息包含服务器支持的并允许使用的特性列表。重要的是,它还包含服务器生成的‘挑战’。
  3. 客户端回应这个挑战,返回类型3消息。这个消息中有一些关于客户端的信息,包括客户所在的域以及用户名。它还包含一个或多个对‘挑战’的响应。

        类型3消息中对‘挑战’的响应字段是最重要的部分,因为他们可以向服务器证明客户知道自己帐户的密码。

        身份验证处理在通信双方间建立起一个共享的上下文;这包含一个共享的会话密钥,用来进行后续的签名和密封操作。

        为了避免混淆,本文将使用如下的约定:

  • 当讨论身份验证时,协议版本将使用“v-数字”的形式;比如“NTLMv1身份验证”。
  • 当讨论会话安全的时候 (签名&密封),字母“v”将被移去;比如“NTLM1会话安全”。

        这样会使讲解变得相对清晰,当然除了谈到棘手的“NTLM2会话响应”身份认证的时候 (NTLMv1身份验证的一个变种,和NTLM2会话安全一起使用)。希望当我们讲到它时一切会听起来更有道理。

        当我们提到“短整型”的时候,我们指的是一个little-endian,16位的无符号数。例如十进制数“1234”,用短整型表示时,物理存放的将是16进制的“0xd204”。

        长整型指的是一个little-endian,32位的无符号数。例如十进制数“1234”,用长整型表示时,物理存放的将是16进制的“0xd2040000”。

        Unicode字符串是这样一串字符,其中每一个字符都由一个16位长的little-endian值组成(16位UTF格式,little-endian字节序,不包含字节顺序标记和零结束符) 。字符串“hello”的Unicode表示为“0x680065006c006c006f00”。

        一个OEM字符串指的是,字符串中的每一个字符都由8位二进制数表示,也就是一般说说的DOS字符页面。这样的字符串也不包含零结束符。在NTLM的消息中,OEM字符串一般会被转换成大写字母。字符“HELLO”的OEM表示为“0x48454c4c4f”。

        安全缓冲区是一个数据结构,用来指向一个包含二进制数据的缓冲区。这个数据结构包括:

  1. 一个短整型,用来表示以比特计数的缓冲区内容长度 (可以为0)
  2. 另一个短整型,表示以比特计数的缓冲区已分配长度。(大于或等于内容长度。一般等于内容长度)。
  3. 一个长整型,表示以比特计数的缓冲区偏移量。(从NTLM消息头到缓冲区起始的字节数)

4.       因此内容为“0xd204d204e1100000”的安全缓冲区可以被描述为:

长度:0xd204 (1234字节)

分配的空间:0xd204 (1234字节)

偏移量:0xe1100000 (4321字节)

        从消息的第一个字节算起,跳过4321个字节,就到了安全缓冲区的开头。需要读取1234个字节 (也就是整个缓冲区内容长度)。由于给缓冲区分配的长度就是1234字节,在读完这些字节后就到了缓冲区的结尾。

 

NTLM消息头格式

        现在是时候看一看NTLM身份验证消息的报头格式了。

        所有的消息开头都是NTLMSSP这几个字符,用ASCII字符表示,结尾附加一个0。这样16进制的表示就是:“0x4e544c4d53535000”。

        接下来是一个长整型,用来表示消息类型 (1,2或者3) 。类型为1的消息,对应的字段值为“0x01000000”。

        紧接着是和消息相关的信息,一般包含安全缓冲区以及消息标志。

NTLM标志

        消息标志包含在一个比特域中。这是一个长整型字段,其中每一位为一个标志。我们会在后续的讨论中详细解释这些标志,但在这里我们先提供一个列表来解释他们的基本意义。标记为“不能识别”或者“未知”的条目超出了作者目前的知识水平。

标志

名称

解释

0x00000001

协商Unicode

安全缓冲区数据中支持Unicode

0x00000002

协商OEM

安全缓冲区数据中支持OEM字符

0x00000004

目标主机请求

(客户端) 请求服务器在返回的类型2消息中包含服务器身份验证区域信息

0x00000008

未知

这个标志的用法还未被识别

0x00000010

协商签名

客户和服务器间认证的通信信息应该包含数字签名 (消息完整性)

0x00000020

协商密封

客户和服务器间认证的通信信息应该被加密 (消息私密性)

0x00000040

协商使用数据报类型

数据报方式的身份验证正在被使用

0x00000080

协商Lan Manager密钥

Lan Manager会话密钥应该被用来签名和密封认证的通信信息

0x00000100

协商Netware

这个标志的用法还未被识别

0x00000200

协商 NTLM

NTLM身份认证正在被使用

0x00000400

未知

这个标志的用法还未被识别

0x00000800

协商匿名会话

由客户端发送,包含在类型3消息中。表示一个匿名上下文被建立起来。它对响应字段同样有影响 (在“匿名响应”章节会用详述)

0x00001000

协商提供域信息

由客户端发送,包含在类型1消息中。表示客户端工作站所属的域信息包含在此消息中。服务器由此断定客户端是否有资格进行本地身份验证

0x00002000

协商提供工作站名称

由客户端发送,包含在类型1消息中。表示客户端工作站名称包含在此消息中。服务器由此断定客户端是否有资格进行本地身份验证

0x00004000

协商本地呼叫

由服务器发送,表示服务器和客户端在一台主机上。暗示客户端可以使用已经建立起来的本地凭据进行身份验证,而不是继续响应challenge

0x00008000

协商总是签名

表示客户端和服务器间的认证通信应该用伪签名进行签名

0x00010000

目标类型为域

由服务器发送,包含在类型2消息中。表示认证目标是一个域

0x00020000

目标类型为服务器

由服务器发送,包含在类型2消息中。表示认证目标是一个服务器

0x00040000

目标类型为共享

由服务器发送,包含在类型2消息中。表示认证目标是一个共享。大概这是认证共享的一个标志。具体用法不详

0x00080000

协商NTLM2密钥

表示认证通信应该使用NTLM2签名和密封。注意这里指的是特别的会话安全方案,而且和NTLMv2身份验证无关。然而这个标志对应的计算有影响。(在NTLM2会话响应章节中有详述)

0x00100000

请求初始化响应

这个标志的用法还未被识别

0x00200000

请求接受响应

这个标志的用法还未被识别

0x00400000

请求非NT会话密钥

这个标志的用法还未被识别

0x00800000

协商目标信息

由服务器发送,包含在类型2消息中。表示此消息包含一个目标信息块。目标信息块被用来计算NTLMv2响应。

0x01000000

未知

这个标志的用法还未被识别

0x02000000

未知

这个标志的用法还未被识别

0x04000000

未知

这个标志的用法还未被识别

0x08000000

未知

这个标志的用法还未被识别

0x10000000

未知

这个标志的用法还未被识别

0x20000000

协商128位加密

表示支持128位加密算法

0x40000000

协商密钥交换

表示客户端将在类型3消息的“会话密钥”字段中包含加过密的主密钥信息

0x80000000

协商56位加密

表示支持56位加密算法

 

         一个例子,假设一个消息设定了如下标志:

        协商unicode(0x00000001)

        请求目标主机信息 (0x00000004)

        协商NTLM(0x00000200)

        协商总是签名(0x00008000)

        将这些标志组合在一起就是“0x00008205”。在物理地址中的存贮表示就是“0x05820000”(因为需要用little-endian比特序来表示)。

 

类型1消息

下面我们来看一看NTLM类型1消息:

 

描述

内容

0

NTLMSSP字串

零结尾的 ASCII字符 "NTLMSSP" (0x4e544c4d53535000)

8

NTLM 消息类型

Long 长整型 (0x01000000)

12

标志

Long 长整型

(16)

域信息 (可选)

安全缓冲区

(24)

工作站信息 (可选)

安全缓冲区

(32)

操作系统版本信息(可选)

8 字节

(32) (40)

数据块起始 (如果有)

 

        类型1消息是由客户端发给服务器的,用来初始化一个NTLM身份验证。它主要的目的是创建身份验证过程的基本规则,这是通过设置消息标志位达成的。客户端也可以给服务器提供自己的工作站名称以及其所在的域信息;这些信息被服务器用来确定客户端是否有资格进行本地身份验证。

        一般的,类型1消息会设置如下标志位子集:

协商Unicode (0x00000001)

客户端通过设置这个标志表明它支持Unicode字符串

协商 OEM (0x00000002)

客户端通过设置这个标志标明它支持OEM字符串

请求目标信息 (0x00000004)

客户端希望服务器在类型2消息中包含服务器信息

协商 NTLM (0x00000200)

表示客户端支持NTLM身份验证

协商提供域信息 (0x00001000)

When set, the client will send with the message the name of the domain in which the workstation has membership.

这个标志置为1,表示客户端在消息中包含它所在的domain信息

协商提供工作站信息 (0x00002000)

Indicates that the client is sending its workstation name with the message.

表示客户端在消息中包含它的工作站名称

协商总是签名 (0x00008000)

表明客户端和服务器间的认证通信应该携带一个伪签名

协商NTLM2 密钥(0x00080000)

表明客户端支持NTLM2签名和密封。如果协商成功,它会对响应计算产生影响

协商 128 位加密(0x20000000)

表明客户端支持128位加密

协商 56位加密 (0x80000000)

表明客户端支持56位加密

        客户端提供的域是存储在一个安全缓冲区的数据结构中,是客户工作站所属域信息。域信息总是使用OEM格式表示,即使客户端支持Unicode。

        客户端提供的工作站信息也是存贮在安全缓冲区中,即工作站的名称。它也总是以OEM格式表示的。

        操作系统版本信息是在最近的Windows更新中才被引入的;它标识了主机操作系统的build号,它的格式如下所示:

 

Description

Content

0

主版本号

1 字节

1

次版本号

1 字节

2

Build 号

短整型

4

未知

总为0x0000000f

 

操作系统的build信息可以通过执行“winver.exe”获得;命令输出类似如下的字符串:

   Version 5.1 (Build 2600.xpsp_sp2_gdr.050301-1519 : Service Pack 2)

对应的操作系统信息标识为:"0x0501280a0000000f":

0x05

(主板本号为 5)

0x01

(此版本号为 1; Windows XP)

0x280a

(build号为2600 16进制的 little-endian表示)

0x0000000f

(未知/保留)

 

        需要注意的是操作系统版本,域或者工作站信息都是可选字段。目前常见的有三种类型1消息:

  1. 版本1 --  在这种类型的消息中,域、工作站和操作系统信息被彻底移除。在这种情况下标志字段为消息的结尾,整个消息长度为固定16字节。这种消息格式一般在Win9x系统中出现,在Open Group的ActiveX文档中有大致描述 (见章节11.2.2) 。
  2. 版本2 -- 这种类型的消息包含域和工作站的信息,但没有操作系统信息。数据块紧跟着安全缓冲区头,偏移量为32。这种格式出现在大部分out-of-box的windows版本中。
  3. 版本3 – 这种类型的消息包含所有三种信息:域、工作站以及操作系统信息。数据块跟在操作系统信息数据结构后,偏移量为40。这种形式的消息是在最近的Service Pack中引入的,我们可以在具有最新Service Pack的window 2000,windows XP 以及windows 2003中看到这样的消息。

        因此,最短的格式正确的类型1消息可以是:4e544c4d535350000100000002020000

        这是一个版本1的类型1消息,仅仅包含NTLMSSP字串,NTLM消息类型以及最少的消息标志 (协商NTLM和协商OEM)。

类型1消息的例子

        以这个16进制的类型1消息为例:

   4e544c4d53535000010000000732000006000600330000000b000b0028000000

   050093080000000f574f524b53544154494f4e444f4d41494e

我们把它分解为如下字段:

0

0x4e544c4d53535000

NTLMSSP 字符串

8

0x01000000

类型1消息

12

0x07320000

标志:

协商 Unicode (0x00000001)
协商OEM (0x00000002)
请求 目标信息 (0x00000004)
协商NTLM (0x00000200)
提供域信息 (0x00001000)
提供工作站信息 (0x00002000)

16

0x0600060033000000

域信息安全缓冲区数据结构:

长度: 6 字节 (0x0600)
分配空间: 6 字节 (0x0600)
偏移量: 51 字节 (0x33000000)

24

0x0b000b0028000000

工作站信息安全缓冲区数据结构

长度: 11 字节 (0x0b00)
分配空间: 11 字节 (0x0b00)
偏移量: 40 字节 (0x28000000)

32

0x050093080000000f

操作系统信息数据结构:

主版本: 5 (0x05)
次版本: 0 (0x00)
Build 号: 2195 (0x9308)
未知/保留 (0x0000000f)

40

0x574f524b53544154494f4e

工作站信息 ("WORKSTATION")

51

0x444f4d41494e

域信息 ("DOMAIN")

 

通过分析以上信息,我们可以得出:

  • 这是一个NTLM类型1消息 (通过NTLMSSP标记以及类型1指示位)
  • 客户端支持Unicode和OEM (对应的两个标志都被设置)
  • 客户端支持NTLM身份验证 (对应标志被设置)
  • 客户端请求服务器返回认证主机信息(对应标志被设置)
  • 客户端操作系统为:Windows 2000 (5.0), build 2195 (OS 数据结构指定)
  • 客户端在消息中包含自己所属域名:DOMAIN (相关标志被设置,并且域名包含在安全缓冲区中)
  • 客户端在消息中包含工作站名称:WORKSTATION。(相关标志被设置,并且工作站名包含在安全缓冲区中)

        需要注意的是工作站和域名用的是OEM格式。另外,如果有多个安全缓冲数据块,它们如何排序是无关紧要的。在上面这个例子中,工作站信息被放置在域信息前。

        客户端准备好类型1消息后,就把它发送给服务器。服务器需要分析这份请求,就像我们前面所作那样,然后准备一份应答。这将把我们带到下一个主题,累型2消息。

 

类型2消息

 

Description

Content

0

NTLMSSP 字串

以0结尾的ASCII字符串“NTLMSSP”

以16进制表示为(0x4e544c4d53535000)

8

NTLM 消息类型

长整型值 (0x02000000)

12

目标名称

安全缓冲区数据类型

20

标志

长整型

24

挑战

8 字节

(32)

上下文 (可选)

8 字节 (两个连续的长整型)

(40)

目标信息 (可选)

安全缓冲区数据类型

(48)

操作系统信息数据结构 (可选)

8 位

32 (48) (56)

数据块开始

 

       类型2消息由服务器发给客户端,作为对客户端类型1消息的相应。这个消息的目的是完成和客户端之间的参数协商,并且为客户端提供挑战。可选的,它可能还会包含认证目标的信息。

   典型的类型2消息包括如下标志:

协商Unicode (0x00000001)

服务器通过设置这个标志告诉客户端它将使用Unicode字符。只有当客户端生成其支持Unicode (在类型1消息中) ,服务器才会设置这个标志。这个标志和协商OEM标志一起,同时只允许一个被设置。

协商 OEM (0x00000002)

服务器通过设置这个标志告诉客户端它将使用OEM字符。只有当客户端生成其支持OEM (在类型1消息中) ,服务器才会设置这个标志。这个标志和协商Unicode标志一起,同时只允许一个被设置。

请求目标信息 (0x00000004)

这个标志在类型2消息中一般会被设置。这个标志的意义在类型1消息中显而易见,但在这里不是很清楚。

协商 NTLM (0x00000200)

这个标志表明服务器支持NTLM身份验证。

协商本地呼叫 (0x00004000)

服务器通过设置这个标志通知客户端,服务器和服务器在同一个主机上。服务器在消息中提供一个本地安全上下文句柄。

协商总是签名 (0x00008000)

这个标志指明身份验证后客户端和服务器间的通信需要携带一个伪签名。

目标类型域 (0x00010000)

服务器设置这个标志指明消息中包含认证目标,它是一个域。

目标类型是服务器 (0x00020000)

服务器设置这个标志指明消息中包含认证目标,它是一个服务器。

目标类型是共享(0x00040000)

服务器设置这个标志指明消息中包含认证目标,它是一网络共享。未确定。

协商 NTLM2 密钥 (0x00080000)

这个标志表明服务器支持NTLM2签名和密封。如果协商成功,他会影响客户端响应的计算。

协商目标信息(0x00800000)

服务器设置这个标志表明目标信息块被包含在消息中。

协商 128位加密(0x20000000)

这个标志表明服务器支持128位加密。

协商 56 位加密(0x80000000)

这个标志表明服务器支持56位加密。

 

目标名是一个安全缓冲区,包括认证目标的名称。它一般是对客户端请求目标信息的响应(类型1消息中对应标志被设置) 。它可以是一个域名,服务器名或者是一个网络共享。对应的目标类型是通过‘域目标类型’、‘服务器目标类型’和‘网络共享目标类型’标着设置的。目标名可以用Unicode或者OEM表示,由类型2消息中对应的标志控制。

挑战是一个8字节的随机数。客户端会用这个数字计算响应。

        上下文字段一般在协商本地呼叫标志被设置时才被填入数据。它包含一个SSPI上下文句柄,允许客户端绕过身份认证,而不用相应挑战。这个上下文用两个长整型表示。我们会在“本地身份验证”章节中详细介绍这个字段。

        目标信息是占用一个安全缓冲区,它包含目标信息数据块,被客户端用来计算NTLMv2相应(稍后会详述)。它有一系列子数据块组成,每一个数据块都包含:

字段

内容

说明

类型

短整型

Indicates the type of data in this subblock:

指明子数据块的数据类型

1 (0x0100):

服务器名称

2 (0x0200):

域名称

3 (0x0300):

完全限定DNS主机名 (i.e., server.domain.com)

4 (0x0400):

DNS 域名 (i.e., domain.com)

长度

短整型

以比特位单位的子数据块的长度

内容

Unicode 字符串

子数据块内容。总是用Unicode表示,即使标志位指明使用OEM

 

    这一系列子数据块由一个结束子数据块终结。这是一个类型为0的子数据块,长度字段为0。我们也见过类型为5的子数据块,显然它包含服务器的父域名称,如果这个服务器在一个子域中。这里或许还有其他未定义的子数据块类型。

操作系统信息数据结构我们已经在前面的章节中讨论过了。

象类型1消息一样,我们也观察到过几种版本的类型2消息:

  1. 版本1 – 上下文、目标信息和操作系统版本数据结构在这个版本中被移除。数据块 (仅包含目标名称数据缓冲区) 从32位偏移处开始。这种格式的消息可以在老的Win9x系统上见到。对它的粗略介绍可以在Open Group ActiveX参考资料中找到。
  2. 版本2 – 上下文、目标信息可以在版本2消息中看到,但操作系统信息并不包含在此消息内。数据块跟在目标信息头之后,偏移量为48。这种格式的消息多在Out-of-box的Windows版本中见到。
  3. 版本3 – 上下文、目标信息和操作系统版本都可以在这个版本的消息中看到。数据块跟在操作系统版本数据结构之后,偏移量为56。再次强调,缓冲区可以为空 (导致数据库长度为0)。 这种格式的消息出现在相对新一些的Service Pack上,包括新近更新过的windows 2000,windows XP以及windows 2003。

最简单的类型2消息看上去是这个样子:

4e544c4d53535000020000000000000000000000020200000123456789abcdef

       这个消息包含“NTLMSSP”字串、NTLM消息类型指示、空的目标名、最少的消息标志(协商NTLM和协商OEM),以及挑战。

 

类型2消息的一个例子:

        让我们看下面这个以16进制表示的类型2消息:

   4e544c4d53535000020000000c000c003000000001028100

   0123456789abcdef0000000000000000620062003c000000

   44004f004d00410049004e0002000c0044004f004d004100

   49004e0001000c0053004500520056004500520004001400

    64006f006d00610069006e002e0063006f006d0003002200

   7300650072007600650072002e0064006f006d0061006900

    6e002e0063006f006d0000000000

        可以把这个消息拆分正以下一些字段:

0

0x4e544c4d53535000

NTLMSSP 字串 (OEM格式)

8

0x02000000

类型2消息

12

0x0c000c0030000000

目标名称安全缓冲数据结构:

长度: 12 字节 (0x0c00)
分配长度: 12 字节 (0x0c00)
偏移量: 48 字节 (0x30000000)

20

0x01028100

标志:

协商 Unicode (0x00000001)
协商 NTLM (0x00000200)
目标类型是域(0x00010000)
协商目标信息(0x00800000)

24

0x0123456789abcdef

挑战

32

0x0000000000000000

上下文

40

0x620062003c000000

目标信息安全缓冲区:

长度: 98 字节 (0x6200)
分配长度: 98 字节 (0x6200)
偏移量: 60 字节 (0x3c000000)

48

0x44004f004d004100

  49004e00

目标名称数据 ("DOMAIN")

60

0x02000c0044004f00

  4d00410049004e00

  01000c0053004500

  5200560045005200

  0400140064006f00

  6d00610069006e00

  2e0063006f006d00

  0300220073006500

  7200760065007200

  2e0064006f006d00

  610069006e002e00

  63006f006d000000

  0000

目标信息数据:

0x02000c0044004f00

  4d00410049004e00

域名称子数据块:

类型: 2 (域名, 0x0200)
长度: 12 字节 (0x0c00)
数据: "DOMAIN"

0x01000c0053004500

  5200560045005200

服务器名称子数据块:

类型: 1 (服务器名称, 0x0100)
长度: 12 字节 (0x0c00)
数据: "SERVER"

0x0400140064006f00

  6d00610069006e00

  2e0063006f006d00

DNS格式的域名子数据块:

类型: 4 (DNS形式的域名, 0x0400)
长度: 20 字节 (0x1400)
数据: "domain.com"

0x0300220073006500

  7200760065007200

  2e0064006f006d00

  610069006e002e00

  63006f006d00

DNS格式的服务器名子数据块:

类型: 3 (DNS格式的服务器名, 0x0300)
长度: 34 字节 (0x2200)
数据: "server.domain.com"

0x00000000

终结符字数据块

类型: 0 (终结符, 0x0000)
长度: 0 字节 (0x0000)

 

    以下是对这个消息的分析:

  • 这是一个NTLM类型2消息 (NTLMSSP字串以及类型2指示字段)
  • 服务器指明字符串数据以Unicode表示 (对应标志被置1)。
  • 服务器支持NTLM身份验证。
  • 服务器提供了目标名,目标是一个域 (相关标志被置1,并且域名在目标名安全缓冲区内)。
  • 服务器提供了目标信息数据结构 (相关标志被设置)。这个数据结构被放置在目标信息安全缓冲区内 (域名为DOMAIN,服务器名为SERVER,DNS格式的域名为domain.com,DNS格式的服务器名为server.domain.com) 。
  • 服务器生成的挑战为"0x0123456789abcdef"。
  • 服务器发送了一个空的上下文信息。

·        Note that the target name is inUnicode format (as specified by the Negotiate Unicode flag).

    需要注意的是服务器名称是用Unicode来表示的(相关标志被设置)。

服务器准备好类型2消息后就把它发送给客户端。对这个挑战的响应是客户端返回的类型3消息。

 

类型3消息

 

说明

内容

0

NTLMSSP 签名

以零结尾的ASCII字符串"NTLMSSP" (0x4e544c4d53535000)

8

NTLM 消息类型

长整型 (0x03000000)

12

LM/LMv2 响应

安全缓冲区

20

NTLM/NTLMv2 响应

安全缓冲区

28

目标名

安全缓冲区

36

用户名

安全缓冲区

44

工作站名

安全缓冲区

(52)

Session Key (optional)

安全缓冲区

(60)

标志 (可选)

长整型

(64)

操作系统版本数据结构(可选)

8 字节

52 (64) (72)

数据块开始

 

        类型3消息是身份验证过程中的最后一个消息。它包含客户端对类型2消息中挑战的响应,以表明客户端知道帐户密码,且不用把密码发给服务器。类型3消息还包含其他信息,包括认证目标(域名或服务器名)、用户名以及客户端工作站名。

需要注意的是在类型3消息中,标志字段是可选的。比较老的客户端不在这个消息中设置会话密钥和标志信息。凭经验我们可以断定类型3中的标志字段在一个面向连接的身份验证中并不携带任何其他语义;略去标志字段并不会对身份验证或建立安全会话产生任何有形的影响。如果包含标志字段,客户端一般仅仅把类型2消息中的标志复制过来。类型3的标志很可能是对已经确定的选项的一个提醒,使服务器避免把协商的设置存入缓存。然而,如果使用数据报类型的身份验证,标志字段是有意义的。

LM/LMv2和NTLM/NTLMv2响应字段都是安全缓冲区,它们是通过使用用户密码进行计算得来,作为对类型2消息中的挑战的响应。关于如何计算这些响应我们会在下面的章节中讲述。

目标名是一个安全缓冲区,它包含认证帐户所在的认证区域(域名,如果是域用户;或者服务器名,如果是本地帐户)。目标名可以是Unicode或OEM字符串,这取决于协商的编码方法。

用户名是一个安全缓冲区,它包含帐户名。用户名可以是Unicode或OEM字符串,这取决于协商的编码方法。

工作站名是一个安全缓冲区,它包含客户端的工作站名称。工作站名可以是Unicode或OEM字符串,这取决于协商的编码方法。

会话密钥在密钥交换时被会话安全机制使用;这方面的内容我们会在“会话安全”部分详述。

如果在类型2消息中“协商本地呼叫”标志被置1,类型3消息中的安全缓冲区一般为空。客户端从类型2消息中获取SSPI上下文信息,这样它不必计算对服务器挑战的响应。

类型3消息中操作系统版本数据结构和前面讲述的没有变化。

如类型1和类型2消息,类型3消息也有几种变形:

  1. 版本1 – 会话密钥,标志和操作系统版本数据结构在版本1中被移除。数据块紧跟着工作站名安全缓冲头,偏移量为52。这种形式的消息出现在老的Win9x系统中。
  2. 版本2 – 会话密钥和标志包含在这个版本中,但是消息中没有操作系统版本数据结构。在这种情况下,数据块跟在标志字段后,偏移量为64。这种类型的消息在Out-of-box的windows版本中常见。在Open Group Active参考资料中有对此消息的大致介绍。
  3. 版本3 – 会话密钥、标志以及操作系统版本数据结构都被包含在这个版本的消息中。数据块跟在操作系统版本数据块后,偏移量为72。这种形式的类型3消息是在最近的Service Pack中被引入的,我们可以在新近更新过的Windows 2000、Windows XP和Windows 2003中看到这个版本的消息。

 

名称变种

        在类型1、类型2和类型3消息中,除了消息结构的变种,用户和目标名也可以被表示以不同的格式。在典型的场景中,用户名字段被填充以Windows帐户名,目标名字段被填充以NT域名。然而,用户名和域名也可以被表示为Kerberos风格的user@domain.com格式,它可能有多种组合。目前已经观测到好几种变种,在消息中有对使用这些变化的一些提示。

Format

Type 3 Field Content

Notes

DOMAIN\user

用户名 = "user"
目标名 = "DOMAIN"

这个是一般的格式;用户名字段包含windows用户名,目标名包含NT风格的NetBIOS域名或服务器名。

domain.com\user

用户名 = "user"
目标名 = "domain.com"

目标名是DNS格式的域或区域名(或者是完全DNS名,如果是本地认证的话)

user@DOMAIN

用户名= "user@DOMAIN"
目标名为空

目标名字段为空(长度为0),用户名字段为Kerberos风格的“user@realm”格式;然而,DNS风格的域名被NetBIOS域名取代。我们发现本地帐户认证不支持这种格式;NTLMv2/LMv2身份验证同样不知它。

user@domain.com

用户名 = "user@domain.com"
目标名为空

目标名字段为空。用户名字段为Kerberos风格的“user@realm”格式,域名为DNS域名。本地帐户认证不支持这种格式。

 

对挑战的响应

客户端生成一个或多个对挑战的响应,并把它们返回给服务器。一共有6种类型的响应消息:

  • LM (LAN Manager) 响应 – 大部分过时的客户端会发送这个响应,它也是最初的响应类型。
  • NTLM响应 – NT风格的客户端会发送这个响应,包括Windows 2000 和Windows XP。
  • NTLMv2响应 – 一种新的响应类型,最初在Windows NT service pack 4中被支持。在支持NTLM版本2的系统中,它会替代NTLM响应。
  • LMv2响应 – 在支持NTLM版本2的系统上,替换LM响应。
  • NTLM2会话响应 – 如果NTLM2会话安全被协商,并且不包括NTLMv2身份验证,将会使用这个响应。这个方案将改变LM和NTLM响应的语义。
  • 匿名响应 – 当建立一个匿名上下文的时候,这个响应会被用到。事实上没有身份验证过程并没有发生,没有任何凭据信息包含在这个响应中。这是类型3消息中包含“存根”字段

想知道更多关于这些响应模式的信息,我们推荐你读一下ChristopherHertel的实现CIFS,特别是身份验证的相关章节。

        对挑战的响应可以间接的证明客户端知道密码信息。客户端通过密码获取LM或者NTLM散 列 (在下一章中介绍); 然后这些散列值被用来计算适当的对挑战的响应。域控制器(或者服务器本身,如果是本地帐户登录的话)保存密码对应的LM和NTLM散列;当得到客户端的响应时,它将把存储的散列值和收到的信息进行比对,并根据比对结果返回适当的响应。如果两者匹配,客户端将完成一个成功的身份验证。

        和Unix密码散列不同的是,因为响应的计算方式,LM和NTLM散列可以说是和密码是等同的;因此必须保护它们,因为即使没有获得密码信息,仅凭这些散列我们就可以进行跨网络的身份验证。

LM响应

大部分客户端都会发送这个响应。这个响应模式比NTLM响应出现的早一些,但不如它安全。尽管更新的客户端支持NTLM响应,它们一般会同时发送LM和NTLM响应,以支持旧的系统。因此,LM中的安全缺陷在许多支持NTLM响应的系统总一样会出现。

LM响应是按照如下步骤计算的 (附录D是一个LM响应的Java实现) :

  1. 用户名 (OEM格式的字符串) 首先被转换成大写。
  2. 密码被补0以达到14位长度。
  3. 这个固定长度的密码被拆分成两个7字节的部分。
  4. 这两个值被用来创建两个DES密钥。
  5. 这两个密钥被用来使用DES加密ASCII字符串常量“KGS!@#$%”(生成两个8字节的密文)。
  6. 这两段密文被连接在一起形成一个16字节的值,即LM散列。
  7. 16字节的LM散列被补0到21字节。
  8. 将它拆分成3个7字节的部分。
  9. 用这三个值产生3个DES密钥
  10. 分别用这三个密钥用DES算法加密类型2消息中的挑战 (产生3个8字节的密文)
  11. 这三段密文被连接在一起,形成24字节的LM响应。

如果用户的密码超过15个字符,那么主机或域控制器将不存储这个用户的LM散列;在这种情况下LM响应将不能被用来进行身份验证。这是LM响应字段还是会出现在类型3消息中,只不过这是客户端用16字节的0作为LM散列来计算LM响应。最终目标主机会忽略这个LM响应。

我们最好举个例子来说明响应计算的过程。想象一个用户使用密码“SecRET01”,类型2消息中的挑战是“0x0123456789abcdef”

  1. OEM格式的密码被转换成大写“SECRET01”(或者以16进制表示的0x5345435245543031)。
  2. 密码补零到14字节:"0x5345435245543031000000000000"。
  3. 把上面的值拆分成两个7字节的部分:"0x53454352455430" 和 "0x31000000000000"。
  4. These two values are used to create two DES keys. A DES key is 8 bytes long; each byte contains seven bits of key material and one odd-parity bit (the parity bit may or may not be checked, depending on the underlying DES implementation). Our first 7-byte value, "0x53454352455430", would be represented in binary as:

01010011 01000101 01000011 0101001001000101 01010100 00110000

  1. 用步骤4中生成的两个值产生两个DES密钥。一个DES密钥为8字节长;每个字节包含7比特的密钥信息和1比特的奇偶校验位 (奇偶校验位可能会被检查,这取决于DES算法的具体实现) 。第一个7字节的数字"0x53454352455430",用二进制表示为:

01010011 01000101 01000011 01010010 01000101 0101010000110000

如果不检查奇偶校验位,对应的DES密钥为:

0101001010100010 0101000001101010 0010010000101010 0101000001100000

(the parity bits are shown in red above). This is "0x52a2506a242a5060" in hexadecimal. Applying odd-parity to ensure thatthe total number of set bits in each octet is odd gives:

(红色标记的数字为奇偶校验位) 。用16进制表示为"0x52a2506a242a5060"。如果包含奇偶校验位,需要确保填充位后每个字节的0的总数为奇数。

01010010 10100010 01010001 01101011 00100101 00101010 01010001 01100001

这是我们的第一个DES密钥:"0x52a2516b252a5161"。下面我们对第二个7字节的值进行同样的操作,"0x31000000000000",二进制的表示为:

0011000100000000 00000000 00000000 00000000 00000000 00000000

无奇偶校验位的DES密钥为:

00110000 10000000 00000000 00000000 00000000 00000000 00000000 00000000  (用16进制表示为"0x3080000000000000") 。

包含奇偶校验位的DES密钥为:

00110001 10000000 00000001 00000001 00000001 00000001 00000001 00000001

        这是我们第二个DES密钥,用16进制表示为"0x3180010101010101"。如果我们使用的DES实现不强制使用奇偶校验 (很多实现都不做要求) ,奇偶校验调整就可以被略去;未被进行奇偶校验处理的值将被作为DES密钥。在任何情况下,奇偶校验位都不会对加密过程产生影响。

  1. 这两个密钥都被用来加密ASCII字符常量"KGS!@#$%" ("0x4b47532140232425") 。加密后的输出分别是"0xff3750bcc2b22412" (使用第一个密钥)和"0xc2265b23734e0dac"(使用第二个密钥)。
  2. 将上面两个密文连接成16字节的LM散列 - "0xff3750bcc2b22412c2265b23734e0dac"。
  3. 将LM散列补零到21字节长:"0xff3750bcc2b22412c2265b23734e0dac0000000000".
  4. 把这个值拆分成3个7字节的字串:"0xff3750bcc2b224", "0x12c2265b23734e" 和 "0x0dac0000000000"。
  5. These three values are used to create three DES keys. Using the process outlined previously, our first value:
  6. 这三个值被用来创建3个DES密钥。用和前面所述同样的方法,第一个值为:

11111111 00110111 01010000 1011110011000010 10110010 00100100

对应的带奇偶校验位的DES密钥:

1111111010011011 1101010100010110 1100110100010101 1100100001001001

第二个值为:

0001001011000010 00100110 01011011 00100011 01110011 01001110

对应的DES密钥为:

00010011 01100001 10001001 11001011 10110011 00011010 11001101 10011101

第三个值为:

0000110110101100 00000000 00000000 00000000 00000000

对应的DES密钥为:

00001101 11010110 00000001 00000001 00000001 00000001 00000001 00000001

  1. 每一个密钥都被用来DES加密类型2消息中的挑战 (在我们这个了例子中,挑战为"0x0123456789abcdef") 。 这将产生3个密文:"0xc337cd5cbd44fc97" (使用第一个密钥),"0x82a667af6d427c6d" (使用第二个密钥)和"0xe67c20c2d3e77c56" (使用第三个密钥)。
  2. 将以上三个密文连接在一起就得到24字节长的LM响应:0xc337cd5cbd44fc9782a667af6d427c6de67c20c2d3e77c56

        使用的加密算法有好几个弱点,这使它易于受到攻击。对这些弱点的讨论是在赫特尔的论述中,我们这里主要提两个最显著的弱点:

  • 在计算响应前密码被转换成大写。这大大减少了必须要在暴力攻击提供的密码数。
  • 如果密码比7位还短,那么第三步中使用的第二个值将是7个空字节。这将使的一半长度的LM散列变得毫无意义 (因为用由7个空字节得到的DES密钥加密“KGS!@#$%”我们总会得到常量"0xaad3b435b51404ee") 。进而它会影响被用来计算响应的3个DES密钥。整个第三个密钥和第二个密钥的后7字节都将成为常量。

NTLM响应

        新一些的客户端会发送NTLM响应。这个响应模式解决了LM中存在的一些缺陷;然而,它还是被认为是相当薄弱的。另外,NTLM响应基本上是和LM响应一起被发送。LM算法可以被破解,首先获得不区分大小写的密码,然后用试错法找到NTLM响应中使用的大小写敏感密码。

        NTLM响应时按照如下步骤被计算出来的 (附录D中有它的一个Java实现):

  1. MD4消息摘要算法 (详见RFC 1320) 首先被应用到区分大小写的Unicode密码上。由此我们得到16字节长的NTLM散列。
  2. 16字节长的NTLM散列被补零到21字节。
  3. 这个值被拆分成3个7字节的部分。
  4. 这三个值被哟过来产生3个DES密钥。
  5. 每一个密钥被用来DES加密类型2中的挑战 (由此得到3个8字节的密文) 。
  6. 这三个密文被连接起来形成24字节的NTLM响应。

        注意只有计算散列的步骤和LM方案有所不同;响应的计算方式完全一样。为了举例讲述整个步骤,我们将使用前面同样的例子 (用户密码"SecREt01",类型2消息中的挑战为"0x0123456789abcdef") 。

  1. 区分大小写的Unicode密码是:"0x53006500630052004500740030003100";计算得出的MD4散列为"0xcd06ca7c7e10c99b1d33b7485a2ed808"。这就是NTLM散列。
  2. 补零到21字节长。
  3. 拆分成3个7字节的部分:"0xcd06ca7c7e10c9", "0x9b1d33b7485a2e" 和 "0xd8080000000000".

4.      这三个值被用来创建3个DES密钥。

第一个值:

11001101 00000110 11001010 0111110001111110 00010000 11001001

对应的带奇偶校验位的DES密钥:

11001101 10000011 10110011 01001111 11000111 11110001 01000011 10010010

第二个值:

1001101100011101 00110011 10110111 01001000 01011010 00101110

对应的带奇偶校验位的DES密钥:

10011011 10001111 01001100 01110110 01110101 01000011 01101000 01011101

第三个值:

1101100000001000 00000000 00000000 00000000 00000000 00000000

对应的带奇偶校验位的DES密钥:

11011001 00000100 00000001 00000001 00000001 00000001 00000001 00000001

  1. 每一个DES密钥都被用来DES加密类型2消息中的挑战("0x0123456789abcdef") 。这样我们得到三个密文:"0x25a98c1c31e81847","0x466b29b2df4680f3"和"0x9958fb8c213a9cc6"。
  2. 这三个密文被连接在一起形成24字节长的NTLM响应:

0x25a98c1c31e81847466b29b2df4680f39958fb8c213a9cc6

 

NTLMv2响应

        NTLM版本2(NTLMv2)的出现是为了解决NTLM响应中存在的安全隐患。当NTLMv2被启用时,在类型3消息中,NTLMv2响应将替换NTLM响应,LMv2(稍后会介绍)响应将代替LM响应。

        NTLMv2响应按照如下步骤进行计算(附录D中有NTLMv2响应计算的一个Java实现):

1.     首先得到NTLM散列(如前所述,这是一个对区分大小写的Unicode密码的MD4摘要)。

2.     将转换成大写的用户名的Unicode字符串和Unicode的认证目标拼接在一起(目标名字段中的域名或服务器名)。需要注意的是,即使在协商过程中选择OEM,计算响应时我们总是用Unicode;另外用户名也被转换成大写,而认证目标是大小写敏感并且必须和目标名字段中的值一致。

然后对这个字符串应用HMAC-MD5消息认证码算法,将16字节的NTLM散列作为密钥。这将是我们得到16字节的NTLMv2散列。

3.     一个被称为“blob”的数据块被创建起来。赫特尔的文章详细讨论了这个数据结构;这里我们做简单介绍:

 

Description

Content

0

Blob 签名

0x01010000

4

保留

long (0x00000000)

8

时间戳

Little-endian,64字节的有符号数,表示1601年1月1日以来的十万分之一秒的个数

16

客户端随机数

8 bytes

24

未知

4 bytes

28

目标信息

目标信息块(拷贝自类型2消息)

(variable)

未知

4 bytes

 

4.     类型2中的挑战被加到这个blob后。对这个字串应用HMAC-MD5消息鉴定码算法,使用16字节的NTMLv2散列作为密钥。这个过程的输出是一个16字节的数值。

5.     这个值被附加到类型2消息中的blob后面,这就是我们要的NTLMv2响应。

 

让我们来看一个例子。因为需要更多一点信息来计算NTLMv2响应,我们将用前面所举的例子中的一些数值:

目标:

DOMAIN

用户名:

user

密码:

SecREt01

挑战:

0x0123456789abcdef

目标信息:

0x02000c0044004f00

  4d00410049004e00

  01000c0053004500

  5200560045005200

  0400140064006f00

  6d00610069006e00

  2e0063006f006d00

  0300220073006500

  7200760065007200

  2e0064006f006d00

  610069006e002e00

  63006f006d000000

  0000

 

  1. 区分大小写的Unicode密码是"0x53006500630052004500740030003100";它的MD4散列为"0xcd06ca7c7e10c99b1d33b7485a2ed808"。这也就是MTLM散列。
  2. 转换成大写的用户名的Unicode和认证目标的Unicode被连接在一起,得到"USERDOMAIN" (或者用16进制表示的 "0x550053004500520044004f004d00410049004e00")。HMAC-MD5算法被应用到这个数值,使用NTLM散列作为密钥,我们得到"0x04b8e0ba74289cc540826bab1dee63ae"。这就是NTLMv2散列。
  3. 然后,blob被创建起来。时间戳是最麻烦的部分:看一下我书桌上的时钟,现在是EDT时间早上6点,日期是2003年6月17日。在Unix时间格式中,这是新纪元后的1055844000秒。加上11644473600秒后就是自1601年1月1日后的秒数(12700317600)。乘以10的7次方(10000000)得到127003176000000000。再转换成64位的little-endian数值,变成"0x0090d336b734c301"。

我们还得生成一个8字节的客户端随机数。我们使用一个看上去不怎么随机的数"0xffffff0011223344"。生成余下的blob就比较容易;我们只管把他们连接起来:

0x01010000

(the blob 签名)

0x00000000

(保留字段)

0x0090d336b734c301

(时间戳)

0xffffff0011223344

(客户端随机数)

0x00000000

(未知,不过全零可以供走)

0x02000c0044004f00

  4d00410049004e00

  01000c0053004500

  5200560045005200

  0400140064006f00

  6d00610069006e00

  2e0063006f006d00

  0300220073006500

  7200760065007200

  2e0064006f006d00

  610069006e002e00

  63006f006d000000

  0000

(目标信息blob)

0x00000000

(未知,但全零可以工作)

 

  1. 然后我们在blob前加上类型2消息中的挑战:

0x0123456789abcdef0101000000000000

  0090d336b734c301ffffff0011223344

  0000000002000c0044004f004d004100

  49004e0001000c005300450052005600

  450052000400140064006f006d006100

  69006e002e0063006f006d0003002200

  7300650072007600650072002e006400

  6f006d00610069006e002e0063006f00

  6d000000000000000000

对这个数值应用HMAC-MD5算法,使用NTLMv2散列作为密钥,这将输出16字节的数值:"0xcbabbca713eb795d04c97abc01ee4983"。

  1. 把这个16字节的数值加到blob之前,我们就得到NTLMv2响应:

   0xcbabbca713eb795d04c97abc01ee4983

 01010000000000000090d336b734c301

 ffffff00112233440000000002000c00

 44004f004d00410049004e0001000c00

 53004500520056004500520004001400

 64006f006d00610069006e002e006300

 6f006d00030022007300650072007600

 650072002e0064006f006d0061006900

 6e002e0063006f006d00000000000000

  0000

 

LMv2响应

        LMv2响应被用来提供传递身份验证,用以和旧的服务器系统兼容。很可能与客户端通信的服务器事实上并没有对客户端进行身份验证;确切的说,服务器会把这个响应发给域控制器进行验证。老的服务器仅仅传递LM响应,并希望它总是24字节长。LMv2响应就是为了让这样的系统能适当的处理传递性身份验证;它实际上是NTLMv2响应的一个缩小版,计算的步骤如下:(附录D中有一个LMv2响应的Java实现)

  1. 通过计算得到NTLM密码散列(区分大小写的密码施以MD4摘要算法)。
  2. 转换成大写的Unicode用户名和Unicode认证目标名(域名或服务器名)被连接在一起。HMAC-MD5消息鉴定码算法被应用在这个数值,使用16字节的NTLM散列作为密钥。我们得到16字节的NTLMv2散列。
  3. 一个8字节的客户端随机数被创建(这和NTLMv2 blob中的客户端随机数一样)。
  4. 类型2消息中的挑战和客户端随机数连接在一起。HMAC-MD5消息鉴定码算法被应用到这个数值,使用NTLMv2散列作为密钥。我们将的到16字节的输出。
  5. 这个数值和8字节的客户端随机数连接在一起形成24字节的LMv2响应。

 

        我们将举例说明这个过程,依旧使用以上例子中的数值:

目标名:

DOMAIN

用户名:

user

密码:

SecREt01

挑战:

0x0123456789abcdef

 

  1. 区分大小写的Unicode密码是"0x53006500630052004500740030003100"。对应的MD4散列是"0xcd06ca7c7e10c99b1d33b7485a2ed808"。这是我们的NTLM散列。
  2. The Unicode uppercase username is concatenated with the Unicode authentication target, giving "USERDOMAIN" (or "0x550053004500520044004f004d00410049004e00" in hexadecimal). HMAC-MD5 is applied to this value using the 16-byte NTLM hash from the previous step as the key, which yields "0x04b8e0ba74289cc540826bab1dee63ae". This is the NTLMv2 hash.
  3. 转换成大写的Unicode用户名和Unicode认证目标被连接在一起,得到"USERDOMAIN"(或者16进制表示的"0x550053004500520044004f004d00410049004e00")。HMAC-MD5算法被应用到这个数值,使用16字节的NTLM散列作为密钥,得出"0x04b8e0ba74289cc540826bab1dee63ae"。这就是NTLMv2散列。
  4. 创建一个客户端随机数:"0xffffff0011223344"。
  5. 类型2消息中的挑战信息和客户端随机数被连接在一起:

0x0123456789abcdefffffff0011223344

对这个数值应用HMAC-MD5算法,使用NTLMv2散列作为密钥,得到16字节的数值"0xd6e6152ea25d03b7c6ba6629c2d6aaf0"。

  1. 这个数值和客户端随机数连接起来形成24字节长的LMv2响应:

0xd6e6152ea25d03b7c6ba6629c2d6aaf0ffffff0011223344

 

NTLM2会话响应

        NTLM2会话响应和NTLM2会话安全一起工作(当“协商NTLM2密钥”标志被设置时会启用它)。在不完全支持NTLMv2身份验证的环境下,它可以被用来加强对预置字典攻击的保护(特别是基于彩虹表的攻击)。

        NTLM2会话响应替换LM和NTLM响应,具体过程如下所示(附录D中有NTLM2会话响应的一个Java实现):

  1. 创建一个8字节的客户端随机数。
  2. 这个随机数被补零到24字节。在类型3消息中,这个值被放在LM响应的字段。
  3. 类型2消息中的挑战和8字节的客户端随机数被连接起来形成会话随机数。
  4. MD5消息摘要算法(RFC1321)被应用到这个会话随机数上,得到一个16字节的数值。
  5. 这个数值被截短,前8个字节作为NTLM2会话散列。
  6. 计算得出NTLM密码散列(如前所述,这是MD4摘要算法被应用到区分大小写的Unicode密码上的输出)。
  7. 16字节的NTLM散列被补零到21字节。
  8. 这个数值被截成3个7字节的数值。
  9. 这些数值被作为DES密钥。
  10. 每一个密钥被用来DES加密NTLM2会话散列(得到3个8字节的密文)。
  11. 3个密文被连接起来形成一个24字节的数值。这就是NTLM2会话响应,被放置在类型3消息中的NTLM响应字段内。

        为了举例说明这个过程,我们同样使用前面的例子(密码"SecREt01",挑战"0x0123456789abcdef"):

  1. 8字节的客户端随机数被创建;这里我们使用"0xffffff0011223344"。
  2. 将挑战补零到24字节:

0xffffff001122334400000000000000000000000000000000

将这个值放置在类型3消息的LM响应字段

  1. 将类型2消息中的挑战和客户端随机数连接起来形成会话随机数("0x0123456789abcdefffffff0011223344")。
  2. Applying the MD5 digest to this nonce yields the 16-byte value "0xbeac9a1bc5a9867c15192b3105d5beb1".
  3. 对这个会话随机数应用MD5摘要算法,得到16字节的数值:

"0xbeac9a1bc5a9867c15192b3105d5beb1".

  1. 将以上数值截断,只保留前8个字节,作为NTLM2会话散列:

("0xbeac9a1bc5a9867c") 。

  1. 区分大小写的Unicode密码为"0x53006500630052004500740030003100";对它应用MD4摘要算法得到NTLM散列

("0xcd06ca7c7e10c99b1d33b7485a2ed808")。

  1. 将NTLM散列补零到21字节:

"0xcd06ca7c7e10c99b1d33b7485a2ed8080000000000"。

  1. This value is split into three 7-byte thirds, "0xcd06ca7c7e10c9", "0x9b1d33b7485a2e" and "0xd8080000000000".
  2. 这个数值被拆分成3个7字节的部分"0xcd06ca7c7e10c9", "0x9b1d33b7485a2e" 和 "0xd8080000000000"。
  3. 用这三个数值生成3个DES密钥(如前面NTLM响应例子所述,得到"0xcd83b34fc7f14392", "0x9b8f4c767543685d", 和 "0xd904010101010101")。
  4. 用这三个密钥分别DES加密NTLM2会话散列("0xbeac9a1bc5a9867c")。这将生成3个密文:"0x10d550832d12b2cc","0xb79d5ad1f4eed3df"和"0x82aca4c3681dd455"。
  5. These three ciphertext values are concatenated to form the 24-byte NTLM2 session response:

0x10d550832d12b2ccb79d5ad1f4eed3df82aca4c3681dd455

which is placed in the NTLM response field of the Type 3message.

  1. 这3个密文被连接在一起形成24字节的NTLM2会话响应:

0x10d550832d12b2ccb79d5ad1f4eed3df82aca4c3681dd455

它被放置在类性3消息的NTLM响应字段中。

 

匿名响应

        当客户端试图创建一个匿名上下文而不是基于真实用户的上下文,这就是所谓的匿名响应。当一个操作不需要涉及认证用户,而只需要一个占位符的时候,我们一般会看到这个响应。匿名连接和Windows的“Guest”用户不同(后者是一个真实的用户帐户,而匿名连接不与任何帐户关联。

        在匿名类型3消息中,客户端设置“协商匿名连接”标志;类型3消息中的NTLM响应字段为空(长度为0);LM响应字段只包含一个空字节("0x00") 。

 

类型3消息的一个例子

        现在我们对类型3消息已经有了一些认识,让我们看一下具体的消息结构:

   4e544c4d5353500003000000180018006a00000018001800

   820000000c000c0040000000080008004c00000016001600

   54000000000000009a0000000102000044004f004d004100

   49004e00750073006500720057004f0052004b0053005400

   4100540049004f004e00c337cd5cbd44fc9782a667af6d42

    7c6de67c20c2d3e77c5625a98c1c31e81847466b29b2df46

    80f39958fb8c213a9cc6

        我们将这个消息分解,用下表说明:

0

0x4e544c4d53535000

NTLMSSP 签名

8

0x03000000

类型3消息指示

12

0x180018006a000000

LM 响应安全缓冲区:

Length: 24 bytes (0x1800)
Allocated Space: 24 bytes (0x1800)
Offset: 106 bytes (0x6a000000)

20

0x1800180082000000

NTLM响应安全缓冲区:

Length: 24 bytes (0x1800)
Allocated Space: 24 bytes (0x1800)
Offset: 130 bytes (0x82000000)

28

0x0c000c0040000000

目标名安全缓冲区:

Length: 12 bytes (0x0c00)
Allocated Space: 12 bytes (0x0c00)
Offset: 64 bytes (0x40000000)

36

0x080008004c000000

用户名安全缓冲区:

Length: 8 bytes (0x0800)
Allocated Space: 8 bytes (0x0800)
Offset: 76 bytes (0x4c000000)

44

0x1600160054000000

工作站安全缓冲区:

Length: 22 bytes (0x1600)
Allocated Space: 22 bytes (0x1600)
Offset: 84 bytes (0x54000000)

52

0x000000009a000000

会话密钥安全缓冲区:

Length: 0 bytes (0x0000)
Allocated Space: 0 bytes (0x0000)
Offset: 154 bytes (0x9a000000)

60

0x01020000

标志:

协商 Unicode (0x00000001)
协商 NTLM (0x00000200)

64

0x44004f004d004100

  49004e00

目标名数据 ("DOMAIN")

76

0x7500730065007200

用户名数据 ("user")

84

0x57004f0052004b00

  5300540041005400

  49004f004e00

工作站名数据 ("WORKSTATION")

106

0xc337cd5cbd44fc97

  82a667af6d427c6d

  e67c20c2d3e77c56

LM 响应数据

130

0x25a98c1c31e81847

  466b29b2df4680f3

  9958fb8c213a9cc6

NTLM 响应数据

对每个字段进行分析:

  • 这是一个NTLM类型3消息(NTLMSSP签名和类型3指示符)。
  • 客户端指明消息中的字符串用Unicode表示(协商Unicode标志被置1)。
  • 客户端支持NTLM身份验证(协商NTLM标志被置1)。
  • 客户端所属域的名称为“DOMAIN”。
  • 客户端所使用的用户名为“user”。
  • 客户端工作站名称为“WORKSTATION”。
  • LM响应为:"0xc337cd5cbd44fc9782a667af6d427c6de67c20c2d3e77c56".
  • NTLM响应为:"0x25a98c1c31e81847466b29b2df4680f39958fb8c213a9cc6".
  • 会话密钥为空。

        服务器收到类型3消息后,它将计算LM和NTLM响应,并将它们和客户端提供的值进行比对;如果两者匹配,那么客户端认证成功。

 

NTLM版本2

        NTLM版本2包括3种新的响应算法(NTLMv2,LMv2以及NTLM2会话响应,我们在前面已经讨论过它们)以及签名和密封(NTLM2会话安全)。NTLM2会话安全是通过设置“协商NTLM2密钥”标志启用的;而NTLMv2身份验证,却是通过修改注册表启用的。另外,必须保证客户端和域控制器相关注册表的设置匹配,这样我们才能获得一个成功的身份验证(尽管有这样的可能,老旧的服务器将NTLMv2身份验证请求交给域控制器进行处理)。如果想启用NTLMv2,我们需要额外计划和配置主机,因为很多主机使用默认配置(NTLMv1),NTLMv2并没有真正被使用。

        在Microsoft Knowledge Base Article 239869中有对启用NTLM版本2的详细介绍;简而言之,就是需要修改注册表相关键值:

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\LSA\LMCompatibilityLevel

(LMCompatibilityon Win9x-based systems). 这是一个 REG_DWORD类型,可以被设置成一下数值列表中的一个:

Level

Sent by Client

Accepted by Server

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也支持它的状况)。默认情况下,Windows95和Windows 98系统支持LM响应;安装目录服务客户端后,它们也能支持NTLMv2(通过启用LMCompatibility设置),尽管他们只有等级0和等级3可以被选择。

        工作在等级2上的客户端在在类型3消息中包含两个NTLM响应(分别在LM响应字段和NTLM响应字段)。在3或者更高的等级上,LMv2和NTLMv2响应将分别代替LM和NTLM响应。

        当NTLM2会话安全被协商(“协商NTLM2密钥”标志被设置),在等级0,1和2上,NTLM2会话响应将替换安全级别相对弱的LM和NTLM响应。在NTLMv1之上,它可以提供对基于服务器的预设字典攻击的增强保护;客户端在对挑战的响应计算中加入了客户端随机数,增加了响应的随机度。

        NTLM2会话安全中有趣的一点是,它可以被支持新方案的客户端和服务器协商,即便有一个老的不支持NTLM2会话安全的域控制器参与到身份验证之中。在一个典型的场景中,在一个身份验证事务中,服务器并不真正处理用户的密码散列;相反,用户的密码散列被存贮在域控制器上。当一个主机加入NT风格的域,它和域控制器见建立一个加密的,双向认证的通道(通俗的讲就是“NetLogon管道”)。当客户端使用NTLMv1认证服务器,将发生如下步骤:

  1. 客户端发送类型1消息,包含标志和其他前面讨论过的信息。
  2. 服务器生成挑战,随类型2消息发送给客户端,这个消息还包含协商的标志信息。
  3. 客户端响应这个挑战,使用LM/NTLM响应。
  4. 服务器将挑战信息以及客户端响应通过NetLogon管道发给域控制器。
  5. 域控制器用保存的密码散列和挑战进行身份验证计算。如果计算结构和客户端响应匹配,那么身份验证成功。
  6. 域控制器计算并发送会话密钥给服务器,用来签名和密封服务器和客户端的后续通信。

7.     In the case of the NTLM2 SessionResponse, it is possible that a client and server have been upgraded to allowthe newer protocol, but the domain controller has not. To allow for thiscontingency, the handshake described above is modified slightly as follows:

        在NTLM2会话安全中,客户端和服务器可能已经支持新的协议,但是域控制器却没有。为了支持这种意外状况,上面的交互步骤被稍做修改:

  1. 客户端发送类型1消息,这里它会设置“协商NTLM2密钥”标志。
  2. 服务器生成挑战,随类型2消息发送给客户端,这个消息还包含协商的标志信息(其中设置“协商NTLM2密钥“标志)。
  3. 客户端对挑战进行响应,在LM字段包含客户端随机数,在NTLM响应字段加入NTLM2会话响应。需要注意的是后者采用和计算NTLM响应同样的方法,除去一点:客户端使用MD5加密服务器挑战与客户端随机数的连接串,而不是直接加密服务器挑战。
  4. 服务器通过NetLogon管道发送服务器挑战和客户端随机数(从类型2消息LM字段拿到)连接串的MD5散列,而不是发送服务器挑战。另外它还给域控制器发送客户端的响应。
  5. 域控制器用保存的密码散列作为密钥解密服务器发来的挑战,注意计算结构应该和NTLM响应字段一样;这样客户端身份验证成功。
  6. 域控制器计算并发送普通的NTLM用户会话密钥给服务器,服务器对它进行后续计算,的到NTLM2会话响应用户会话密钥(在后续章节中介绍)。

        这基本上可以允许升级后的客户端和服务器在域控制器不支持NTLMv2的网络中使用NTLM2会话响应(或者网络管理员还没有设置LMCompatibilityLevel来启用NTLMv2)。

        和LMCompatibilityLevel相关的另两个注册表项是NtlmMinClientSec和NtlmMinServerSec。它们定义了通过NTLMSSP建立NTLM上下文需要的最小需求集。它们都是REG_WORD类型项,位域代表了NTLM标志组合:

  • 协商签名(0x00000010) – 指出建立会话上下文时必须支持消息完整性(签名)。
  • 协商密封(0x00000020) –指出建立会话上下文时必须支持消息私密性(密封)。
  • 协商NTLM2密钥(0x00080000) – 指明建立会话上下文时必须支持NTLM2会话安全。
  • 协商128位加密(0x20000000) – 指明建立会话上下文时要至少支持128位的签名/密封密钥。
  • 指明建立会话上下文时要至少支持56位的签名/密封密钥。

        尽管以上大部分标志更适用于NTLM2签名和密封,“协商NTLM2密钥”在身份验证中也是至关重要,因为它可以防止和不支持NTLM2会话安全的客户端建立会话。它可以确保LM和NTLM响应不被发送(要求任何时候身份验证至少使用NTLM2会话响应)。

 

NTLMSSP和SSPI

        在这里我们看一看NTLM是如何被置于一个更大的范围内工作的。

        Windows提供了一个名叫SSPI安全框架 – 安全提供程序接口。这是微软的GSS-API(通用安全服务应用程序接口,RFC2742)的一个具体实现,它是一个高层次的,机制无关的实现认证,完整性和机密性的方法原型。SSPI支持好几个重要的安全提供程序,其中一个就是NTLMSSP(NTLM安全支持程序),它提供NTLM的身份验证机制,这正是我们正在讲解的东西。SSPI提供灵活的API来处理难于理解的、提供程序特有的身份验证标记;TNLM类型1、类型2和类性3消息就是这样一些标记,为NTLMSSP特有并被其所处理。SSPI提供的这个API抽象除去几乎所有NTLM的细节。应用程序开发者甚至不需要知道NTLM是否被使用,而且另外一种身份验证机制(如Kerberos)可以通过应用层上很少的修改,甚至不必修改,来替换NTLMSSP。

        我们不准备对SSPI框架进行深入讨论,不过看一下适用于NTLM的SSPI身份验证交互过程还是推荐的:

  1. 客户端通过SSPI的 AcquireCredentialsHandle函数获得用户的凭据集。
  2. 客户端调用SSPI的InitializeSecurityContext函数得到身份验证请求访问令牌(在本书中,这就是类型1消息)。客户端发送这个令牌给服务器。函数返回值指出身份验证需要多个步骤。
  3. 服务器收到客户端发来的访问令牌,并用它作为SSPI函数AcceptSecurityContext的输入。这将在服务器上建立一个代表客户端的本地安全上下文,并且生成认证响应令牌(类型2消息)返回给客户端。函数返回值指出服务器需要客户端提供后续的信息。
  4. 客户端收到服务器的响应令牌,再一次调用InitializeSecurityContext,用服务器的响应令牌作为函数输入。这让我们得到另外一个请求令牌(类型3消息)。函数返回值指明安全上下文被成功初始化。请求令牌被发往服务器。
  5. 服务器收到客户端的令牌并再次调用AcceptSecurityContext函数,类型3消息做为函数的输入。函数返回值指明上下文被服务器接收,没有新的令牌产生,认证过程结束。

 

本地身份验证

        在我们的讨论中多次提及本地身份验证;现在有了对SSPI的基本认识,我们可以对这个场景进行更详细的讨论:

       客户端和服务器根据NTLM消息中的一些信息,基于一系列决定来协商本地身份验证。这个过程如下所示:

  1. 客户端调用AcquireCredentialsHandle函数,通过将“pAuthData”参数置零来指定默认凭据。这让我们获得对登录用户凭据的一个一次性操作权限。
  2. 客户端调用SSPI函数InitializeSecurityContext来创建类型1消息。当提供默认凭据句柄时,类型1消息包含客户端所在的工作站和所属域的名称。“协商提供域名”和“协商提供工作站名”标志的存在告诉服务器相关的安全缓冲区被包含在消息中。
  3. 服务器收到客户端的类型1消息,并调用AcceptSecurityContext函数。这会在服务器上创建一个代表客户端的本地安全上下文。服务器检查客户端提供的域和工作站信息,来确认客户端时候是和服务器在同一台主机上。如果在同一台主机,服务器将通过设置类型2消息中的“协商本地呼叫”标志初始化一个本地身份验证。类型2消息上下文数据结构中的第一个长整型被放置新获得的SSPI上下文句柄的高位部分(具体说就是SSPI的CtxtHandle数据结构的“dwUpper”字段)。上下文中第二个长整型总为空。(尽管逻辑上我们会设想它应该包含上下文句柄数据结构的低位部分)。
  4. 客户端收到服务器的类型2消息,并把它传递给InitializeSecurityContext函数。注意到“协商本地呼叫”标志被设置,客户端检查服务器上下文句柄,确认它是否是一个合法的本地安全上下文。如果上下文无法被确认,客户端将使用一般的认证处理 – 计算得出适当的响应,类型3消息中会包括域名、工作站名以及用户名。如果类型2消息中的安全上下文句柄可以被确认,那么无论如何都不计算响应。相反,默认凭证信息被内在的和服务器上下文关联起来。这使得类型3消息完全为空,包含长度为0的安全缓冲区、用户名、域名和工作站名。
  5. 服务器收到客户端的类型3消息,用它作为AcceptSecurityContext函数的输入。服务器确认安全上下文是否已经和用户关联起来;如果是,认证过程成功完成。如果上下文没有被和用户绑定,认证失败。

 

基于数据报的身份验证

        数据报风格的身份验证被用来在非面向连接的运输层上协商NTLM。尽管消息中大部分的语义都没有变化,它们还是有几个明显的区别:

  • SSPI在第一次调用InitializeSecurityContext时并不创建类型1消息。
  • 身份验证选项由服务器提供,而不是由客户端请求。
  • 类型3消息中的标志携带它们一般的意义,而不是被赋予更多的语义(像面向连接的身份验证一样)。

        在普通(面向连接的)的身份验证中,所有选项在客户端和服务器的第一个交互事务中被协商,也就是类型1消息和类型2消息。协商过的设置被服务器记录下来,并被复制到客户端类型3消息中。尽管大部分客户端在类型3消息中包含协商过的标志,这个消息中的标志在面向连接的身份验证中未被使用。

        在基于数据报的身份验证中情况则不然;为了让服务器能够跟踪协商的选项(在没有一个持续的连接的情况下这将变得更加困难),类型1消息被完全移除。服务器生成类型2消息,其中包含所有支持的标志(当然也包括挑战)。客户端决定它支持哪一些选项,在类型3消息中包含对应的标志,以及对挑战的响应。基于数据报的身份验证的SSPI握手交互如下所述:

  1. 客户端调用AcquireCredentialsHandle函数得到用户的凭证信息。
  2. 客户端调用InitializeSecurityContext函数,通过提供fContextReq参数设置上下文需要的ISC_REQ_DATAGRAM标志。这样我们开始创建客户端安全上下文,但是并没有生成请求令牌(类型1消息)。
  3. 服务器调用AcceptSecurityContext函数,设置ASC_REQ_DATAGRAM上下文标志,并用一个空的令牌作为函数输入。这将创建一个本地安全上下文,并且生成一个认证响应令牌(类型2消息)。这个类型2消息包含“协商数据报风格”标志,以及所有服务器支持的标志。消息照例被发往客户端。
  4. 客户端收到类型2消息,把它作为InitializeSecurityContext函数的输入。客户端选择服务器支持的合适的选项(包括“协商数据报风格”标志,这是必选的标志),生成对挑战的响应,生成类型3消息。这个消息被发往服务器。
  5. 客户端把类型3消息作为AcceptSecurityContext函数的输入。这个函数根据客户端选择的标志来处理这个消息,然后上下文被成功建立。

        当和SSPI一起使用时,很明显我们无法生成数据报风格的类型1消息。有趣的是,我们可以在底层巧妙的操作NTLMSSP令牌,诱使数据报语义为我们生成自己的类型1令牌。

        这可以通过如下方法达到这个目的:在一个面向连接的SSPI握手过程中,从第一个nitializeSecurityContext函数调用中得到类型1消息,在将它发送给服务器之前,对这个消息进行修改,设置“协商数据报风格”标志。被修改过的类型1消息被传递给AcceptSecurityContext函数时,服务器将接受数据报风格的语义(即便ASC_REQ_DATAGRAM参数没有被设定)。这将使生成的类型2消息中“协商数据报风格”标志被置1,其余的信息都和一般的面向连接的消息别无二致;也就是说,客户端类型1消息中的标志被服务器作为生成类型2消息的参考,而不是简单的把所有服务器支持的选项置1。

        客户端然后调用InitializeSecurityContext,用类性2令牌作为函数输入。需要注意的是客户端现在还处于面向连接的模式。生成类型3消息时客户端会忽略类型2消息中的“协商数据报风格”标志。而服务器则会强制使用数据报风格的语义,这需要合理设置类型3消息中的标志。在发送类型3消息之前,我们手工设置这个标志,这样服务器就能成功使用修改过的令牌调用AcceptSecurityContext函数。

        这将是我们得到一个成功的身份验证。被“动过手脚“的类型1消息使服务器切换到数据报风格的身份验证模式。虽然没有什么实际用处,它可以给我们展示一些有趣的不可预料的行为,如果我们有意修改NTLM消息。

 

会话安全 – 签名&密封的概念

        除了身份验证服务,SSPI还提供保证消息完整性和私密性功能。这同样在NTLM安全提供程序中被实现。“签名”是由SSPI的MakeSignature函数完成的,它为消息生成一个消息鉴别码(MAC)。消息的接收方可以对它进行验证,确认消息在传输过程中没有被修改。签名使用一个密钥得到的,发送方和接收方都知道这个密钥。MAC只能被拥有密钥的个体验证(这样可以保证这个签名是由发送者创建的)。“密封”是由SSPI函数EncryptMessage完成的。它对消息进行加密,防止消息在传递过程中被第三方观察到。NTLMSSP用一些对等加密算法(加密和解密时使用同一个密钥)完成签名和密封。

        签名和密封所使用的密钥是NTLM身份验证过程的一个副产品;除了验证客户端的身份外,认证握手过程在客户和服务器间建立一个上下文,包括用来签名和加密消息的密钥。我们将讨论如何生成这些密钥,以及如何用这些密钥在NTLMSSP中进行签名和密封。

        在签名和密封时有数不清的密钥方案可供选择;我们将用对不同的密钥类型的概述和对核心会话安全概念的介绍开始我们的叙述。

用户会话密钥

        这是会话安全中一个基本的密钥类型。它有很多变种:

  • LM用户会话密钥
  • NTLM用户会话密钥
  • LMv2用户会话密钥
  • NTLMv2用户会话密钥
  • NTLM2会话响应用户会话密钥 

        生成密钥的方法依赖类型3消息中的响应。这些密钥变种和它们的计算方法如下所示。

LM用户会话密钥

       当只提供LM响应时使用(比如Win9x客户端)。LM用户会话密钥按以下方式计算:

  1. 16字节的LM散列被截断到8字节。
  2. 再补零到16字节,这就是LM用户会话密钥

        和LM散列一样,这个密钥仅当用户修改密码后才会变化。另外注意只有密码的前7位对这个密钥有贡献(见LM响应章节的响应计算过程;LM用户会话密钥为LM散列的前半部分)。另外,密钥空间实际上小得多,因为LM散列自己是基于转换成大写的密码计算的。所有这些因素导致LM用户会话密钥的防护攻击能力非常弱。

NTLM用户会话密钥

        当客户端发送NTLM响应时会使用这个密钥变种。这个密钥的计算方法非常简单:

  1. 首先得到NTLM散列(如前所述,这是对区分大小写的密码进行MD4摘要算法后的得出结果)。
  2. 再对这个散列应用MD4消息摘要算法,得出16字节的NTLM用户会话密钥。

        和LM用户会话密钥相比,NTLM用户会话密钥的安全性有了很大的改进。密码空间更大(密码区分大小写,而不是在使用之前把它转换成大写);另外,所用密码字符都参与密钥的生成。但是,这个会话密钥也仅当用户修改密码时才改变;这使得离线攻击变得相对容易。

LMv2用户会话密钥

        它和LMv2响应一起使用(而不是NTLMv2响应)。获得这个密钥稍有一点复杂:

  1. 首先得到NTLMv2散列(如前所述)。
  2. 得到LMv2客户随机数(和LMv2响应中使用的一样)。
  3. 将类型2消息中的挑战和客户随机数连接在一起。将HMAC-MD5消息认证码算法应用到这个数值,用NTLMv2散列作为密钥输入,得到16字节的输出。
  4. 再次将HMAC-MD5消息认证码算法应用到步骤3的输出,用NTLMv2散列作为密钥输入。这样我们得到16字节的LMv2用户会话散列。

        和NTLMv1用户会话密钥相比,LMv2用户会话密钥有几个好处。首先它是用NTLMv2散列(它本身是NTLM散列的计算结果)经过计算得到的,这个散列包含用户名和域/服务器信息;另外,在密钥计算中用到了挑战和客户端随机数。我们也可以这样简单叙述密钥的计算过程,即对LMv2相应的前16字节进行HMAC-MD5摘要算法,用NTLMv2散列作为输入密钥。

NTLMv2用户会话密钥

        和NTLMv2相应一起使用。密钥计算方法和LMv2用户会话密钥非常相似:

  1. 首先得到NTLMv2散列。
  2. 得到NTLMv2“blob”(和NTLMv2响应中使用的一样)。
  3. 类型2消息中的挑战和这个“blob”连接在一起。对它应用HMAC-MD5消息鉴定码算法,并用NTLMv2散列作为输入密钥,得到16字节的输出。
  4. 对步骤3的输出应用HMAC-MD5消息鉴定码算法,并用NTLMv2散列作为输入密钥。输出就是16字节的NTLMv2用户会话密钥。

        从密钥计算上来讲,NTLMv2用户会话密钥和LMv2用户会话密钥非常类似。我们也可以这样解释密钥的计算过程,即对NTLMv2相应的前16个字节进行HMAC-MD5摘要算法,用NTLMv2散列作为输入密钥。

NTLM2会话相应用户会话密钥

        当NTLMv1身份验证和NTLM2会话安全一起使用时会用到这个变种。这个密钥是从NTLM2会话相应信息中得到的,计算过程如下所述:

  1. 首先获得NTLM用户会话密钥,计算方法如前所述。
  2. 得到会话随机数(如前所述,这是类型2消息中的挑战和NTLM2会话响应的客户随机数的连接串)。
  3. 对这个会话随机数应用HMAC-MD5算法,用NTLM用户会话密钥作为输入密钥。这样我们得到16字节的NTLM2会话响应用户会话密钥。

        NTLM2会话相应用户会话密钥的一个显著特点是,它是在客户端和服务器间计算的,而不是发生在域控制器上。域控制器得到NTLM用户会话密钥并把它提供给服务器;如果NTLM2会话安全被协商使用,服务器将对NTLM用户会话密钥施加HMAC-MD5摘要算法,并用NTLM用户会话密钥作为MAC密钥。

空用户会话密钥

        当使用匿名身份验证时会用到空用户会话密钥。它非常简单,是一个16字节的空串:("0x00000000000000000000000000000000")。

 

Lan Manager会话密钥

        Lan Manager会话密钥是出了用户会话密钥的另外一种选择。当“协商Lan Manager密钥”NTLM标志被设置,也就是说使用NTLM1签名和密封时,它被用来作为加密密钥。计算Lan Manager会话密钥的方法如下所示:

  1. 16字节的LM散列被截断成8字节。
  2. 再把它补上"0xbdbdbdbdbdbd",扩展到14字节。
  3. 这个字串被等分成两个7字节的部分。
  4. 用这两个字串生成两个DES密钥。
  5. 分别用这两个密钥DES加密LM相应的前8个字节,得到两个8字节的密文。
  6. 两个密文被连接在一起形成16字节的Lan Manager会话密钥。

        需要注意的是Lan Manager会话密钥是基于LM响应(而不是LM散列)计算的,这意味着对应不同服务器的挑战,这个会话密钥也会随着变化。这对LM和NTLM用户会话密钥来说是一个提高,它们完全是基于密钥散列计算的;Lan Manager会话密钥对于每一个身份验证过程都有不同的数值,而LM/NTLM用户会话密钥只有在用户修改密码时才会改变。由于这个原因,Lan Manager会话密钥比LM用户会话密钥更安全(拥有相同的密钥强度,但Lan Manager会话密钥可以抵御重放攻击)。NTLM用户会话密钥拥有完全128位密钥空间,但是它和LM用户会话密钥一样在每一个身份验证过程中保持不变。

 

密钥交换

        当“协商密钥交换”标志被协商,客户端和服务器将同意使用辅助密钥来代替会话密钥进行签名和密封。这个过程如下所示:

  1. 客户端选择一个16字节的随机密钥(辅助密钥)。
  2. 会话密钥(可以是用户会话密钥或者Lan Manager会话密钥,取决于“协商Lan Manager密钥”标志是否设置)被用来用RC4加密算法来加密辅助密钥。这样我们得到一个16字节的密文。
  3. 这个密文被放在类型3消息的“会话密钥”字段中并被发往服务器。
  4. 服务器收到类型3消息并对客户端发送的消息进行解密(用RC4算法和用户会话密钥或者Lan Manager会话密钥)。
  5. 这样服务器获得辅助密钥,并用它代替会话密钥进行签名和密封。

        另外,如果启用密钥交换,NTLM2会话安全中的签名过程会有一点变化(在下面的章节我们会进行介绍)。

密钥削弱

        用来签名和密封的密钥受限于加密算法,如果加密算法很弱,那么的到的密钥的安全性同样弱。密钥强度由“协商128位加密”和“协商56位加密”标志。最终的密钥强度会是客户端和服务器都支持的最强的强度。如果两个标志都没有被设置,默认的密钥长度是40比特。NTLM1签名和密封支持40比特和56比特长度的密钥;NTLM2会话安全支持40比特、56比特以及从未被攻破的128比特密钥。

 

NTLM1会话安全

         NTLM1会话安全是NTLMSSP最初的签名和密封方案,当“协商NTLM2密钥”标志没有被协商时使用。如下NTLM标志存在时会启用它:

协商Lan Manager密钥

当这个标志被设定,将被用来进行签名和密封的密钥将从Lan Manager会话密钥中得到(而不是用户会话密钥)。如果协商失败,签名和密封的密钥将通过用户会话密钥得到。

协商 56位密钥

指明其支持56位密钥。如果没有协商这个标志,将使用40位密钥。这个标志只有和“协商Lan Manager密钥”标志一起工作时才有意义。在NTLM1下用户会话密钥没有变弱(因为它本来就已经很弱)。

协商密钥交换

 指明密钥交换会被用来协商辅助密钥进行签名和密封。

 

NTLM1密钥获取

        NTLM1密钥的获取是一个包含3个步骤的操作:

  1. 主密钥协商。
  2. 密钥交换。
  3. 密钥削弱。

主密钥协商

        第一步需要协商一个128位的主密钥,它被用来生成最终的用来签名和密封密钥。它是被“协商Lan Manager密钥”NTLM标志驱动的,Lan Manager会话密钥被用作主密钥。否则,适当的用户会话密钥将被使用。

        一个例子,设想一个用户使用密码"SecREt01"。如果“协商Lan Manager密钥”标志没有设定,而且类型3消息包含的是NTLM相应,那么NTLM用户会话密钥将被选作为主密钥。它是对NTLM散列进行MD4摘要算法的输出(NTLM散列是对Unicode的密码进行MD4算法的输出):

0x3f373ea8e4af954f14faa506f8eebdc4

密钥交换

        如果“协商密钥交换”标志被设置,客户端将把一个新的主密钥放在类型3消息的“会话密钥”字段里,并用之前使用的主密钥对它进行RC4加密。服务器将解密并使用这个新的主密钥。

        例如,假如客户端选择"0xf0f0aabb00112233445566778899aabb"作为新的主随机密钥,它将用之前的主密钥("0x3f373ea8e4af954f14faa506f8eebdc4")以RC4算法对其进行加密,得到:

0x1d3355eb71c82850a9a2d65c2952e6f3

        这个数值被放置在类型3消息中的“会话密钥”字段并被发往服务器。服务器用老的密钥来RC4解密这个字段来获得客户端选择的新的主会话密钥 ("0xf0f0aabb00112233445566778899aabb")。

密钥削弱

        最后,密钥被削弱以遵守其他限制。NTLM1支持40比特和56比特长的密钥。如果“协商56位密钥”标志被设置,那么128比特长的主密钥被削弱到56位;否则,它会被削弱到40位。注意到密钥削弱是NTLM1中特有的,并且要求Lan Manager会话密钥被使用(“协商Lan Manager密钥”标志被设置)。LM和NTLM用户会话密钥是基于密码散列,而不是基于响应;在NTLM1中,一个给定的密码总是对应固定的用户会话密钥。密钥削弱明显不应该在NTLM1中使用,因为用户会话密钥会很容易使用用户密码散列来破解。

        NTLM1中的密钥削弱的过程如下所示:

  • 为了得到56比特的密钥,128比特的主密钥被截短到7字节(56比特),然后在最后补一个字节的“0xa0”。
  • 为了得到40比特的密钥,128比特的主意要被截短到5字节(40比特),然后在最后补上"0xe538b0"。

        以主密钥0x0102030405060708090a0b0c0d0e0f00"为例,40比特的密钥为"0x0102030405e538b0"。如果协商使用56比特密钥,最后的密钥为"0x01020304050607a0"。

 

Signing

        一旦密钥被协商我们可以用它来生成数字签名,以提供消息完整性。为了支持签名功能,需要设置“协商签名”NTLM标志。

        NTLM1加密(由SSPI的MakeSignature函数实现)包含如下步骤:

  1. 用前面协商的密钥生成一个RC4密文。这个步骤仅进行一次(在第一个签名操作之前),而且这个密钥流不会被重置。
  2. 计算消息的CRC32校验码;这是一个32位的little-endian数值。
  3. 得到一个序列号;序列号从0开始计算,每签名一个消息,值加1。它用一个长整型表示。
  4. 4个字节的零和CRC32校验码加上序列号被连接在一起形成一个12字节的数值 ("0x00000000" + CRC32 (message) + sequenceNumber) 。
  5. 这个值被被前面生成的RC4密文加密。
  6. 密文的前4个字节被一个伪随机计数值替换(值为何无关紧要)。
  7. 版本号("0x01000000")和步骤6的输出连接在一起形成数字签名。

        一个例子:比如我们要对消息“jCIFS”(16进制表示的"0x6a43494653")用40比特的密钥进行签名:

  1. 首先计算消息的CRC32校验,用little-endian的16进制表示为"0xa0310bb7"。
  2. 得到序列号。因为这是我们签名的第一个消息,所以序列号为0("0x00000000")。
  3. 4个值为0的字节和CRC32校验码以及序列号被连接在一起得到一个12字节的数值 ("0x00000000a0310bb700000000") 。
  4. 用前面用到的40比特的密钥("0x0102030405e538b0")来RC4加密步骤4中的数值;这样我们得到密文"0xecbf1ced397420fe0e5a0f89"。
  5. 密文的前四个字节被一个计数器值"0x78010900"替换,得到"0x78010900397420fe0e5a0f89"。
  6. 版本信息和步骤6的输出连接在一起形成最后的签名:0x0100000078010900397420fe0e5a0f89

        下一个签名的消息将使用序列号1;另外需要注意的是用第一个消息得到RC4密钥后就不会被重置,所有后续的签名都会使用这个RC4密钥。

 

密封

        除了消息完整性,密封功能可以提供消息私密性保证。“协商密封”标志需要被设置来支持密封特性。在SSPI的NTLM提供程序下,密封总是和签名一起使用(对消息密封的同时会生成一个数字签名);签名和密封使用相同的RC4密钥。

        NTLM1密封(由SSPI的EncryptMessage函数完成)包括如下步骤:

  1. 用前面协商过的密钥得到RC4密文。RC4密文只被计算一次(在密封第一个消息之前),然后这个密文永远不会被重置。
  2. 消息用RC4密文进行加密;这将生成密封的密文。
  3. 用前面讨论过的方法生成消息签名,并把它放置在安全缓冲区内。

4.     As an example, consider the sealingof the message "jCIFS" ("0x6a43494653")using the 40-bit key "0x0102030405e538b0":

        以消息“jCIFS”为例("0x6a43494653"),使用40比特的密钥"0x0102030405e538b0"对其进行密封:

  1. 由40比特的密钥("0x0102030405e538b0")生成RC4密文。
  2. 消息被用RC4密文加密,生成密文"0x86fc55abca",这就是密封过的消息。
  3. 计算得出消息的CRC32校验码(little-endian的16进制数"0xa0310bb7")。
  4. 得到序列号。由于这是第一个被签名的消息,所以序列号为0 ("0x00000000") 。
  5. 4个值为0的字节和CRC32校验码以及序列号被连接生成12字节的数值 ("0x00000000a0310bb700000000") 。
  6. 步骤5中的数值被RC4密钥加密;这让我们得到密文"0x452b490efa3e828bcc8affc3"。
  7. 密文的前4字节被替换成"0x78010900",得到"0x78010900fa3e828bcc8affc3"。
  8. 版本信息和步骤7的输出连接在一起得到最终的签名,它被放置在安全缓冲区内:

0x0100000078010900fa3e828bcc8affc3

整个密封过的数据以16进制表示为:

0x86fc55abca0100000078010900fa3e828bcc8affc3

 

NTLM2会话安全

        NTLM2会话安全是一个新一些的签名和密封方案,当“协商NTLM2密钥”标志被设置时会使用这个方案。如下NTLM标志存在时会启用它:

协商 NTLM2 密钥

指明支持NTLM2会话安全

协商 56位密钥

指明支持56比特密钥。如果这个标志和“协商128位密钥”都没有设置,将使用40位密钥。

协商 128位密钥

指明支持128比特密钥。如果这个标志和“协商128位密钥”都没有设置,将使用40位密钥。

协商密钥交换

指明密钥交换会被用来协商辅助密钥以进行签名和密封。

NTLM2密钥获取

         获取NTLM2密钥包含4个步骤:

  1. 主密钥获取
  2. 密钥交换
  3. 密钥削弱
  4. 子密钥生成

主密钥生成

        用户会话密钥在NTLM2签名和密封中总是被用来作为主基础密钥。当使用NTLMv2身份验证时,LMv2或者是NTLMv2用户会话密钥会被用来作为主密钥。当NTLMv1身份验证和NTLM2会话安全一起使用时,NTLM2会话相应用户会话密钥被用作主密钥。注意NTLM2会话安全中使用的用户会话密钥比起NTLM1中的用户会话密钥或Lan Manager会话密钥来要安全很多,因为它们包含服务器挑战和客户端随机数信息。

密钥交换

        密钥交换的过程和前面所述的NTLM1中的步骤一样。客户端选择一个辅助主密钥,用老的主密钥以RC4算法加密,放置在类型3消息的“会话密钥”字段并发送给服务器。客户端必须设置“协商密钥交换”标志来告诉服务器它要更新会话密钥。

密钥削弱

        在NTLM2会话安全中,密钥削弱只是简单的把主密钥(或者是辅助密钥,如果发生了密钥交换)截短道合适的长度;对于主密钥"0xf0f0aabb00112233445566778899aabb",如果使用40比特的密钥,主密钥会被削弱成"0xf0f0aabb00",如果使用56比特的密钥,主密钥被削弱成"0xf0f0aabb001122"。注意NTLM2支持128比特的密钥;在这种情况下,主密钥被直接用来生成子密钥(并不发生密钥削弱)。

        在NTLM2中,只有在生成密封子密钥时主密钥才会被削弱;当生成签名子密钥时,总是使用完整的128比特的主密钥。

子密钥生成

        在NTLM2下,最多有4中子密钥;主密钥从来不直接参与签名或者是密封消息。子密钥按照如下步骤生成:

1.      128比特(未被削弱的)的主密钥和以零结尾的ASCII字符串常量(session key toclient-to-server signing key magic constant)连接在一起:如果以16进制表示,这个常量为:

0x73657373696f6e206b657920746f2063

6c69656e742d746f2d73657276657220

7369676e696e67206b6579206d616769

6320636f6e7374616e7400

把字符串常量分为多行表示只是为了方便阅读而已。对这个字符串应用MD5消息摘要加密算法,得到一个16字节的数值。这是客户端的签名密钥,被客户端用来签名消息。(这里应该有点问题,应该是对主密钥和ASCII字符串常量的连接串施以MD5算法,作者可能忽略了)

  1. 128比特(未被削弱的)的主密钥和以零结尾的ASCII字符串常量(session key to server-to-client signing key magic constant)连接在一起。如果以16进制表示,这个常量为:

0x73657373696f6e206b657920746f2073

  65727665722d746f2d636c69656e7420

  7369676e696e67206b6579206d616769

  6320636f6e7374616e7400

对这个字符串应用MD5消息摘要加密算法,得到一个16字节的数值。这是服务器的签名密钥,服务器用它来签名消息。

  1. 被削弱的主密钥(取决于是否协商40bit、56比特或是128比特密钥)和以零结尾的ASCII字符串常量(session key to client-to-server sealing key magic constant)连接在一起。如果以16进制表示,这个常量为:

0x73657373696f6e206b657920746f2063

  6c69656e742d746f2d73657276657220

  7365616c696e67206b6579206d616769

  6320636f6e7374616e7400

对这个字符串应用MD5消息摘要加密算法,得到一个16字节的数值。这是客户端的密封密钥,客户端用它来加密消息。

  1. 被削弱的主密钥和以零结尾的ASCII字符串常量(session key to server-to-client sealing key magic constant)连接在一起。如果以16进制表示,这个常量为:

0x73657373696f6e206b657920746f2073

  65727665722d746f2d636c69656e7420

  7365616c696e67206b6579206d616769

  6320636f6e7374616e7400

对这个字符串应用MD5消息摘要加密算法,得到一个16字节的数值。这是服务器的密封密钥,服务器用它来加密消息。

签名

         “协商签名”NTLM标志指明其支持消息签名。客户端用客户签名密钥对消息进行签名;服务器用服务器签名密钥对消息进行签名;签名密钥是从未被削弱的主密钥得来的(如前所述)。

        NTLM2签名(由SSPI函数MakeSignature完成)过程如下所示:

  1. 首先获得一个序列号;序列号从0开始,每签名一个消息,序列号加1;序列号用一个长整型表示(32位的little-endian值)。
  2. 把序列号和需要签名的消息连接在一起;对它应用HMAC-MD5消息鉴别码算法,使用适当的签名密钥。这样我们得到一个16字节的数值。
  3. 如果密钥交换被协商,使用合适的密封密钥生成一个RC4密文。这个过程只发生一次(在第一次操作时),然后密钥不再改变。HMAC输出的前8个字节用RC4密文进行加密。如果密钥交换没有被协商,那么这个过程就被略去。
  4. 一个版本号("0x01000000")和前一个步骤的输出以及序列号被连接在一起形成消息的数字签名。

        用一个例子说明以上过程,假设我们要在客户端上使用主密钥"0x0102030405060708090a0b0c0d0e0f00"对消息“jCIFS”进行签名。主密钥和Client-to-server签名常量连接在一起,对其应用MD5加密算法,得到客户端签名密钥 ("0xf7f97a82ec390f9c903dac4f6aceb132") 和客户端密封密钥 ("0x2785f595293f3e2813439d73a223810d") 。按如下步骤使用它们对消息进行签名:

  1. 首先得到一个序列号。由于这是被签名的第一个消息,所以序列号为0 ("0x00000000") 。
  2. 序列号和需要签名的消息被连接在一起:0x000000006a43494653

对它进行HMAC-MD5算法,使用客户端签名密钥("0xf7f97a82ec390f9c903dac4f6aceb132")。我们得到16字节的"0x0a003602317a759a720dc9c7a2a95257"。

  1. 用密封密钥("0x2785f595293f3e2813439d73a223810d")生成一个RC4密文。对步骤2输出结果的前8个字节用这个密文加密,生成新的密文:"0xe37f97f2544f4d7e"。
  2. 版本号,步骤3的输出和序列号连接在一起形成最终的签名:

0x01000000e37f97f2544f4d7e00000000

密封

        在NTLM2中,“协商密封”NTLM标志指明其是否支持消息私密性。NTLM2密封(由SSPI函数EncryptMessage函数完成)包含以下步骤:

  1. 用合适的密封密钥(取决于是客户端还是服务器在进行消息密封)生成一个RC4密文。这个步骤只发生一次(在第一个消息密封操作之前),之后密钥不再被重置。
  2. 用RC4密文来加密消息,这样我们得到密封的密文。
  3. 消息的数字签名同时被生成,并被放在安全缓冲区内。注意到用于签名的RC密文已经生成(在步骤2中),并且它在签名消息过程中不会被重置。

         用一个例子说明以上步骤,假设我们需要在客户端上密封消息“jCIFS”(16进制表示为"0x6a43494653"),使用主密钥"0x0102030405060708090a0b0c0d0e0f00"。和前面的例子一样,我们使用未被削弱的主密钥生成客户端签名密钥("0xf7f97a82ec390f9c903dac4f6aceb132")。我们还需要生成客户端密封密钥;假设40比特密钥被协商,我们需要把削弱过得主密钥("0x0102030405")与client-to-server密封字符串常量连接起来,并对其应用MD5算法,得到客户端密封密钥("0x6f0d99535033951cbe499cd1914fe9ee")。密封消息包括以下步骤:

  1. 用客户端密封密文生成一个RC4密文:("0x6f0d99535033951cbe499cd1914fe9ee")。
  2. 用RC4密文加密我们的消息,的到消息密文"0xcf0eb0a939"。这就是密封了的消息。
  3. 得到一个序列号。由于这是第一个需要密封的密文,所以序列号为0 ("0x00000000") 。
  4. 序列号和消息被连接在一起:0x000000006a43494653。对这个数值应用HMAC-MD5算法,使用客户端签名密钥 ("0xf7f97a82ec390f9c903dac4f6aceb132") 作为输入。这样我们得到16字节的数值"0x0a003602317a759a720dc9c7a2a95257"。
  5. 用密封密文对步骤4结果的前8个字节进行加密,得到新的密文"0x884b14809e53bfe7"。

6.      版本信息和步骤5中的结果以及序列号连接在一起形成最终的签名,并被放在安全缓冲区内:0x01000000884b14809e53bfe700000000

整个密封的数据结构的16进制表示为:

0xcf0eb0a93901000000884b14809e53bfe700000000

 

 后面的附录和代码例子就不翻译了。大家自己看英文版吧。

原创粉丝点击