0
votes

I would like to hash passwords using PBKDF2 with a pepper and a salt in C#. I am a bit new to Cryptography, so feel free to correct me if I am wrong.

I use the Rfc2898DeriveBytes Class because (according to other Stackoverflow users) bcrypt and other hash algorithms aren't natively supported and verified in C#, so it could pose a security threat. The purpose of this post isn't to start a discussing about which hashing algorithm is the best. > Bcrypt in C# Stackoverflow

My goal: Every password will get a random salt and pepper, the password will be hashed with a certain amount of iterations.

My question: Is it bad to have a bigger input size compared to the desired hash size and is my implementation correct?

  • Example: (PasswordInput (?) + Pepper (16 Bytes) + Salt (16 Bytes) > HashOutput (20 Bytes)

My code

public class GenerateHash
{
    //Fields
    private const int saltSize = 16;
    private const int hashSize = 16;
    private const int iterations = 10000;
    private const string secretPepper = "Secret 16 Byte pepper."; 

   //Properties
    private string inputId { get; set; }

    //Methods
    public byte[] GeneratePBKDF2String(string inputId, string secretPepper, int saltSize, int 
    hashSize, int iterations)
    {
        // Generate a random salt.
        RNGCryptoServiceProvider cryptographicServiceProvider = new RNGCryptoServiceProvider();
        byte[] salt = new byte[saltSize];
        provider.GetBytes(salt);

        // Generate a salted hash with pepper.
        Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(inputId + secretPepper, salt, iterations);
        return pbkdf2.GetBytes(hashSize);
    }
}

I understand that:

  • A hash isn't reversible.
  • A salt and pepper are added to increase security and prevent rainbow table attacks.
  • A salt is a unique and random string, it doesn't have to be secret and can be stored alongside the hash in a database.
  • A pepper is not unique and it is used for every hash. It is a secret and it isn't stored in the database.
  • At least a 128-bit (16 bytes > 16 characters) should be used for the salt and pepper.
  • At least 10.000 iterations should be used for the algorithm.

Research: Microsoft Rfc2898DeriveBytes, Example Code

2

2 Answers

1
votes

PBKDF2 (with SHA-1) - which the terribly named Rfc2898DeriveBytes implements - uses a repeated HMAC with the password - encoded to bytes - as key. Generally, HMAC simply performs a padding method on the input key (ipad and opad if you want to look it up). This padding goes up to the input block size of the hash function. However, let's look at the definition of HMAC in the HMAC RFC:

We denote by B the byte-length of such blocks (B=64 for all the above mentioned examples of hash functions), and by L the byte-length of hash outputs (L=16 for MD5, L=20 for SHA-1). The authentication key K can be of any length up to B, the block length of the hash function. Applications that use keys longer than B bytes will first hash the key using H and then use the resultant L byte string as the actual key to HMAC.

In your case, if your encoded password is longer than 64 - 16 = 48 bytes (with 16 the pepper size) your HMAC may be slower. A smart PBKDF2 function however could detect this and work around the issue by performing the initial hashing part only once.

So if your password is over 48 byte then you could give some benefit to the attacker if:

  1. your implementation isn't that smart and
  2. the implementation of the attacker is smart.

Note that the hash output size has nothing to do with this. You could use PBKDF2 with SHA-512 - with a block size of 1024 bits rather than 512 bits for SHA-1 and SHA-256 - in case this is a problem.

The hash output size of the hash function within PBKDF2 (SHA-1 by default) does matter if you request more than the output size of bytes. In that case you are also handing back advantage to an attacker. Fortunately you're only asking for 16 bytes in hashSize (a variable name that you might want to change to passwordHashSize to avoid confusion).


I understand that:

Oh dear ;)

  • A hash isn't reversible.

A cryptographic hash function and password hash function isn't reversible, other hash functions may be reversible.

  • A salt and pepper are added to increase security and prevent rainbow table attacks.

You only require a salt for that. A pepper prevents an attacker from guessing the password altogether, if it can be kept safe and if it is strong enough.

  • A salt is a unique and random string, it doesn't have to be secret and can be stored alongside the hash in a database.

That's correct.

  • A pepper is not unique and it is used for every hash. It is a secret and it isn't stored in the database.

Or it is encrypted itself and stored in the database, but yeah, in the end it needs to be secured one way or the other.

  • At least a 128-bit (16 bytes > 16 characters) should be used for the salt and pepper.

Well, it's kind-of the upper limit, I'd say 64 to 128 fully random bits, preferably over 80. However, not every character can be mapped to a byte, so your pepper is unlikely to be fully random - a bad idea for something that is basically a secret key.

Note that the salt configuration option of PBKDF2 may be any size up to 64 - 8 - 4 = 52 bytes before another block encrypt is needed for SHA-1. For that reason the salt & pepper are usually concatenated. This will also allow you use a true random pepper. It leaves more characters (64) for the password as well that way.

  • At least 10.000 iterations should be used for the algorithm.

Commonly we recommend about a million nowadays. But really, any amount of CPU cycles you can spare makes it harder to an adversary. The point is bit moot if the adversary really cannot get to the pepper of course. In that case a single round is enough - but you may want to use a higher iteration count as a second line of defense (e.g. against sysadmins trying to get to passwords of your users, using a copy of the database).

0
votes
  1. Having an input bigger than the output size is not a problem. A good hash function should resist all attacks, even if the input is really big.
  2. Speaking of good hash functions: The output size of 20 bytes hints at SHA-1 being used. SHA-1 is so broken that practical collisions already exist so it should not be used. (I don't know if Rfc2898 is a scheme that is secure if collisions for SHA-1 exist or not, but one is better safe than sorry when it comes to security.
  3. The implementation seems to be ok otherwise. I'm not up-to-date to C# but you seem to be using a cryptographic RNG for the salt.
  4. You pepper should also be random.
  5. 128 bit (16 byte) keys are totally fine as long you're passwords don't need to be kept secure for longer than 50 years or so.