【问题标题】:Salting and Hashing with PBKDF2使用 PBKDF2 进行盐分和散列
【发布时间】:2014-06-03 20:42:19
【问题描述】:

我正在尝试通过散列和加盐将密码保存在数据库中来学习密码学,因此我决定创建一个登录系统来尝试实现该系统。

我的数据库包括

  • 用户ID int PK
  • 用户名 varchar(250)
  • varbinary(64)
  • 密码varbinary(64)
  • RegDate 日期时间
  • 电子邮件 varchar(250)

我正在使用 PBKDF2,但这似乎不是散列/加盐方法,如果不是,那是什么?

如果是这样,我这样做对吗?

我的钥匙

private const int SALT_SIZE = 64;
private const int KEY_SIZE = 64;

将数据插入数据库

public static void RegisterMe(string _username, string _password, string _email)
        {
            using (var cn = new SqlConnection(User.strcon))
            {
                string _sqlins = @"
                    INSERT INTO 
                    [User]
                        ([Username],[Salt],[Password],[RegDate], [Email]) 
                    VALUES 
                        (@Username, @Salt, @Password, CURRENT_TIMESTAMP, @Email)";

                var cmd = new SqlCommand(_sqlins, cn);
                cn.Open();
                using (var deriveBytes = new Rfc2898DeriveBytes(_password, SALT_SIZE))
                {
                    byte[] salt = deriveBytes.Salt;
                    byte[] key = deriveBytes.GetBytes(KEY_SIZE);  

                    // save salt and key to database 
                    cmd.Parameters.AddWithValue("@Username", _username);
                    cmd.Parameters.AddWithValue("@Password", key);
                    cmd.Parameters.AddWithValue("@Salt", salt);
                    cmd.Parameters.AddWithValue("@Email", _email);
                }
                cmd.ExecuteNonQuery();
            }
        }

检查用户是否有效

public bool IsValid(string _email, string _password)
    {

        using (var cn = new SqlConnection(strcon))
        {
            byte[] salt = { }, key = { };
            string _sql = @"
                            SELECT 
                                SALT, 
                                [Password], 
                                UserID 
                            FROM 
                                [User] 
                            WHERE [Email] = @email";

            SqlCommand cmd = new SqlCommand(_sql, cn);
            cmd.Parameters.AddWithValue("@email", _email);

            cn.Open();
            SqlDataReader reader = cmd.ExecuteReader();
            if (reader.Read())
            {
                salt = reader.GetSqlBytes(0).Value;
                key = reader.GetSqlBytes(1).Value;

                reader.Dispose();
                cmd.Dispose();
                using (var deriveBytes = new Rfc2898DeriveBytes(_password, salt))
                {
                    byte[] newKey = deriveBytes.GetBytes(KEY_SIZE);  // derive a 20-byte key
                    return newKey.SequenceEqual(key);
                }
            }
            else
            {
                reader.Dispose();
                cmd.Dispose();
                return false;
            }
        }
    }

我的系统工作正常,它将数据以字节的形式设置到数据库中,如果用户输入了正确的密码,它会返回 true。但这是正确的方法吗?这甚至是散列/加盐吗?

【问题讨论】:

  • 我对使用 PBKDF2 不是很熟悉,但从外观上看,我认为你是正确的,你实际上并没有对密码进行哈希处理。尝试阅读此 MSDN 文档 (msdn.microsoft.com/en-us/library/…),其中显示了一些示例用法,但这是使用对称加密。散列是非对称的,因此一旦散列,您将永远无法取回原始明文。
  • @martin_costello 我认为它正在对密码进行哈希处理。它没有使用对称加密 PBKDF2 基于 HMAC,而不是一种密码。散列不是不对称的,但它确实是一种方式。我非常熟悉 PBKDF2。
  • @owlstead 我的意思是 MSDN 示例是对称的。
  • @martin_costello 是的;但是你想具体说什么?我的意思是,它使用输出作为键,这与存储它不同,但除此之外......
  • PBKDF2 在循环中使用 HMAC 构造作为 PRF(伪随机函数)。它根据输入创建 pseudo 随机字节 - 密钥(读取:UTF-8 编码密码)和密钥派生信息(读取:salt)。 HMAC 是基于散列的消息身份验证代码,因此是用于大多数实际目的的 PRF。对于散列,可以使用任何加密安全散列,但 Rfc2898DeriveBytes 使用 SHA-1。检查密码是否正确,只需重新计算 PBKDF2 值并进行比较即可,无需额外加密。

标签: c# encryption hash cryptography pbkdf2


【解决方案1】:

你基本上是在朝着正确的方向前进,但我会指出一些需要考虑的事情:

  1. PBKDF2 方法的默认迭代次数可能不够,您可能不想将其保留为默认值。我建议指定至少 10K 的迭代计数。

  2. 另一方面,此实现以字节为单位计算密钥大小和盐大小。 64 字节有点太多了。将两者都保持在 16 个字节应该足够了。不建议超过 20 个字节,因为这是底层哈希函数 / HMAC 的最大大小。超越它只会给攻击者带来优势(根据许多人的说法,这是 PBKDF2 中的设计错误)。您当然可以将 varbinary 的大小设置为更高的值,以便将来升级。

  3. 建议您在盐和哈希密码中保留一个协议号。这样做可以让您在以后每次输入时升级方案,当用户可以重置他/她的密码时。

  4. 小点; MSDN 没有指定何时生成盐。我会检查盐的随机性(检查是否每次都不同)并且只在调用getBytes 后询问盐以确保盐确实是随机的,即使实现发生变化。否则,您可以使用加密安全的随机数生成器自行生成。

【讨论】:

  • 非常感谢朋友!这对我帮助很大! 1:通过阅读您的评论和微软的文档,Rfc2898DeriveBytes(String, Int32, Int32) 是迭代的第三个参数吗?所以你会推荐使用10K int?怎么会? 2:知道了,我想,为什么不让它更长一点,这样更难折断。但我可以清楚地看到为什么这也是一个缺点! 3:协议号是什么意思? 4:是的,我也打算实现它,但在所有其他步骤之前没有考虑过它,我的散列/加盐写得正确! :)
  • 是的,第三个 int 是迭代次数。一开始就指定了1K,但这被认为是低的。 10K在实际使用中是最低的,但是查看this question。迭代计数有助于防止攻击者暴力破解密码。它有助于确保不接受弱护照。或者,显示密码/密码强度并让用户决定。
  • 对于协议号,我的意思是一个数字,例如 0,最初是用盐和密码哈希保存的,意味着:我们使用 PBKDF2 和 SHA1,一个 16 字节的盐,64Ki 的迭代和生成哈希时密码短语的 UTF-8 编码。您可能希望稍后升级,并且当您切换到另一种算法时,您无法从存储在数据库中的信息重新计算。
  • 非常感谢您的大力帮助!它对我继续有关如何保护密码的工作和学习曲线帮助很大。
猜你喜欢
  • 2016-11-25
  • 2011-02-23
  • 1970-01-01
  • 2015-10-08
  • 2013-12-13
  • 1970-01-01
  • 2015-08-06
  • 2011-02-27
  • 2011-07-27
相关资源
最近更新 更多