【问题标题】:Security risks from trimming a salt修剪盐的安全风险
【发布时间】:2012-08-17 15:09:30
【问题描述】:

所以我最近为密码相关方法创建了一个静态类,并且必须创建一个生成安全盐的方法。

最初我实现了RNGCryptoServiceProvider并将n个字节归档到一个数组中,我将其转换为base64并返回。

问题在于输出长度在转换后当然比 n (which makes sense) 长。

为了解决这个问题,我将函数更改为下面的方法,我只是想知道通过修剪 base64 字符串是否会引发任何安全风险?

/// <summary>
/// Generates a salt for use with the Hash method.
/// </summary>
/// <param name="length">The length of string to generate.</param>
/// <returns>A cryptographically secure random salt.</returns>
public static string GenerateSalt(int length)
{
    // Check the length isn't too short.
    if (length < MIN_LENGTH)
    {
        throw new ArgumentOutOfRangeException("length", "Please increase the salt length to meet the minimum acceptable value of " + MIN_LENGTH + " characters.");
    }

    // Calculate the number of bytes required.
    // https://en.wikipedia.org/wiki/Base64#Padding
    // http://stackoverflow.com/questions/17944/how-to-round-up-the-result-of-integer-division
    int bytelen = ((3 * length) + 4 - 1) / 4;

    // Create our empty salt array.
    byte[] bytes = new byte[bytelen];

    // Where we'll put our generated salt.
    string salt;

    // Generate a random secure salt.
    using (RNGCryptoServiceProvider randcrypto = new RNGCryptoServiceProvider())
    {
        // Fill our array with random bytes.
        randcrypto.GetBytes(bytes);

        // Get a base64 string from the random byte array.
        salt = GetBase64(bytes);
    }

    // Trim the end off only if we need to.
    if (salt.Length > length)
    {
        // Substring is the fastest method to use.
        salt = salt.Substring(0, length);
    }

    // Return the salt.
    return salt;
}

另外作为一个附带问题,我快速浏览了一下,实际上找不到RNGCryptoServiceProviderC# 实现的哈希函数实际上是什么。有人知道吗?

【问题讨论】:

  • 为什么要将盐作为字符串传递?为什么这个 string 的长度是关键工厂?
  • @Damien_The_Unbeliever 对于当盐存储在数据库中时,知道你会得到什么样的输出是更实际的。
  • 几乎我听说过的每个数据库都有一些机制可以让你存储二进制数据。
  • 作为旁注,我认为您的 Calculate the number of bytes required 行不正确。作为指导,0 => 0、2 => 1 和 3 => 2(然后 4 => 3、6 => 4 等),但 1 应该没有意义,因为没有输入可以生成长度为 1 的 Base64 字符串(模式 4)。

标签: c# hash cryptography base64 salt


【解决方案1】:

为什么盐的长度对您来说如此重要?我不认为有任何真正的安全隐患,因为盐的唯一真正要求是它是随机且不可猜测的。

换句话说,去吧。

编辑:这是使用 Linq 的另一种方法。

Random random = new Random();
int length = 25; // Whatever length you want
char[] keys = "ABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890!£$%^&*()".ToCharArray(); // whatever chars you want
var salt = Enumerable
    .Range(1, length) // equivalent to the loop bit, for(i.. ) 
    .Select(k => keys[random.Next(0, keys.Length - 1)])  // generate a new random char 
    .Aggregate("", (e, c) => e + c); // join them together into a string

【讨论】:

  • 主要原因是如果它与现有数据库一起使用,该数据库对 salt 有最大大小要求(例如 16 char 或较小的东西,您不妨每次都将其最大化)。跨度>
  • 很公平。您可以只生成一个 GUID 并获取它的前 16 个字符!
  • 但是你被限制在 32 个字符,还不如做一些我可以使用的通用方法。
  • 我不同意你关于盐的长度不重要的说法。盐的目的是防止预计算攻击。在您拥有 1 位盐的极端情况下,盐会迫使攻击者创建 2 个预先计算的数据集,而不是 1 个(空间的两倍)。类似地,一个 1 字节的盐只需要 256 个预先计算的集合,依此类推。由于彩虹表可以用存储空间换取计算时间,因此实际最小值可能比您想象的要大。
  • @Luke:我觉得你有点迂腐。我说盐的长度不应该关心你,盐的要求是它是“随机的和不可猜测的”。显然,最多可以在两次尝试中猜到 1 位盐。我是为人类而不是计算机编写指令,因此应该以常识为准!
【解决方案2】:

这种生成盐的方式没有安全风险。

盐根本不需要那种级别的安全性,它就在那里,因此彩虹表不能用于破解哈希/加密。常规的 Random 类足以创建盐。

例子:

/// <summary>
/// Generates a salt for use with the Hash method.
/// </summary>
/// <param name="length">The length of string to generate.</param>
/// <returns>A random salt.</returns>
public static string GenerateSalt(int length) {
    // Check the length isn't too short.
    if (length < MIN_LENGTH) {
        throw new ArgumentOutOfRangeException("length", "Please increase the salt length to meet the minimum acceptable value of " + MIN_LENGTH + " characters.");
    }

    // Where we'll put our generated salt.
    StringBuilder salt = new StringBuilder(length);

    // Fill our string with random characters.
    Random rnd = new Random();
    string chars = "0123456798ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    for (int i = 0; i < length; i++) {
      salt.Append(chars[rnd.Next(chars.Length)]);
    }

    // Return the salt.
    return salt.ToString();
}

注意:如果函数将在时间关闭时多次使用,您将使用单个 Random 对象并传递给函数,因为创建的时间太近的 Random 实例将给出相同的随机序列。

【讨论】:

  • 由于使用Random 的问题,这就是我使用RNGCryptoServiceProvider 的原因。如果我有选择的话最好:P
  • @Sam:看到您在代码中关于Substring 是截断字符串的最快方法的评论,考虑到加密随机生成器比@987654329 慢很多 @类。
  • 公平点,但我的想法不是为了稍微提高速度而冒着安全风险。在我看来,在static method 中当然不值得使用Random
【解决方案3】:

只是为了好玩,这里有一种更快的方法(即使代码看起来不太好)。尝试剪切并粘贴它以查看。在我的机器上,它的执行时间约为 1.6 秒,而 7.1 秒。由于我在每种情况下都进行了一百万次迭代,所以我认为执行时间并不那么重要!

string msg = "";
int desiredLength = 93; // Length of salt required
Stopwatch watch = new Stopwatch();
watch.Start();
for (int k=0; k<1000000; k++)
{
    double guidsNeeded = Math.Ceiling(desiredLength / 36.0);
    string salt = "";
    for (int i=0; i<guidsNeeded; i++)
    {
       salt += Guid.NewGuid().ToString();
    }
    salt = salt.Substring(0,desiredLength);
}
msg += watch.ElapsedMilliseconds.ToString(); // 1654 ms

watch.Start();
for (int j=0; j<1000000; j++)
{
    GenerateSalt(93);
}
msg += "\r\n" + watch.ElapsedMilliseconds.ToString(); // 7096 ms

这是使用 Guffa 的代码 GenerateSalt

【讨论】:

  • 知道修剪生成的盐是安全的有点有趣 :) 虽然我从不喜欢 guid 的相似程度。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-06-25
  • 1970-01-01
  • 2011-09-14
  • 2010-11-24
  • 2013-07-07
  • 1970-01-01
  • 2017-08-07
相关资源
最近更新 更多