Salting and Hashing with PBKDF2
Asked Answered
A

1

3

I am trying to learn cryptography with saving passwords in database with hashing and salting, so I decided to make a login-system trying implementing this system.

My database consist of

  • UserID int PK
  • Username varchar(250)
  • Salt varbinary(64)
  • Password varbinary(64)
  • RegDate datetime
  • Email varchar(250)

I am using PBKDF2, but it seems like this isn't a hashing / salting method, what is it if it isn't?

If so I am doing this right?

My Keys

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

Inserting data to the database

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();
            }
        }

Checking if user is valid

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;
            }
        }
    }

My system works, it is setting data into the database as bytes and if the user is typing the correct password it returns true. But is this the right way? Is this even hashing / salting?

Alidaalidade answered 19/4, 2014 at 11:20 Comment(8)
I'm not massively familiar with using PBKDF2, but from the looks of it I think you're correct that you aren't actually hashing the password. Try reading through this MSDN document (msdn.microsoft.com/en-us/library/…) which shows some example usage, but this is using symmetric encryption. Hashing is asymmetric, so you should never be able to get the original plaintext back once it is hashed.Houstonhoustonia
@martin_costello I think it is hashing the password. It is not using symmetric encryption PBKDF2 is based on HMAC, not a cipher in sight. Hashing is not asymmetric, but it is indeed one way. I'm massively familiar with PBKDF2.Windsail
@owlstead What I mean was that the MSDN example was symmetric.Houstonhoustonia
@martin_costello Yes it is; but what are you trying to say with that specifically? I mean, it uses the output as key and that's different than storing it, but otherwise...Windsail
@owlstead I was saying "here's some example about using PBKDF2, but it's not a complete example of what you're doing as it's symmetric rather than hash-based".Houstonhoustonia
@martin_costello The hashing is performed by PBKDF2, that encryption is used on top of that in the example does not take anything away from that. IMHO your initial comment does not make anything more clear than it already was; it is not correct on most parts.Windsail
You guys just confused me! :) So basically @ martin_costello, you are saying that PBKDF2 isn't a hashing method but just a step to help hashing and afterwards I can use something like a symmetric or asymmetric to go further? @owlstead what do you mean with HMAC, and you are saying that PBKDF2 is indeed doing hashing!Alidaalidade
PBKDF2 uses a HMAC construction as PRF (pseudo random function) in a loop. It creates pseudo random bytes based on the input - the key (read: UTF-8 encoded password) and key derivation info (read: the salt). A HMAC is a hash based message authentication code, and as such is a PRF for most practical purposes. For the hash any cryptographically secure hash can be used, but Rfc2898DeriveBytes uses SHA-1. For checking if a password is correct, you only need to recalculate the PBKDF2 value and compare, no need for additional cryptography.Windsail
W
8

You're basically going in the right direction, but I'll point out some things to think about:

  1. The default number of iterations of the PBKDF2 method may not be sufficient, and you may not want to leave things to the default. I would recommend specifying an iteration count of at least 10K.

  2. On the other hand, the key size and salt size are counted by this implementation in bytes. 64 bytes is a bit too much. Keeping both to 16 bytes each should be ample. It's not recommended to go over 20 bytes as that's the maximum size of the underlying hash function / HMAC. Going over that will only give an advantage to the attacker (this, according to many, is a design mistake in PBKDF2). You may of course set the size of varbinary to a higher value to allow for future upgrades.

  3. It's recommended that you keep a protocol number with your salt and hashed password. Doing so makes it possible for you to upgrade the scheme at a later date and per entry, when the user is available to reset his/her password.

  4. Minor point; MSDN does not specify when the salt is generated. I would check the randomness of the salt (check if it is different each time) and only ask the salt after calling getBytes to make sure that the salt is indeed random, even if the implementation changes. Otherwise generate it yourself using a cryptographically secure random number generator.

Windsail answered 19/4, 2014 at 12:0 Comment(4)
Thanks a lot pal! It helped me a lot! 1: By reading your comment and Microsofts documentation, is Rfc2898DeriveBytes(String, Int32, Int32) is the third parameter the iteration? So you would recommend using a 10K int? How come? 2: Got ya, I thought, well why not make it longer therefore harder to break. But I can clearly see why that is also a downside! 3: What do you mean with a protocol number? 4: Ya I am going to implement that too, but haven't considered it before all the other steps and my hashing / salting is written correct! :)Alidaalidade
Yep, third int is the iteration count. 1K was specified at the beginning, but that's considered low. 10K is the lowest in practical use, but check this question. The iteration count helps against an attacker brute forcing the passwords. It helps much more to make sure that weak passports are not accepted though. Alternatively, show the password/passphrase strength and let the user decide.Windsail
With protocol number I mean a number, say 0 initially, that is saved with the salt & password hash and means: we were using PBKDF2 with SHA1, a 16 byte salt, 64Ki of iterations and UTF-8 encoding of the pass phrase when the hash was generated. You may want to upgrade later on, and you cannot recalculate from the information stored in the database when you switch to another algorithm.Windsail
Thank you so much for the big help pal! It helped me a lot to continue the work and the learning curve on how to secure passwords.Alidaalidade

© 2022 - 2024 — McMap. All rights reserved.