4

I need to update some code that is using the PBKDF2 implementation in .Net, Rfc2898DeriveBytes to hash user credentials. It is my understanding that this function uses SHA-1 under the hood. I need to update the underlying hashing algorithm of the systems password hashing to use SHA-256 (This is a client IT-SEC requirement).

Having done some reading it seems it is best practice to continue to to use a Key derivation function, however PBKDF2 doesn't allow you to dictate the algorithm is should use, which is obviously a problem for me.

Our system is using .NET 4.5.1 and currently is not an option to upgrade that and I am reasonably confident it is not an option to reference any new .NET core .dlls that I've heard contain a new implementation of PBKDF2 that allows you to specify your algorithm.

I want to avoid home made implementations at all cost,s as that's the 1st rule of Crypto-Club right?

Any guidance on what is best practice would be appreciated.

Thanks

Dave
  • 2,829
  • 3
  • 17
  • 44
  • If you look at the source to the PBKDF2 implementation, you may find that it uses the OS to generate the credentials. Then it's just a matter of creating your own implementation to use a later version of the OS calls. Perhaps some P/Invoke's somewhere and you are done. – Neil Jun 11 '18 at 11:25
  • 1
    If you can't upgrade .NET (which really is your best option) you may be able to use [Bouncy Castle](https://stackoverflow.com/q/34950611/4137916). – Jeroen Mostert Jun 11 '18 at 11:33
  • 1
    *Our system is using .NET 4.5.1 and currently is not an option to upgrade that* Well it should be, Microsoft ended support for .NET 4.5.1 on January 12, 2016. How are you getting security updates??? https://blogs.msdn.microsoft.com/dotnet/2015/12/09/support-ending-for-the-net-framework-4-4-5-and-4-5-1/ – ta.speot.is Jun 11 '18 at 11:36
  • The easy upgrade is of course 4.5.2, still supported. But not a solution to this one. Standing still is as much a risk as going forward. – bommelding Jun 11 '18 at 11:40
  • @ta.speot.is "Applications targeting a .NET Framework version that is no longer supported will not need to retarget or recompile to a newer version as they can be run on a later .NET Framework version.". Sometimes retargetting you're application isn't an option. At least not one that I get to make the decision on – Dave Jun 11 '18 at 11:40
  • If your client is security conscious enough to demand a new hashing algorithm, it's probably not too much to ask them to use the latest (and also more secure) version of .NET, so what your application is targeting should not be an issue. If your application should support the old platform *in addition*, and you don't want to maintain two binaries (though you could, with conditional compilation) it could target .NET 4.5.1 and use runtime reflection to find if the new functionality is present. Code like that is brittle and hard to write, though. Ultimately security concerns will force upgrading. – Jeroen Mostert Jun 11 '18 at 11:49

3 Answers3

3

You can P/Invoke to BCryptDeriveKeyPBKDF2, assuming you're on Win7+.

private static void PBKDF2(
    string password,
    byte[] salt,
    int iterationCount,
    string hashName,
    byte[] output)
{
    int status = SafeNativeMethods.BCryptOpenAlgorithmProvider(
        out SafeNativeMethods.SafeBCryptAlgorithmHandle hPrf,
        hashName,
        null,
        SafeNativeMethods.BCRYPT_ALG_HANDLE_HMAC_FLAG);

    using (hPrf)
    {
        if (status != 0)
        {
            throw new CryptographicException(status);
        }

        byte[] passBytes = Encoding.UTF8.GetBytes(password);

        status = SafeNativeMethods.BCryptDeriveKeyPBKDF2(
            hPrf,
            passBytes,
            passBytes.Length,
            salt,
            salt.Length,
            iterationCount,
            output,
            output.Length,
            0);

        if (status != 0)
        {
            throw new CryptographicException(status);
        }
    }
}

[SuppressUnmanagedCodeSecurity]
private static class SafeNativeMethods
{
    private const string BCrypt = "bcrypt.dll";
    internal const int BCRYPT_ALG_HANDLE_HMAC_FLAG = 0x00000008;

    [DllImport(BCrypt, CharSet = CharSet.Unicode)]
    [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
    internal static extern int BCryptDeriveKeyPBKDF2(
        SafeBCryptAlgorithmHandle hPrf,
        byte[] pbPassword,
        int cbPassword,
        byte[] pbSalt,
        int cbSalt,
        long cIterations,
        byte[] derivedKey,
        int cbDerivedKey,
        int dwFlags);

    [DllImport(BCrypt)]
    [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
    private static extern int BCryptCloseAlgorithmProvider(IntPtr hAlgorithm, int flags);

    [DllImport(BCrypt, CharSet = CharSet.Unicode)]
    [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
    internal static extern int BCryptOpenAlgorithmProvider(
        out SafeBCryptAlgorithmHandle phAlgorithm,
        string pszAlgId,
        string pszImplementation,
        int dwFlags);

    internal sealed class SafeBCryptAlgorithmHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        public SafeBCryptAlgorithmHandle() : base(true)
        {
        }

        protected override bool ReleaseHandle()
        {
            return BCryptCloseAlgorithmProvider(handle, 0) == 0;
        }
    }
}
bartonjs
  • 30,352
  • 2
  • 71
  • 111
2

I'll tell you what I would do: I would take the source of the newest (not exactly the newest because it uses Span<>... Just a little older :-) ) of Rfc2898DeriveBytes from the corefx github

You'll need the full code of:

plus two methods (GenerateRandom and WriteInt) from

Then you'll have some calls to SR.*something* that you'll have to replace to some messages like "some error", plus a SR.Format that you have to replace with string.Format.

Then you'll have (nearly) the newest version of Rfc2898DeriveBytes that has a constructor that accepts as a parameter HashAlgorithmName.SHA256.

This should be the end result: https://ideone.com/lb2Qya

I had the bad idea of putting the source code in the namespace My.System... bad bad idea... I had to prefix global:: to all the namespaces :-(

xanatos
  • 109,618
  • 12
  • 197
  • 280
1

You can specify an algorithm now, msdn page

Note: Available since 4.7.2

The names are available in System.Security.Cryptography.HashAlgorithmName

bommelding
  • 2,969
  • 9
  • 14
  • While I appreciate the answer, I did specifically say in my question that upgrade the .NET version is not an option – Dave Jun 11 '18 at 11:33