一、实体鉴别#
实体鉴别(经常简称为鉴别)就是一方验证另一方身份的技术。一个实体可以是人、客户/服务器进程等。这里仅讨论如何鉴别通信对端 实体的身份,即验证正在通信的对方确实是所认为的通信实体,而不是其他的假冒者。进行通信实体鉴别需要使用鉴别协议。鉴别协议通常在两个通信实体之间传输实际数据或者进行访问控制之前运行,是很多安全协议的重要组成部分或前奏。一种最简单的实体鉴别方法就是,利用用户名/口令。
但直接在网络中传输用户名/口令是不安全的,因为攻击者可以在网络上截获该用户名/口令,因此在实体鉴别过 程中需要使用加密技术。其基本流程如下:
但不幸的是,这种简单的鉴别方法具有明显的漏洞。因为攻击者C从网络上截获该报文后,完全不用破译该报文而仅仅直接将该报文发送 给B,就可以使B误认为C就是A。这就是重放攻击(replayattack)。
为了对付重放攻击,可以使用不重数(nonce),即一个不重复使用的大随机数,也称为“一次一数”。下图为不重数鉴别过程:
A首先用明文发送其身份和一个不重数RA给B。接着,B在响应A的报文中用共享的密钥KAB加密RA,并同时也给出了自己的不重数RB。 最后,A再用加密的RB响应B。A和B分别通过验证对方返回的加密的不重数实现双向的身份鉴别。
由于不重数不能重复使用,攻击者C无法利用重放攻击来冒用A或B的身份。这种使用不重数进行实体鉴别的协议又称为挑战-响应(Challenge-Response)协议。
同样,使用公钥加密算法也能实现实体鉴别。这时,通信双方可以利用自己的私钥对不重数进行签名,而用对方的公钥来鉴别对方签名的不重数,从而实现通信双方身份的鉴别。
二、大随机数的生成#
在 .NET 中,我们可以使用通过生成的 GUID 作为不重数。
GUID,全局唯一标识符(Globally Unique Identifier)是一种由算法生成的二进制长度为128位的数字标识符。GUID主要用于在拥有多个节点、多台计算机的网络或系统中。在理想情况下,任何计算机和计算机集群都不会生成两个相同的GUID。GUID 的总数达到了2128(3.4×1038)个,所以随机生成两个相同GUID的可能性非常小,但并不为0。所以,用于生成GUID的算法通常都加入了非随机的参数(如时间),以保证这种重复的情况不会发生。
下面我们采用 “GUID + 时间戳”生成一个真正的随机数:
1 static void Main(string[] args)2 {3 // 生成新的 GUID4 var guid = Guid.NewGuid();5 6 // 格式1:5881b633c1264df5a92294052e7e33487 string format1 = guid.ToString("N");8 9 // 格式2:5881b633-c126-4df5-a922-94052e7e3348
10 string format2 = guid.ToString("D");
11
12 // 格式3:{5881b633-c126-4df5-a922-94052e7e3348}
13 string format3 = guid.ToString("B");
14
15 // 格式4:(5881b633-c126-4df5-a922-94052e7e3348)
16 string format4 = guid.ToString("P");
17
18 // 将字符串转换成 GUID
19 Guid g = Guid.Parse(format1);
20
21 // 输出
22 Console.WriteLine($"格式1:{format1}\r\n格式2:{format2}\r\n格式3:{format3}\r\n格式4:{format4}");
23
24 // 生成一个真正的唯一随机数(GUID+时间戳):406b4d0390a9498384dba0ab801bb3e31611813012065
25 string random = $"{Guid.NewGuid().ToString("N")}{new DateTimeOffset(DateTime.UtcNow).ToUnixTimeMilliseconds()}";
26 Console.WriteLine($"大随机数:{random}");
27
28 Console.ReadKey();
29 }