使用PBKDF2进行盐值哈希处理

3
我正在尝试使用哈希和盐值来保存数据库中的密码,学习密码学知识,所以我决定制作一个登录系统来实现这个系统。
我的数据库包括:
- UserID int PK - Username varchar(250) - Salt varbinary(64) - Password varbinary(64) - RegDate datetime - Email 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文档(http://msdn.microsoft.com/en-us/library/system.security.cryptography.rfc2898derivebytes.aspx),其中显示了一些示例用法,但这是使用对称加密的。散列是非对称的,因此一旦散列,您永远无法获取原始明文。 - Martin Costello
@martin_costello 是的,它是这样的;但你具体想要表达什么?我的意思是,它使用输出作为键,这与存储不同,但除此之外... - Maarten Bodewes
@owlstead 我是在说“这里有一些关于使用PBKDF2的例子,但它并不是你所做的完整示例,因为它是对称的而不是基于哈希的”。 - Martin Costello
@martin_costello PBKDF2执行哈希操作,示例中的加密不会减弱这一点。依我之见,你最初的评论并没有比原先更清楚;在大部分内容上是不正确的。 - Maarten Bodewes
1
PBKDF2在循环中使用HMAC构造作为PRF(伪随机函数)。它基于输入 - 密钥(即UTF-8编码的密码)和密钥派生信息(即盐)创建随机字节。HMAC是基于哈希的消息认证代码,因此对于大多数实际目的而言,它是PRF。对于哈希,可以使用任何具有加密安全性的哈希,但Rfc2898DeriveBytes使用SHA-1。要检查密码是否正确,只需重新计算PBKDF2值并进行比较,无需进行其他加密。 - Maarten Bodewes
显示剩余3条评论
1个回答

8

您的方向基本正确,但我会指出一些需要考虑的事情:

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

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

  3. 建议您使用盐和散列密码保持协议编号。这样做可以使您在以后的日期和每个条目中升级方案,当用户可以重置他/她的密码时。

  4. 小问题;MSDN没有指定生成盐的时间。我会检查盐的随机性(检查它是否每次都不同),并在调用getBytes之后才要求盐,以确保盐确实是随机的,即使实现发生变化。否则,请使用密码学安全的随机数生成器自行生成。


非常感谢,伙计!它对我帮助很大!1:通过阅读您的评论和微软的文档,Rfc2898DeriveBytes(String,Int32,Int32)中的第三个参数是迭代次数吗?所以您建议使用10K int?为什么?2:明白了,我想,那为什么不让它更长,因此更难破解呢。但我可以清楚地看到这也是一个缺点!3:您说的协议号是什么意思?4:是的,我也要实现它,但在执行其他步骤和我的哈希/盐值正确之前,我还没有考虑过它! :) - Sigils
是的,第三个整数是迭代计数。开始时指定了1K,但那被认为是低的。在实际使用中,10K是最低的,但请查看此问题。迭代计数有助于防止攻击者对密码进行暴力破解。然而,更重要的是确保不接受弱密码。或者,显示密码/密码短语的强度,让用户决定。 - Maarten Bodewes
所谓协议号,是指一个数字,比如初始值为0,它与盐和密码哈希一起保存,表示:在生成哈希时,我们使用了PBKDF2与SHA1、16字节的盐、64Ki次迭代以及UTF-8编码的密码短语。您可能希望稍后进行升级,但当您切换到另一种算法时,无法从数据库中存储的信息重新计算。 - Maarten Bodewes
非常感谢你的大力帮助,伙计!这对我继续工作和学习如何保护密码的曲线有很大帮助。 - Sigils

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接