CryptoConfig is not able to identify HashPbkdf2 from the machine.config file, which is set as: <cryptoClass PKDF2Hashing="HashPbkdf2,…


To know more about CryptoConfig please refer to https://msdn.microsoft.com/en-us/library/system.security.cryptography.cryptoconfig(v=vs.110).aspx
It's a class that accesses the cryptography configuration information.
Please refer to https://blogs.msdn.microsoft.com/shawnfa/2008/12/02/cryptoconfig/ to know more on CryptoConfig.
Recently I encountered an issue where a customer was implementing his own HashPbkdf2 class that does the SHA256 hashing based on Password-Based Key Derivation Function 2.
A theoritical reference to PBKDF2 can be found here: https://en.wikipedia.org/wiki/PBKDF2
In .NET the Rfc2898DeriveBytes class implements PBKDF2, by using a pseudo-random number generator based on HMACSHA1. However this class does not implement SHA256 or SHA512.
The issue is how to implement the cryptography configuration information correctly, so that your implementation of the class can take effect on all flavours of the .NET framework when you do a HashAlgorithm.Create() providing your class information as an input.
For example if you implement the following:
A. Class: HashPbkdf2
B. Assembly: ClassLib.DLL
The implementation of cryptography configuration information in machine.config should look like:
<configuration>
<mscorlib>
<cryptographySettings>
<cryptoNameMapping>
<cryptoClasses>
<cryptoClass PKDF2Hashing="HashPbkdf2, ClassLib,
Culture=neutral, PublicKeyToken=69b7085a78ef8580, Version=1.0.0.0"/>
</cryptoClasses>
<nameEntry name="PKDF2" class="PKDF2Hashing"/>
<nameEntry name="HashPbkdf2" class="PKDF2Hashing"/>
<nameEntry name="ClassLib.HashPbkdf2" class="PKDF2Hashing"/>
</cryptoNameMapping>
</cryptographySettings>
<SNIP>
File: C:\Windows\Microsoft.NET\Framework\vX.X.XXXXX\CONFIG
Here is a sample code to see that the above configuration works:
private static string EncodePassword(string pass, string salt)
{
byte[] bIn = Encoding.Unicode.GetBytes(pass);
byte[] bSalt = Convert.FromBase64String(salt);
byte[] bRet = null;
            // user defined PBKDF2 custom algorithm
// CryptoConfig looks for information in the configuration/mscorlib/cryptographySettings element of machine.config.
// If there are multiple mscorlib sections, then crypto config prefers one with a version attribute that matches the current runtime
            HashAlgorithm hm = HashAlgorithm.Create("PKDF2");
            if (hm == null)
{
return "HashAlgorithm cannot be NULL. Exception";
}
            if (hm is KeyedHashAlgorithm)
{
KeyedHashAlgorithm kha = (KeyedHashAlgorithm)hm;
if (kha.Key.Length == bSalt.Length)
{
kha.Key = bSalt;
}
else if (kha.Key.Length < bSalt.Length)
{
byte[] bKey = new byte[kha.Key.Length];
Buffer.BlockCopy(bSalt, 0, bKey, 0, bKey.Length);
kha.Key = bKey;
}
else
{
byte[] bKey = new byte[kha.Key.Length];
for (int iter = 0; iter < bKey.Length;)
{
int len = Math.Min(bSalt.Length, bKey.Length - iter);
Buffer.BlockCopy(bSalt, 0, bKey, iter, len);
iter += len;
}
kha.Key = bKey;
}
bRet = kha.ComputeHash(bIn);
}
else
{
byte[] bAll = new byte[bSalt.Length + bIn.Length];
Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);
bRet = hm.ComputeHash(bAll);
}
            return Convert.ToBase64String(bRet);
}
There is a 2nd way to solve this by not adding an entry to "machine.config" file. We can add the PKDF2 nameEntry to the current appdomain, so that the HashAlgorithm can be created at runtime.
Code below for clarity.
This is the custom class for adding the algo to the current AppDomain.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Threading;
namespace TestPBKDF2
{
public static class MyCryptoConfig
{
private static Dictionary<string, Type> s_algorithmMap = DefaultAlgorithmMap;
private static ReaderWriterLockSlim s_algorithmMapLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
        /// <summary>
///     Default mapping of algorithm names to algorithm types
/// </summary>
private static Dictionary<string, Type> DefaultAlgorithmMap
{
get
{
Dictionary<string, Type> map = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
                //
// System.Core algorithms
//
                AddAlgorithmToMap(map, typeof(AesCryptoServiceProvider), "AES");
AddAlgorithmToMap(map, typeof(AesManaged));
                AddAlgorithmToMap(map, typeof(ECDsaCng), "ECDsa");
                AddAlgorithmToMap(map, typeof(ECDiffieHellmanCng), "ECDH", "ECDiffieHellman");
                AddAlgorithmToMap(map, typeof(MD5Cng));
AddAlgorithmToMap(map, typeof(SHA1Cng));
AddAlgorithmToMap(map, typeof(SHA256Cng));
AddAlgorithmToMap(map, typeof(SHA256CryptoServiceProvider));
AddAlgorithmToMap(map, typeof(SHA384Cng));
AddAlgorithmToMap(map, typeof(SHA384CryptoServiceProvider));
AddAlgorithmToMap(map, typeof(SHA512Cng));
AddAlgorithmToMap(map, typeof(SHA512CryptoServiceProvider));
                return map;
}
}
        /// <summary>
///     <para>
///         AddAlgorithm allows an application to register a new algorithm with CryptoConfig2 in the
///         current AppDomain. The algorithm is then creatable via calling
///         <see cref="CreateFromName" /> and supplying one of:
///     </para>
///     <list type="bullet">
///         <item>The name of the algorithm type</item>
///         <item>The namespace qualified name of the algorithm type</item>
///         <item>Any of the aliases supplied for the type</item>
///     </list>
///     <para>
///         This registration is valid only in the AppDomain that does the registration, and is not
///         persisted. The registered algorithm will only be creatable via CryptoConfig2 and not via
///         standard <see cref="CryptoConfig" />.
///     </para>
///     <para>
///         All algorithms registered with CryptoConfig2 must have a default constructor, or they wil
///          not be creatable at runtime.
///     </para>
///     <para>
///         This method is thread safe.
///     </para>
/// </summary>
/// <permission cref="PermissionSet">The immediate caller of this API must be fully trusted</permission>
/// <param name="algorithm">type to register with CryptoConfig2</param>
/// <param name="aliases">list of additional aliases which can create the type</param>
/// <exception cref="ArgumentNullException">
///     if <paramref name="algorithm"/> or <paramref name="aliases"/> are null
/// </exception>
/// <exception cref="InvalidOperationException">
///     if an alias is either null, empty, or a duplicate of an existing registered alias
/// </exception>
[PermissionSet(SecurityAction.LinkDemand, Unrestricted = true)]
[SecurityCritical]
public static void AddAlgorithm(Type algorithm, params string[] aliases)
{
if (algorithm == null)
throw new ArgumentNullException("algorithm");
if (aliases == null)
throw new ArgumentNullException("aliases");
            s_algorithmMapLock.EnterWriteLock();
try
{
// Make sure that we don't already have mappings for the input aliases - we want to eagerly
// check for this rather than just letting the hash table insert fail so that the map doesn't
// end up with some of the aliases added and others not added.
//
// Note that we're explicitly not trying to protect against having the same alias added
// multiple times via the same call to AddAlgorithm, since that problem is detectable by the
// user of the API whereas detecting a conflict with another alias which had been previously
// added cannot be reliably detected in the presense of multiple threads.
foreach (string alias in aliases)
{
if (String.IsNullOrEmpty(alias))
{
throw new InvalidOperationException("EmptyCryptoConfigAlias");
}
                    if (s_algorithmMap.ContainsKey(alias))
{
throw new InvalidOperationException("DuplicateCryptoConfigAlias");
}
}
                AddAlgorithmToMap(s_algorithmMap, algorithm, aliases);
}
finally
{
s_algorithmMapLock.ExitWriteLock();
}
}
        /// <summary>
///     Add an algorithm to a given type map
/// </summary>
private static void AddAlgorithmToMap(Dictionary<string, Type> map, Type algorithm, params string[] aliases)
{
Debug.Assert(map != null, "map != null");
Debug.Assert(algorithm != null, "algorithm != null");
            foreach (string alias in aliases)
{
Debug.Assert(!String.IsNullOrEmpty(alias), "!String.IsNullOrEmpty(alias)");
map.Add(alias, algorithm);
}
            if (!map.ContainsKey(algorithm.Name))
{
map.Add(algorithm.Name, algorithm);
}
            if (!map.ContainsKey(algorithm.FullName))
{
map.Add(algorithm.FullName, algorithm);
}
}
        /// <summary>
///     <para>
///         CreateFactoryFromName is similar to <see cref="CreateFromName"/>, except that instead of
///         returning a single instance of a crypto algorithm, CreateFactoryFromName returns a
///         function that can create new instances of the algorithm.   This function will be more
///         efficient to use if multiple intsances of the same algorithm are needed than calling
///         CreateFromName repeatedly.
///     </para>
///     <para>
///         Name comparisons are case insensitive.
///     </para>
///     <para>
///         This method is thread safe.
///     </para>
/// </summary>
/// <param name="name">name of the algorithm to create a factory for</param>
/// <exception cref="ArgumentNullException">if <paramref name="name"/> is null</exception>
public static Func<object> CreateFactoryFromName(string name)
{
// Figure out what type of algorithm we need to create
object algorithm = CreateFromName(name);
if (algorithm == null)
{
return null;
}
            Type algorithmType = algorithm.GetType();
            // Since we only need the algorithm type, rather than the full algorithm itself, we can clean up
// the algorithm instance if it is disposable
IDisposable disposableAlgorithm = algorithm as IDisposable;
if (disposableAlgorithm != null)
{
disposableAlgorithm.Dispose();
}
            // Create a factory delegate which returns new instances of the algorithm type
NewExpression algorithmCreationExpression = Expression.New(algorithmType);
LambdaExpression creationFunction = Expression.Lambda<Func<object>>(algorithmCreationExpression);
return creationFunction.Compile() as Func<object>;
}
        /// <summary>
///     <para>
///         CreateFromName attempts to map the given algorithm name into an instance of the specified
///         algorithm. It works with both the built in algorithms in the .NET Framework 3.5 as well
///         as the algorithms in the Security.Cryptography.dll assembly. Since it does work with the
///         built in crypto types, CryptoConfig2.CreateFromName can be used as a drop-in replacement
///         for <see cref="CryptoConfig.CreateFromName(string)" />
///     </para>
///     <para>
///         Types in System.Core.dll and Security.Cryptography.dll can be mapped either by their
///         simple type name or their namespace type name. For example, AesCng and
///         Security.Cryptography.AesCng will both create an instance of the <see cref="AesCng" />
///         type. Additionally, the following names are also given mappings in CryptoConfig2:
///     </para>
///     <list type="bullet">
///         <item>AES - <see cref="AesCryptoServiceProvider" /></item>
///         <item>ECDsa - <see cref="ECDsaCng" /></item>
///         <item>ECDH - <see cref="ECDiffieHellmanCng" /></item>
///         <item>ECDiffieHellman - <see cref="ECDiffieHellmanCng" /></item>
///     </list>
///     <para>
///         Name comparisons are case insensitive.
///     </para>
///     <para>
///         This method is thread safe.
///     </para>
/// </summary>
/// <param name="name">name of the algorithm to create</param>
/// <exception cref="ArgumentNullException">if <paramref name="name"/> is null</exception>
public static object CreateFromName(string name)
{
if (name == null)
throw new ArgumentNullException("name");
            // First try to use standard CryptoConfig to create the algorithm
object cryptoConfigAlgorithm = CryptoConfig.CreateFromName(name);
if (cryptoConfigAlgorithm != null)
{
return cryptoConfigAlgorithm;
}
            // If we couldn't find the algorithm in crypto config, see if we have an internal mapping for
// the name
s_algorithmMapLock.EnterReadLock();
try
{
Type cryptoConfig2Type = null;
if (s_algorithmMap.TryGetValue(name, out cryptoConfig2Type))
{
return Activator.CreateInstance(cryptoConfig2Type);
}
}
finally
{
s_algorithmMapLock.ExitReadLock();
}
            // Otherwise we don't know how to create this type, so just return null
return null;
}
}
}
Here is the test code to read in PKDF2 from the current application domain.
using System;
using System.Text;
using System.Security.Cryptography;
using FDVS.Web.Portal.Membership.ClassLib;
namespace TestPBKDF2
{
class Program
{
private static string EncodePassword(string pass, string salt)
{
byte[] bIn = Encoding.Unicode.GetBytes(pass);
byte[] bSalt = Convert.FromBase64String(salt);
byte[] bRet = null;
// user defined PBKDF2 custom algorithm
// WE ARE NOT USING MACHINE.CONFIG FOR NOW. WE ARE USING OUR OWN MyCryptoConfig TO ADD THE NEW ALGO TO THE CURRENT APP DOMAIN.
MyCryptoConfig.AddAlgorithm(typeof(HashPbkdf2),
"PKDF2",
"HashPbkdf2",
"ClassLib");
            HashAlgorithm hm = MyCryptoConfig.CreateFromName("PKDF2") as HashAlgorithm;
            if (hm == null)
{
return "HashAlgorithm cannot be NULL. Exception";
}
            if (hm is KeyedHashAlgorithm)
{
KeyedHashAlgorithm kha = (KeyedHashAlgorithm)hm;
if (kha.Key.Length == bSalt.Length)
{
kha.Key = bSalt;
}
else if (kha.Key.Length < bSalt.Length)
{
byte[] bKey = new byte[kha.Key.Length];
Buffer.BlockCopy(bSalt, 0, bKey, 0, bKey.Length);
kha.Key = bKey;
}
else
{
byte[] bKey = new byte[kha.Key.Length];
for (int iter = 0; iter < bKey.Length;)
{
int len = Math.Min(bSalt.Length, bKey.Length - iter);
Buffer.BlockCopy(bSalt, 0, bKey, iter, len);
iter += len;
}
kha.Key = bKey;
}
bRet = kha.ComputeHash(bIn);
}
else
{
byte[] bAll = new byte[bSalt.Length + bIn.Length];
Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);
bRet = hm.ComputeHash(bAll);
}
            return Convert.ToBase64String(bRet);
}
        private static string CreateSalt()
{
int max_length = 32;
byte[] userBytes = new byte[max_length];
string salt;
long XORED = 0x00;
            foreach (int x in userBytes)
XORED = XORED ^ x;
            Random rand = new Random(Convert.ToInt32(XORED));
salt = rand.Next().ToString();
salt += rand.Next().ToString();
salt += rand.Next().ToString();
salt += rand.Next().ToString();
return salt;
}
        static void Main(string[] args)
{
string pass = "Password@123";
string salt = CreateSalt();
// Call EncodePassword
string encodedPassword = EncodePassword(pass, salt);
Console.WriteLine(encodedPassword);
}
}
}
Comments (0)

Skip to main content