views:

68

answers:

1

I've being messing around the C# Bouncy Castle API to find how to do a PBKDF2 key derivation.

I am really clueless right now.

I tried reading through the Pkcs5S2ParametersGenerator.cs and PBKDF2Params.cs files but i really cant figure out how to do it.

According to the research I have done so far, PBKDF2 requires a string (or char[]) which is the password, a salt and an iteration count.

So far the most promising and most obvious i've come so far is the PBKDF2Params and Pkcs5S2ParametersGenerator.

None of these seems to be accepting a string or a char[].

Has anyone done this in C# or have any clue about this? Or perhaps someone who has implemented BouncyCastle in Java and can help?

Thanx a lot in advance :)

UPDATE: I have found how to do this in Bouncy Castle. Look below for answer :)

A: 

After hours and hours of going through the code, I found that the easiest way to do this is to take a few parts of the code in Pkcs5S2ParametersGenerator.cs and create my own class which of course use other BouncyCastle API's. This works perfectly with the Dot Net Compact Framework (Windows Mobile). This is the equivalent of Rfc2898DeriveBytes class which is not present in the Dot Net Compact Framework 2.0/3.5. Well, maybe not the EXACT equivalent but does the job :)

This is PKCS5/PKCS#5

The PRF (Pseudo Random Function) which is used will be HMAC-SHA1

First things, first. Download the Bouncy Castle compiled assembly from http://www.bouncycastle.org/csharp/, add the BouncyCastle.Crypto.dll as a reference to your project.

After that create new class file with the code below.

using System;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Macs;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Security;

namespace PBKDF2_PKCS5
{
    class PBKDF2
    {

        private readonly IMac hMac = new HMac(new Sha1Digest());

        private void F(
            byte[] P,
            byte[] S,
            int c,
            byte[] iBuf,
            byte[] outBytes,
            int outOff)
        {
            byte[] state = new byte[hMac.GetMacSize()];
            ICipherParameters param = new KeyParameter(P);

            hMac.Init(param);

            if (S != null)
            {
                hMac.BlockUpdate(S, 0, S.Length);
            }

            hMac.BlockUpdate(iBuf, 0, iBuf.Length);

            hMac.DoFinal(state, 0);

            Array.Copy(state, 0, outBytes, outOff, state.Length);

            for (int count = 1; count != c; count++)
            {
                hMac.Init(param);
                hMac.BlockUpdate(state, 0, state.Length);
                hMac.DoFinal(state, 0);

                for (int j = 0; j != state.Length; j++)
                {
                    outBytes[outOff + j] ^= state[j];
                }
            }
        }

        private void IntToOctet(
            byte[] Buffer,
            int i)
        {
            Buffer[0] = (byte)((uint)i >> 24);
            Buffer[1] = (byte)((uint)i >> 16);
            Buffer[2] = (byte)((uint)i >> 8);
            Buffer[3] = (byte)i;
        }

        // Use this function to retrieve a derived key.
        // dkLen is in octets, how much bytes you want when the function to return.
        // mPassword is the password converted to bytes.
        // mSalt is the salt converted to bytes
        // mIterationCount is the how much iterations you want to perform. 


        public byte[] GenerateDerivedKey(
            int dkLen,
            byte[] mPassword,
            byte[] mSalt,
            int mIterationCount
            )
        {
            int hLen = hMac.GetMacSize();
            int l = (dkLen + hLen - 1) / hLen;
            byte[] iBuf = new byte[4];
            byte[] outBytes = new byte[l * hLen];

            for (int i = 1; i <= l; i++)
            {
                IntToOctet(iBuf, i);

                F(mPassword, mSalt, mIterationCount, iBuf, outBytes, (i - 1) * hLen);
            }

        //By this time outBytes will contain the derived key + more bytes.
       // According to the PKCS #5 v2.0: Password-Based Cryptography Standard (www.truecrypt.org/docs/pkcs5v2-0.pdf) 
       // we have to "extract the first dkLen octets to produce a derived key".

       //I am creating a byte array with the size of dkLen and then using
       //Buffer.BlockCopy to copy ONLY the dkLen amount of bytes to it
       // And finally returning it :D

        byte[] output = new byte[dkLen];

        Buffer.BlockCopy(outBytes, 0, output, 0, dkLen);

        return output;
        }


    }
}

So how to use this function? Simple! :) This is a very simple example where the password and the salt is provided by the user.

private void cmdDeriveKey_Click(object sender, EventArgs e)
        {
            byte[] salt = ASCIIEncoding.UTF8.GetBytes(txtSalt.Text);

            PBKDF2 passwordDerive = new PBKDF2();


      // I want the key to be used for AES-128, thus I want the derived key to be
      // 128 bits. Thus I will be using 128/8 = 16 for dkLen (Derived Key Length) . 
      //Similarly if you wanted a 256 bit key, dkLen would be 256/8 = 32. 

            byte[] result = passwordDerive.GenerateDerivedKey(16, ASCIIEncoding.UTF8.GetBytes(txtPassword.Text), salt, 1000);

           //result would now contain the derived key. Use it for whatever cryptographic purpose now :)
           //The following code is ONLY to show the derived key in a Textbox.

            string x = "";

            for (int i = 0; i < result.Length; i++)
            {
                x += result[i].ToString("X");
            }

            txtResult.Text = x;

        }

How to check whether this is correct? There is an online javascript implementation of PBKDF2 http://anandam.name/pbkdf2/

I got consistent results :) Please report if anyone is getting an incorrect result :)

Hope this helps someone :)

UPDATE: Confirmed working with test vectors provided here

http://tools.ietf.org/html/draft-josefsson-pbkdf2-test-vectors-00

UPDATE: Alternatively, for the salt we can use a RNGCryptoServiceProvider. Make sure to reference the System.Security.Cryptography namespace.

RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();        

byte[] salt = new byte[16];

rng.GetBytes(salt);
Ranhiru Cooray
You answer your own questions pretty fast!
GregS