在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
前面文章介绍了如何使用Identity在ASP.NET MVC中实现用户的注册、登录以及身份验证。这些功能都是与用户信息安全相关的功能,数据安全的重要性永远放在第一位。那么对于注册和登录功能来说要把密码及用户其它信息通过表单的形式安全的提交到服务器上,那么最适合的方法就是使用HTTPS(如果有条件或者有安全需求,应该所有请求都基于HTTPS,本章不涉及HTTPS的介绍),而在注册时用户的密码应该加密后保存在数据库中,包括登录时对用户名的验证也是对密码明文加密后再进行匹配,对于身份验证来说,服务器生成的用户信息字符串是必须进行加密的,其目的是保护用户信息并且能够让当前的服务器(或集群)能够识别。 本章将从以下几点对Identity中涉及到的加解密进行介绍: 常用的加密方法 软件中常用的加密方法分为两类,一类是密文可解密回明文的,而另一类是密文不可解密的。 .Net对常用加密方法的实现 .Net的System.Security.Cryptography命名空间下包含了用于加解密的类型,这些类型有些是基于托管代码的,有些是基于Windows API的。 Identity用户密码的加解密 用户的密码一般来说是一个长度较短的包含各种字符的字符串,而对用户密码加密的目的是避免用户密码在数据库中明文存储,明文存储密码会导致系统开发或运营人员对用户信息安全的威胁以及黑客攻击数据泄露导致的用户信息安全。所以一般来说加密密码使用无法解密的Hash算法“加密”。
2. 登录调用的代码(注:SignInManager位于Microsoft.AspNet.Identity.Owin程序集中):
但通过查看源码可知,SignInManager实际上也是通过UserManager来匹配密码的:
3. 所以根据上面的分析,用户密码的加密是在UserManager中完成的,而UserManager定义中有一个IPasswordHasher的接口,该接口定义了密码的Hash加密以及Hash后的密码校验:
IPasswordHasher的默认实现是PasswordHasher类型:
从代码中可以看到PassswordHasher又是通过一个名称为Crypto类型的静态方法完成加密和验证的: Hash计算:
Hash验证:
从Crypto的代码中可以得出以下几点结论: Identity用户身份信息的处理过程Identity的用户身份信息相对于密码来说要复杂很多,因为密码仅仅是一个字符串,对一个字符串的加解密很容易,但是Identity的用户身份信息实际上是一个AuthenticationTicket实例:
那么Identity是如何对这个用户身份信息实例进行处理的呢? 1. 首先我们知道的是Identity通过app.UseCookieAuthentication方法在管道中添加了一个类型为CookieAuthenticationMiddleware的中间件,而通过对源码分析可以看到,该中间件中实际上是通过创建一个名为CookieAuthenticationHandler的内部类型,通过这个类型完成了请求时Cookie的获取、验证,验证失败的跳转以及响应时Cookie的写入等功能。 其中Cookie的加解密代码如下: 解密:先获取Cookie值,然后通过TicketDataFormat的Unprotect方法返回一个AuthenticationTicket实例:
加密:将AuthenticationTicket实例通过TicketDataFormat的Protect方法转换为一个加密后的字符串。
2. Identity对用户身份信息的处理主要是通过TicketDataFormat完成,从上面代码中可以看到TicketDataFormat是来来自Options。这里的Options实际上就是app.UseCookieAuthentication方法中的参数CookieAuthenticationOptions:
TicketDataFormat默认值是在构造方法中创建的,它需要一个protector(注:Protector实际上就是加解密的组件,本章后面详解)
3. TicketDataFormat的职责: 由于TicketDataFormat是继承于SecureDataFormat类型,并且仅仅是在构造方法中硬编码了传入基类的参数,所以其功能实际上是基类实现的:
职责一:数据“保护”,先通过序列化器将泛型类型TData进行序列化(这里的TData实际上是AuthenticationTicket类型),然后通过加密组件对序列化后的二进制进行加密,最后通过编码器将二进制数据转换为Base64Url字符串,代码如下图:
这里要注意以下两点: 1). 序列化器是由TicketDataFormat构造方法中硬编码的,其真实类型为TicketSerializer(对于序列化这个概念,实际上就是将一个程序中的内存实例,用二进制数据或者XML、Json等方式保存下来,然后需要使用的时候在通过这些数据把它反序列化为之前的内存实例,这里的TicketSerializer是一个二进制序列化器):
2). 编码器的名称为Base64Url与Base64编码器的区别是,由于Base64字符串中可能会存在斜杠(/)等特殊符号,但是这些符号在url中是无法被正确识别的,所以Base64Url对这些字符进行了特殊处理:
职责二:数据的“解保护”实际上就是保护功能反过来:先将Base64Url字符串解码为二进制数据,然后对二进制数据解密,最后对解密后的数据进行反序列化:
而本章的重点实际上是在数据的加解密上,所以protector才是关注重点,这里的protector从上面的代码中可以看到是通过IAppBulider创建的:
前面的文章分析过,Owin的核心实际上是一个字典,所以通过Owin来获取的东西应该是保存在字典中的:
AppBuilder的初始化代码:
根据上面的分析得出,在没有指定特殊的数据保护器情况下,Identity使用MachineKeyDataProtector作为默认的数据保护器。 补充说明: Identity中的身份验证的原理,实际上是获取到Cookie成功解密并反序列化为AuthenticationTicket实例后,将通过身份验证的Identity(该Identity中的IsAuthenticated属性为true)信息添加到HTTP请求的上下文中的。MVC中需要通过身份验证的访问控制就是通过请求上下文中Identity的IsAuthenticated属性完成判断的。
MachineKey的加解密.Net中有一个名为MachineKey的组件,它用于Forms验证用户信息、asp.net 的View State以及跨进程的会话状态数据的加密和验证,MachineKey可以通过在web.config文件中加入以下的配置文件来对MachineKey的加解密、验证算法及其密钥进行配置,详情可参考文档:https://msdn.microsoft.com/en-us/library/w8h3skw9(v=vs.100).aspx
而上面分析知道Identity使用MachineKeyDataProtector作为数据保护器,而MachineKeyDataProtector实际上使用的就是MachineKey:
注:由于MachineKey相关代码比较复杂,本文中仅对其主要的一些对象以及加解密过程进行介绍: MachineKey的主要相关对象: ● AspNetCryptoServiceProvider(内部类型):ASP.NET用其获取适合的加密组件。
上图为Purpose的定义,从定义中也可以看出针对功能的不同如Forms验证的、角色信息的以及WebForm中一系列组件的目的均不相同。 ● NetFXCryptoService(内部类型):MachineKey在.Net平台下使用的加解密服务组件。也是Identity中使用的身份信息加解密组件。 以下代码为NetFXCryptoService加解密的算法,其算法包括了数据加解密以及数据完整性校验两个部分: 加密: 1 public byte[] Protect(byte[] clearData) //claerData为需要加密的二进制数据 2 { 3 byte[] buffer4; 4 using (SymmetricAlgorithm algorithm = this._cryptoAlgorithmFactory.GetEncryptionAlgorithm()) //通过工厂获取加密算法,实际上就是使用默认的或配置文件指定的如AES等 5 { 6 algorithm.Key = this._encryptionKey.GetKeyMaterial();//Purpose通过配置文件获取加密密钥并根据实际目的派生出来的真实密钥 7 if (this._predictableIV) 8 { 9 algorithm.IV = CryptoUtil.CreatePredictableIV(clearData, algorithm.BlockSize); 10 } 11 else 12 { 13 algorithm.GenerateIV(); 14 } 15 byte[] iV = algorithm.IV; 16 using (MemoryStream stream = new MemoryStream()) 17 { 18 stream.Write(iV, 0, iV.Length); 19 using (ICryptoTransform transform = algorithm.CreateEncryptor()) 20 { 21 using (CryptoStream stream2 = new CryptoStream(stream, transform, CryptoStreamMode.Write)) 22 { 23 stream2.Write(clearData, 0, clearData.Length); 24 stream2.FlushFinalBlock(); 25 using (KeyedHashAlgorithm algorithm2 = this._cryptoAlgorithmFactory.GetValidationAlgorithm())//通过工厂获取数据校验的算法,该算法在配置文件中配置,如SHA1等 26 { 27 algorithm2.Key = this._validationKey.GetKeyMaterial();//Purpose通过配置文件获取的数据校验密钥并根据实际目的派生出来的真实密钥 28 byte[] buffer = algorithm2.ComputeHash(stream.GetBuffer(), 0, (int) stream.Length); 29 stream.Write(buffer, 0, buffer.Length); 30 buffer4 = stream.ToArray(); 31 } 32 } 33 } 34 } 35 } 36 return buffer4; 37 } 解密(加密的反过程): 1 public byte[] Unprotect(byte[] protectedData) 2 { 3 byte[] buffer3; 4 using (SymmetricAlgorithm algorithm = this._cryptoAlgorithmFactory.GetEncryptionAlgorithm()) 5 { 6 algorithm.Key = this._encryptionKey.GetKeyMaterial(); 7 using (KeyedHashAlgorithm algorithm2 = this._cryptoAlgorithmFactory.GetValidationAlgorithm()) 8 { 9 algorithm2.Key = this._validationKey.GetKeyMaterial(); 10 int offset = algorithm.BlockSize / 8; 11 int num2 = algorithm2.HashSize / 8; 12 int count = (protectedData.Length - offset) - num2; 13 if (count <= 0) 14 { 15 return null; 16 } 17 byte[] buffer = algorithm2.ComputeHash(protectedData, 0, offset + count); 18 if (!CryptoUtil.BuffersAreEqual(protectedData, offset + count, num2, buffer, 0, buffer.Length)) 19 { 20 buffer3 = null; 21 } 22 else 23 { 24 byte[] dst = new byte[offset]; 25 Buffer.BlockCopy(protectedData, 0, dst, 0, dst.Length); 26 algorithm.IV = dst; 27 using (MemoryStream stream = new MemoryStream()) 28 { 29 using (ICryptoTransform transform = algorithm.CreateDecryptor()) 30 { 31 using (CryptoStream stream2 = new CryptoStream(stream, transform, CryptoStreamMode.Write)) 32 { 33 stream2.Write(protectedData, offset, count); 34 stream2.FlushFinalBlock(); 35 buffer3 = stream.ToArray(); 36 } 37 } 38 } 39 } 40 } 41 } 42 return buffer3; 43 } 自定义Identity身份信息的验证(基于MachineKey)本例将在Controller的Action方法中获取登录生成的Cookie值,并将其解密后反序列化成AuthenticactionTicket实例: 代码: 1 public ActionResult Index() 2 { 3 //1.从Cookie中获取加密后的用户信息字符串 4 var cookieStr = this.HttpContext.Request.Cookies[".AspNet.ApplicationCookie"].Value.ToString(); 5 //2.将用户信息字符串以Base64Url的方式转换为二进制数据 6 var cookieBytes = TextEncodings.Base64Url.Decode(cookieStr); 7 //3.转换后的二进制数据通过MachineKey进行解密(注:MachinKey默认使用User_MacineKey_Protect为主目的, 8 //特殊目的由Owin Cookie验证中间件提供) 9 var result = MachineKey.Unprotect(cookieBytes, 10 new string[] { "Microsoft.Owin.Security.Cookies.CookieAuthenticationMiddleware", 11 "ApplicationCookie", 12 "v1"}); 13 TicketSerializer ticketSerializer = new TicketSerializer(); 14 //4.将解密后的二进制数据反序列化为AuthenticationTicket实例 15 var ticket = ticketSerializer.Deserialize(result); 16 17 return View(); 18 } 登录后的运行结果:
注:MachineKey可以通过配置文件来改变加解密以及数据验证的算法及密钥,该配置文件可以通过IIS的“计算机密钥”功能来实现:
小结本章在软件开发中常用的加密算法及其在.Net中的应用介绍的基础上,引出了Identity中用户密码以及用户信息的加解密的过程与方法,其中用户密码的加解密较为简单,而用户信息作为一个复杂的对象实例,在加解密之前还需要进行序列化与反序列的流程,另外也得知了对于用户信息的保护不仅仅是加密而且还附带了数据完整性验证功能。数据安全是一个非常重要的话题,而Identity的身份验证是默认ASP.NET MVC带有独立身份验证模板提供的功能,一个一分钟就能创建的应用程序模板就提供了如此复杂的用户数据安全保护功能,由此可见.Net的强大之处。 另外本章除了介绍Identity,实际上也是介绍了一种数据保护以及身份验证的方式,在没有使用Identity的情况下,仍旧可以使用其理念来打造一个符合自身需求的数据保护方案。 参考: https://docs.microsoft.com/en-us/dotnet/standard/security/cryptography-model 本文链接:http://www.cnblogs.com/selimsong/p/7771875.html |
请发表评论