【问题标题】:Rfc2898 / PBKDF2 with SHA256 as digest in c#Rfc2898 / PBKDF2 与 SHA256 作为 C# 中的摘要
【发布时间】:2013-09-09 23:33:56
【问题描述】:

我想在 c# 中使用 Rfc2898 来派生密钥。我还需要使用 SHA256 作为 Rfc2898 的摘要。我找到了 Rfc2898DeriveBytes 类,但它使用 SHA-1,我看不到让它使用不同摘要的方法。

有没有办法在 c# 中使用 Rfc2898 和 SHA256 作为摘要(没有从头开始实现它)?

【问题讨论】:

  • 没有安全需要使用不同的摘要,SHA-1 在此使用中没有“损坏”。
  • @zaph 可笑!他想使用 SHA-2。 SHA1 可能不会为你而坏,但它为 Carsten 坏。
  • @DouglasHeld 荒谬!使用SHA1的Rfc2898或PBKDF2不坏,不存在碰撞漏洞。 SHA1 因签名而损坏。见Is PBKDF2-HMAC-SHA1 really broken?Is PBKDF2 (RFC 2898) broken because SHA1 is broken?
  • 有人对提高安全性有什么看法吗?
  • 解开这个谜团 ;-) :我需要使用 SHA256 的原因是我的应用程序需要解密数据,而这些数据恰好是由 3rd 方软件使用 PBKDF2 和 SHA256 加密的。由于我无法更改 3rd 方软件加密数据的方式,我真的没有任何选择要使用什么摘要。

标签: c# cryptography


【解决方案1】:

.NET Core 有 Rfc2898DeriveBytes 的新实现。

CoreFX version no longer has the the hashing algorithm hard-coded

The code is available on Github。它于 2017 年 3 月合并到 master,并随 .NET Core 2.0 一起提供。

【讨论】:

  • 这是目前最好的答案。如果 Microsoft 已经发布了更新的库,那么原则上这将比任何其他实现都更好,这仅仅是因为它得到了 Microsoft QA 的认可。 Matthew 的回答和 Peter O. 的回答可能是很棒的代码,但据我所知,后续的 cmets 已经在实现中发现了潜在的错误。这支持一般原则,不要自己动手......
【解决方案2】:

对于需要它的人,.NET Framework 4.7.2 包含一个overload of Rfc2898DeriveBytes,允许指定散列算法:

byte[] bytes;
using (var deriveBytes = new Rfc2898DeriveBytes(password, salt, iterations, HashAlgorithmName.SHA256))
{
    bytes = deriveBytes.GetBytes(PBKDF2SubkeyLength);
}

目前的HashAlgorithmName 选项是:

  • MD5
  • SHA1
  • SHA256
  • SHA384
  • SHA512

【讨论】:

  • .NET Core 2.1 也支持这个。
【解决方案3】:

请参阅布鲁诺·加西亚的回答。

Carsten:请接受那个答案而不是这个答案。


在我开始回答这个问题时,Rfc2898DeriveBytes 无法配置为使用不同的哈希函数。但与此同时,它也得到了改进。见布鲁诺加西亚的回答。以下函数可用于生成用户提供的密码的散列版本,以存储在数据库中用于身份验证。

对于旧 .NET 框架的用户,这仍然很有用:

// NOTE: The iteration count should
// be as high as possible without causing
// unreasonable delay.  Note also that the password
// and salt are byte arrays, not strings.  After use,
// the password and salt should be cleared (with Array.Clear)

