zoukankan      html  css  js  c++  java
  • 第二十五节:数据保护程序和Hash的最佳实现(彩虹表原理)

    一. 数据保护

    1.控制台步骤

      通过Nuget安装数据保护程序集【Microsoft.AspNetCore.DataProtection】和依赖注入程序集【Microsoft.Extensions.DependencyInjection】,详见下面代码,进行数据的protect和unprotect。

    PS:Core MVC程序中,已经内置上述程序集了。

     1             {
     2                 var serviceCollection = new ServiceCollection();
     3                 serviceCollection.AddDataProtection();
     4                 var services = serviceCollection.BuildServiceProvider();
     5                 var provider = services.GetRequiredService<IDataProtectionProvider>();
     6                 var protector = provider.CreateProtector("myProtector");
     7 
     8                 //等价于上面的两句话
     9                 //var provider = services.GetDataProtectionProvider();
    10                 //var protector= services.GetDataProtector("myProtector");
    11                 string input = "ypf001";
    12                 // protect the payload
    13                 string protectedPayload = protector.Protect(input);
    14                 // unprotect the payload
    15                 string unprotectedPayload = protector.Unprotect(protectedPayload);
    16              }

    2. DataProtectionProvider 和 IDataProtector

      对于多个调用方是线程安全的。 它的目的是,在组件通过调用 CreateProtector获取对IDataProtector 的引用时,它会将该引用用于多次调用 Protect 和 Unprotect。

    3. 扩展

    (1). 可以层级创建provider,provider.CreateProtector("purpose1").CreateProtector("purpose2")

    (2). 加密器的生命周期,安装程序集【Microsoft.AspNetCore.DataProtection.Extensions】,利用ToTimeLimitedDataProtector方法。

     1                try
     2                 {
     3                     string input = "this is ITimeLimitedDataProtector";
     4                     var serviceCollection = new ServiceCollection();
     5                     serviceCollection.AddDataProtection();
     6                     var services = serviceCollection.BuildServiceProvider();
     7                     var provider = services.GetRequiredService<IDataProtectionProvider>();
     8                     var protector = provider.CreateProtector("myProtector").ToTimeLimitedDataProtector();
     9                     //配置生命周期为5秒
    10                     string protectedData = protector.Protect(input, TimeSpan.FromSeconds(5));
    11                     string stringDatas = protector.Unprotect(protectedData);
    12                     //等待6s,进行解密,解不了(报异常)
    13                     Task.Delay(TimeSpan.FromSeconds(6)).Wait();
    14                     string stringDatas2 = protector.Unprotect(protectedData);
    15                 }
    16                 catch (Exception ex)
    17                 {
    18                     Console.WriteLine(ex.Message);
    19                 }

    4. 配置数据保护程序的存储位置

      初始化加密器时,系统会基于当前机器的运行环境默认配置,但是有些时候可能需要对这些配置做一些改变,默认支持保存到:文件系统和注册表PersistKeysToFileSystem和PersistKeysToRegistry。

     1   {
     2                 var serviceCollection = new ServiceCollection();
     3                 serviceCollection.AddDataProtection()
     4                     .PersistKeysToFileSystem(new System.IO.DirectoryInfo(@"d:	emp-keys"));          //保存磁盘
     5                //.PersistKeysToRegistry(Registry.CurrentUser.OpenSubKey(@"SOFTWARESamplekeys"));  //保存注册表(路径要写实际路径)
     6                 var services = serviceCollection.BuildServiceProvider();
     7                 var provider = services.GetRequiredService<IDataProtectionProvider>();
     8                 var protector = provider.CreateProtector("myProtector");
     9 
    10                 string input = "ypf001";
    11                 // protect the payload
    12                 string protectedPayload = protector.Protect(input);
    13                 // unprotect the payload
    14                 string unprotectedPayload = protector.Unprotect(protectedPayload);
    15  }

    PS:另外还可以配置 SQLServer、Redis、Azure、证书,可以配置相关的算法,详见:

    https://docs.microsoft.com/zh-cn/aspnet/core/security/data-protection/configuration/overview?view=aspnetcore-3.1

    上面案例都是通过控制台来演示的,Core MVC更容易,在ConfigureService中直接注册,详见官方文档:

     https://docs.microsoft.com/zh-cn/aspnet/core/security/data-protection/configuration/overview?view=aspnetcore-3.1

    二. Hash的最佳实践

    1. Hash最佳实践

      通过Nuget安装程序集【Microsoft.AspNetCore.Cryptography.KeyDerivation】,微软开发的单独包,依赖于 PBKDF2 算法实现,不依赖于数据保护系统。 方法KeyDerivation.Pbkdf2将检测当前操作系统, 并尝试选择最适合的例程实现, 在某些情况下提供更好的性能。 (在 Windows 8 上, 它提供的吞吐量Rfc2898DeriveBytes大约为10倍。)

     1   {
     2                 var password = "ypf001";
     3                 byte[] salt= Encoding.Default.GetBytes("xxx1111");
     4                 Console.WriteLine($"Salt: {Convert.ToBase64String(salt)}");
     5 
     6                 // derive a 256-bit subkey (use HMACSHA1 with 10,000 iterations)
     7                 string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
     8                     password: password,
     9                     salt: salt,
    10                     prf: KeyDerivationPrf.HMACSHA1,
    11                     iterationCount: 10000,
    12                     numBytesRequested: 256 / 8));
    13                 Console.WriteLine($"Hashed: {hashed}");
    14  }

    2. 防止彩虹表情况的攻击

      大致原理, 对于通过一个字符串,利用该算法加密每次生成的值都是不同,但是可以利用VerifyHashedPassword方法对应的算法,比较生成的值和原字符串比对,每次加密生成的值和字符都能比对成功, 所以数据库存的那个加密值只是众多加密值中的一个,从而防止了彩虹表的攻击。

      1    public class PasswordHasher
      2     {
      3         /* =======================
      4          * HASHED PASSWORD FORMATS
      5          * =======================
      6          * PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations.
      7          * Format: { 0x01, prf (UInt32), iter count (UInt32), salt length (UInt32), salt, subkey }
      8          * (All UInt32s are stored big-endian.)
      9          */
     10 
     11         private readonly int _iterCount=1000;
     12 
     13         private readonly RandomNumberGenerator _rng= RandomNumberGenerator.Create();
     14 
     15         // Compares two byte arrays for equality. The method is specifically written so that the loop is not optimized.
     16         [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
     17         private static bool ByteArraysEqual(byte[] a, byte[] b)
     18         {
     19             if (a == null && b == null)
     20             {
     21                 return true;
     22             }
     23             if (a == null || b == null || a.Length != b.Length)
     24             {
     25                 return false;
     26             }
     27             var areSame = true;
     28             for (var i = 0; i < a.Length; i++)
     29             {
     30                 areSame &= (a[i] == b[i]);
     31             }
     32             return areSame;
     33         }
     34 
     35         /// <summary>
     36         /// Returns a hashed representation of the supplied .
     37         /// </summary>
     38         /// <param name="password">The password to hash.</param>
     39         /// <returns>A hashed representation of the supplied for the specified .</returns>
     40         public virtual string HashPassword(string password)
     41         {
     42             if (password == null)
     43             {
     44                 throw new ArgumentNullException(nameof(password));
     45             }
     46 
     47             return Convert.ToBase64String(HashPassword(password, _rng));
     48         }
     49 
     50 
     51         private byte[] HashPassword(string password, RandomNumberGenerator rng)
     52         {
     53             return HashPassword(password, rng,
     54                 prf: KeyDerivationPrf.HMACSHA256,
     55                 iterCount: _iterCount,
     56                 saltSize: 128 / 8,
     57                 numBytesRequested: 256 / 8);
     58         }
     59 
     60         private static byte[] HashPassword(string password, RandomNumberGenerator rng, KeyDerivationPrf prf, int iterCount, int saltSize, int numBytesRequested)
     61         {
     62             // Produce a version 3 (see comment above) text hash.
     63             byte[] salt = new byte[saltSize];
     64             rng.GetBytes(salt);
     65             byte[] subkey = KeyDerivation.Pbkdf2(password, salt, prf, iterCount, numBytesRequested);
     66 
     67             var outputBytes = new byte[13 + salt.Length + subkey.Length];
     68             outputBytes[0] = 0x01; // format marker
     69             WriteNetworkByteOrder(outputBytes, 1, (uint)prf);
     70             WriteNetworkByteOrder(outputBytes, 5, (uint)iterCount);
     71             WriteNetworkByteOrder(outputBytes, 9, (uint)saltSize);
     72             Buffer.BlockCopy(salt, 0, outputBytes, 13, salt.Length);
     73             Buffer.BlockCopy(subkey, 0, outputBytes, 13 + saltSize, subkey.Length);
     74             return outputBytes;
     75         }
     76 
     77         private static uint ReadNetworkByteOrder(byte[] buffer, int offset)
     78         {
     79             return ((uint)(buffer[offset + 0]) << 24)
     80                 | ((uint)(buffer[offset + 1]) << 16)
     81                 | ((uint)(buffer[offset + 2]) << 8)
     82                 | ((uint)(buffer[offset + 3]));
     83         }
     84 
     85         /// <summary>
     86         /// Returns a indicating the result of a password hash comparison.
     87         /// </summary>
     88         /// <param name="hashedPassword">The hash value for a user's stored password.</param>
     89         /// <param name="providedPassword">The password supplied for comparison.</param>
     90         /// <returns>A indicating the result of a password hash comparison.</returns>
     91         /// <remarks>Implementations of this method should be time consistent.</remarks>
     92         public virtual bool VerifyHashedPassword(string hashedPassword, string providedPassword)
     93         {
     94             if (hashedPassword == null)
     95             {
     96                 throw new ArgumentNullException(nameof(hashedPassword));
     97             }
     98             if (providedPassword == null)
     99             {
    100                 throw new ArgumentNullException(nameof(providedPassword));
    101             }
    102 
    103             byte[] decodedHashedPassword = Convert.FromBase64String(hashedPassword);
    104 
    105             // read the format marker from the hashed password
    106             if (decodedHashedPassword.Length == 0)
    107             {
    108                 return false;
    109             }
    110 
    111             return VerifyHashedPassword(decodedHashedPassword, providedPassword, out int embeddedIterCount);
    112         }
    113 
    114         private static bool VerifyHashedPassword(byte[] hashedPassword, string password, out int iterCount)
    115         {
    116             iterCount = default;
    117 
    118             try
    119             {
    120                 // Read header information
    121                 KeyDerivationPrf prf = (KeyDerivationPrf)ReadNetworkByteOrder(hashedPassword, 1);
    122                 iterCount = (int)ReadNetworkByteOrder(hashedPassword, 5);
    123                 int saltLength = (int)ReadNetworkByteOrder(hashedPassword, 9);
    124 
    125                 // Read the salt: must be >= 128 bits
    126                 if (saltLength < 128 / 8)
    127                 {
    128                     return false;
    129                 }
    130                 byte[] salt = new byte[saltLength];
    131                 Buffer.BlockCopy(hashedPassword, 13, salt, 0, salt.Length);
    132 
    133                 // Read the subkey (the rest of the payload): must be >= 128 bits
    134                 int subkeyLength = hashedPassword.Length - 13 - salt.Length;
    135                 if (subkeyLength < 128 / 8)
    136                 {
    137                     return false;
    138                 }
    139                 byte[] expectedSubkey = new byte[subkeyLength];
    140                 Buffer.BlockCopy(hashedPassword, 13 + salt.Length, expectedSubkey, 0, expectedSubkey.Length);
    141 
    142                 // Hash the incoming password and verify it
    143                 byte[] actualSubkey = KeyDerivation.Pbkdf2(password, salt, prf, iterCount, subkeyLength);
    144                 return ByteArraysEqual(actualSubkey, expectedSubkey);
    145             }
    146             catch
    147             {
    148                 // This should never occur except in the case of a malformed payload, where
    149                 // we might go off the end of the array. Regardless, a malformed payload
    150                 // implies verification failed.
    151                 return false;
    152             }
    153         }
    154 
    155         private static void WriteNetworkByteOrder(byte[] buffer, int offset, uint value)
    156         {
    157             buffer[offset + 0] = (byte)(value >> 24);
    158             buffer[offset + 1] = (byte)(value >> 16);
    159             buffer[offset + 2] = (byte)(value >> 8);
    160             buffer[offset + 3] = (byte)(value >> 0);
    161         }
    162     }
    PasswordHasher
     1             {
     2                 string password = "ypf001";
     3                 PasswordHasher passwordHasher = new PasswordHasher();
     4                 //生成加密值
     5                 string hashedPassword = passwordHasher.HashPassword(password);
     6                 //进行比对,ture表示验证通过
     7                 bool result = passwordHasher.VerifyHashedPassword(hashedPassword, password);
     8                 //下面是之前生成的加密值,比对也是ture也能通过
     9                 string has2 = "AQAAAAEAAAPoAAAAEAP2ilbJba6iVmbhGKsNB + s6Jrkxb7GuWhwX / g9t40S77o7yO6wbfM5EKHRM3MRHgQ ==";
    10                 string has3 = "AQAAAAEAAAPoAAAAEMbR85H1WbzrFxQ73+FS5Im0cw+UMrAf6L3LUzhvCPBIL6qF0aPaBUnPeTqykpVw8A==";
    11                 bool result2 = passwordHasher.VerifyHashedPassword(has2, password);
    12                 bool result3 = passwordHasher.VerifyHashedPassword(has3, password);
    13             }

    PS:

    彩虹表的原理:首先定义哈希加密函数H,Q=H(P)表示将明文密码P加密成哈希串Q;然后定义规约函数R,p=R(Q)表示将哈希串Q转换成明文p,注意p不是真正的密码P。将一个可能的密码p0交替带入H和Q两个函数进行运算,先后得到q1,p1,q2,p2,...,q(n-1),p(n-1),qn,pn。其中p是明文,q是哈希串,它们组成的链称为哈希链,n是哈希链的长度,一般大于2000。将哈希链的首尾元素p0和pn做为一个数对存入表中,中间的其它元素全部删除。由多个数对组成的表称为彩虹表。

    彩虹表攻击:找到哈希串Q对应的明文密码P,利用彩虹表进行密码攻击的过程如下:c1=R(Q),将c1与彩虹表中每一个pn进行比对,如果相等,则P=p(n-1),由于彩虹表中只保存了p0和pn,因此需要重新计算该哈希链得到p(n-1);如果没找到相等的pn,计算c2=R(H(c1)),将c2与彩虹表中所有pn进行比对,如果相等,则P=p(n-2),重新计算该哈希链得到p(n-2);如果没找到相等的pn,继续计算c3...以此类推。

    彩虹表概念普及:https://blog.csdn.net/whatday/article/details/88527936

    !

    • 作       者 : Yaopengfei(姚鹏飞)
    • 博客地址 : http://www.cnblogs.com/yaopengfei/
    • 声     明1 : 本人才疏学浅,用郭德纲的话说“我是一个小学生”,如有错误,欢迎讨论,请勿谩骂^_^。
    • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
     
  • 相关阅读:
    uva514Rails(栈)
    hdu1160FatMouse's Speed
    如何截取整个屏幕
    equals 与==
    (转载)equals与==
    (转载)关于ArrayList的5道面试题
    java的一些程序
    (转载)OSI七层参考模型和TCP/IP四层参考模型
    (转载)算法面试题
    (转载)火车运煤
  • 原文地址:https://www.cnblogs.com/yaopengfei/p/12121755.html
Copyright © 2011-2022 走看看