public static byte[] PBKDF2Sha256GetBytes(int dklen, byte[] password, byte[] salt, int iterationCount){
    using(var hmac=new System.Security.Cryptography.HMACSHA256(password)){
        int hashLength=hmac.HashSize/8;
        if((hmac.HashSize&7)!=0)
            hashLength++;
        int keyLength=dklen/hashLength;
        if((long)dklen>(0xFFFFFFFFL*hashLength) || dklen<0)
            throw new ArgumentOutOfRangeException("dklen");
        if(dklen%hashLength!=0)
            keyLength++;
        byte[] extendedkey=new byte[salt.Length+4];
        Buffer.BlockCopy(salt,0,extendedkey,0,salt.Length);
        using(var ms=new System.IO.MemoryStream()){
            for(int i=0;i<keyLength;i++){
                extendedkey[salt.Length]=(byte)(((i+1)>>24)&0xFF);
                extendedkey[salt.Length+1]=(byte)(((i+1)>>16)&0xFF);
                extendedkey[salt.Length+2]=(byte)(((i+1)>>8)&0xFF);
                extendedkey[salt.Length+3]=(byte)(((i+1))&0xFF);
                byte[] u=hmac.ComputeHash(extendedkey);
                Array.Clear(extendedkey,salt.Length,4);
                byte[] f=u;
                for(int j=1;j<iterationCount;j++){
                    u=hmac.ComputeHash(u);
                    for(int k=0;k<f.Length;k++){
                        f[k]^=u[k];
                    }
                }
                ms.Write(f,0,f.Length);
                Array.Clear(u,0,u.Length);
                Array.Clear(f,0,f.Length);
            }
            byte[] dk=new byte[dklen];
            ms.Position=0;
            ms.Read(dk,0,dklen);
            ms.Position=0;
            for(long i=0;i<ms.Length;i++){
                ms.WriteByte(0);
            }
            Array.Clear(extendedkey,0,extendedkey.Length);
            return dk;
        }
    }

【讨论】:

  • 不错。一旦不再需要这些值,您可能希望将算法的内部状态(包括流)归零。否则你的密钥材料可能会暴露。
  • @owlstead:大部分已经完成了;调用 Dispose(或退出“使用”范围)会将密码和盐归零。这里还有什么需要做的吗?
  • 您已经清除了输入,但没有清除流(如前所述),也没有清除循环中声明的其他内部变量。当然,这些将被释放或从堆栈中删除,但这并不意味着该值已从内存中删除。班级设计有点奇怪。无需将密码和盐放在字段中 - 存储它们以备后用几乎没有用处。目前您只能更改迭代次数并重新生成密钥(可能具有不同的长度)。
  • 该类中使用的接口仅仅是模仿Rfc2898DeriveBytes中使用的接口。但是,您的担忧是有道理的;一方面,Rfc2898DeriveBytes 将密码存储为字符串,该字符串是不可变的,完成后无法清除。更好的选择是 char 数组,如 Java 的 PBEKeySpec。
  • 兼容性无疑是保持某种设计的一个很好的理由。请注意,使用字符串和字符的缺点是 PBKDF2 没有默认字符编码;提到了 UTF-8,但算法本身适用于字节。 Oracle 指定使用低 8 位 (yuk),Microsoft 静默 使用 UTF-8,至少在测试中如此。像你一样使用字节可能是最好的方法;它使用户考虑编码,并且可以清除字节(由用户在需要时)。 真的很不错的更新!!
【解决方案4】:

BCL Rfc2898DeriveBytes 被硬编码为使用 sha-1。

KeyDerivation.Pbkdf2 允许完全相同的输出,但它也允许 HMAC SHA-256 和 HMAC SHA-512。它也更快;在我的机器上大约 5 倍 - 这对安全性有好处,因为它允许更多轮次,这使得饼干的生活更加困难(顺便说一下,sha-512 对 gpu 的友好性远低于 sha-256 或 sha1)。而且 api 更简单,启动:

byte[] salt = ...
string password = ...
var rounds = 50000;                       // pick something bearable
var num_bytes_requested = 16;             // 128 bits is fine
var prf = KeyDerivationPrf.HMACSHA512;    // or sha256, or sha1
byte[] hashed = KeyDerivation.Pbkdf2(password, salt, prf, rounds, num_bytes_requested);

它来自 nuget 包 Microsoft.AspNetCore.Cryptography.KeyDerivation,它 依赖于 asp.net 核心;它在 .net 4.5.1 或 .net standard 1.3 或更高版本上运行。

【讨论】:

  • 从 v.2.0.0 开始需要 NS2。这意味着 .NET 4.6.1 并且您必须使用 VS2017,因为 NuGet 不会在 VS2015 下安装它。
  • 您不需要使用 v2.0.0 - PBKDF2 并没有发生任何变化。
【解决方案5】:

您可以使用充气城堡。 C#规范列出了算法“PBEwithHmacSHA-256”,它只能是带有SHA-256的PBKDF2。

【讨论】:

    【解决方案6】:

    我知道这是一个老问题,但是对于遇到它的任何人,您现在都可以使用 Microsoft.AspNetCore.Cryptography.KeyDerivation nuget 包中的 KeyDerivation.Pbkdf2。这是asp.net核心中使用的。

    不幸的是,它会添加大量并不真正需要的引用。您可以复制代码并将其粘贴到您自己的项目中(尽管您现在必须维护作为 PITA 的加密代码)

    【讨论】:

      【解决方案7】:

      对于它的价值,这里是微软实现的副本,但将 SHA-1 替换为 SHA512:

      namespace System.Security.Cryptography
      {
      using System.Globalization;
      using System.IO;
      using System.Text;
      
      [System.Runtime.InteropServices.ComVisible(true)]
      public class Rfc2898DeriveBytes_HMACSHA512 : DeriveBytes
      {
          private byte[] m_buffer;
          private byte[] m_salt;
          private HMACSHA512 m_HMACSHA512;  // The pseudo-random generator function used in PBKDF2
      
          private uint m_iterations;
          private uint m_block;
          private int m_startIndex;
          private int m_endIndex;
          private static RNGCryptoServiceProvider _rng;
          private static RNGCryptoServiceProvider StaticRandomNumberGenerator
          {
              get
              {
                  if (_rng == null)
                  {
                      _rng = new RNGCryptoServiceProvider();
                  }
                  return _rng;
              }
          }
      
          private const int BlockSize = 20;
      
          //
          // public constructors 
          // 
      
          public Rfc2898DeriveBytes_HMACSHA512(string password, int saltSize) : this(password, saltSize, 1000) { }
      
          public Rfc2898DeriveBytes_HMACSHA512(string password, int saltSize, int iterations)
          {
              if (saltSize < 0)
                  throw new ArgumentOutOfRangeException("saltSize", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
      
              byte[] salt = new byte[saltSize];
              StaticRandomNumberGenerator.GetBytes(salt);
      
              Salt = salt;
              IterationCount = iterations;
              m_HMACSHA512 = new HMACSHA512(new UTF8Encoding(false).GetBytes(password));
              Initialize();
          }
      
          public Rfc2898DeriveBytes_HMACSHA512(string password, byte[] salt) : this(password, salt, 1000) { }
      
          public Rfc2898DeriveBytes_HMACSHA512(string password, byte[] salt, int iterations) : this(new UTF8Encoding(false).GetBytes(password), salt, iterations) { }
      
          public Rfc2898DeriveBytes_HMACSHA512(byte[] password, byte[] salt, int iterations)
          {
              Salt = salt;
              IterationCount = iterations;
              m_HMACSHA512 = new HMACSHA512(password);
              Initialize();
          }
      
          //
          // public properties 
          //
      
          public int IterationCount
          {
              get { return (int)m_iterations; }
              set
              {
                  if (value <= 0)
                      throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
                  m_iterations = (uint)value;
                  Initialize();
              }
          }
      
          public byte[] Salt
          {
              get { return (byte[])m_salt.Clone(); }
              set
              {
                  if (value == null)
                      throw new ArgumentNullException("value");
                  if (value.Length < 8)
                      throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, Environment.GetResourceString("Cryptography_PasswordDerivedBytes_FewBytesSalt")));
                  m_salt = (byte[])value.Clone();
                  Initialize();
              }
          }
      
          // 
          // public methods
          // 
      
          public override byte[] GetBytes(int cb)
          {
              if (cb <= 0)
                  throw new ArgumentOutOfRangeException("cb", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
              byte[] password = new byte[cb];
      
              int offset = 0;
              int size = m_endIndex - m_startIndex;
              if (size > 0)
              {
                  if (cb >= size)
                  {
                      Buffer.InternalBlockCopy(m_buffer, m_startIndex, password, 0, size);
                      m_startIndex = m_endIndex = 0;
                      offset += size;
                  }
                  else
                  {
                      Buffer.InternalBlockCopy(m_buffer, m_startIndex, password, 0, cb);
                      m_startIndex += cb;
                      return password;
                  }
              }
      
              //BCLDebug.Assert(m_startIndex == 0 && m_endIndex == 0, "Invalid start or end index in the internal buffer.");
      
              while (offset < cb)
              {
                  byte[] T_block = Func();
                  int remainder = cb - offset;
                  if (remainder > BlockSize)
                  {
                      Buffer.InternalBlockCopy(T_block, 0, password, offset, BlockSize);
                      offset += BlockSize;
                  }
                  else
                  {
                      Buffer.InternalBlockCopy(T_block, 0, password, offset, remainder);
                      offset += remainder;
                      Buffer.InternalBlockCopy(T_block, remainder, m_buffer, m_startIndex, BlockSize - remainder);
                      m_endIndex += (BlockSize - remainder);
                      return password;
                  }
              }
              return password;
          }
      
          public override void Reset()
          {
              Initialize();
          }
      
          private void Initialize()
          {
              if (m_buffer != null)
                  Array.Clear(m_buffer, 0, m_buffer.Length);
              m_buffer = new byte[BlockSize];
              m_block = 1;
              m_startIndex = m_endIndex = 0;
          }
          internal static byte[] Int(uint i)
          {
              byte[] b = BitConverter.GetBytes(i);
              byte[] littleEndianBytes = { b[3], b[2], b[1], b[0] };
              return BitConverter.IsLittleEndian ? littleEndianBytes : b;
          }
          // This function is defined as follow : 
          // Func (S, i) = HMAC(S || i) | HMAC2(S || i) | ... | HMAC(iterations) (S || i)
          // where i is the block number. 
          private byte[] Func()
          {
              byte[] INT_block = Int(m_block);
      
              m_HMACSHA512.TransformBlock(m_salt, 0, m_salt.Length, m_salt, 0);
              m_HMACSHA512.TransformFinalBlock(INT_block, 0, INT_block.Length);
              byte[] temp = m_HMACSHA512.Hash;
              m_HMACSHA512.Initialize();
      
              byte[] ret = temp;
              for (int i = 2; i <= m_iterations; i++)
              {
                  temp = m_HMACSHA512.ComputeHash(temp);
                  for (int j = 0; j < BlockSize; j++)
                  {
                      ret[j] ^= temp[j];
                  }
              }
      
              // increment the block count.
              m_block++;
              return ret;
          }
      }
      }
      

      除了将HMACSHA1替换为HMACSHA512外,还需要添加StaticRandomNumberGenerator属性,因为在microsoft程序集中Utils.StaticRandomNumberGeneratorinternal,并且需要添加static byte[] Int(uint i)方法,因为microsoft的@ 987654328@ 也是internal。除此之外,代码有效。

      【讨论】:

      • -1:我最初使用它,虽然我不知道为什么,但它不会产生有效的派生密钥。我通过编写并与我自己的实现进行比较发现了这一点,但这可以通过针对 BouncyCastle 测试此类来确认,甚至可以通过将 HMACSHA256 替换为 HMACSHA512 来确认接受的答案
      【解决方案8】:

      虽然这是一个老问题,但由于我在我的问题 Configurable Rfc2898DeriveBytes 中添加了对这个问题的引用,我在其中询问了 Rfc2898DeriveBytes 算法的通用实现是否正确。

      我现在已经测试并验证,如果为TAlgorithm 提供HMACSHA1 作为Rfc2898DeriveBytes 的.NET 实现,它会生成完全相同的哈希值

      为了使用该类,必须为需要字节数组作为第一个参数的 HMAC 算法提供构造函数。

      例如:

      var rfcGenSha1 = new Rfc2898DeriveBytes<HMACSHA1>(b => new HMACSHA1(b), key, ...)
      var rfcGenSha256 = new Rfc2898DeriveBytes<HMACSHA256>(b => new HMACSHA256(b), key, ...)
      

      这需要算法在这一点上继承HMAC,我相信只要算法的构造函数接受一个数组,我相信可以减少要求从KeyedHashAlgorithm而不是HMAC继承的限制构造函数的字节数。

      【讨论】:

        猜你喜欢
        • 2014-11-27
        • 2012-04-17
        • 2011-05-29
        • 1970-01-01
        • 2022-12-12
        • 2020-12-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多