Поделиться через


PBKDF2 .Net API does not exists with SHA256 implementation. Here PBKDF2 stands for “Password-Based Key Derivation Function 2”.

PBKDF2 .Net API does not exists with SHA256 implementation.

This is true and we know that we have the Rfc2898DeriveBytes class which implements password-based key derivation functionality, PBKDF2, by using a pseudo-random number generator based on HMACSHA1.

However PBKDF2 can be implemented using SHA256, SHA384, SHA512 by using the CNG API’s.

See below for an implementation.

 

using System;

using System.Diagnostics;

using System.Globalization;

using System.Security;

using System.Security.Cryptography;

namespace Security.Cryptography

{

/// <summary>

/// Set of hash algorithms that can be used with PBKDF2.

/// Choosing, e.g., SHA-256, with compute PBKDF2 with HMAC-SHA256 as

/// a PRF.

/// </summary>

public static class PBKDF2HashAlgorithm

{

public const string SHA1 = BCryptNative.AlgorithmName.Sha1;

public const string SHA256 = BCryptNative.AlgorithmName.Sha256;

public const string SHA384 = BCryptNative.AlgorithmName.Sha384;

public const string SHA512 = BCryptNative.AlgorithmName.Sha512;

 

public static bool ValidateHashName(string name)

{

if(name != SHA1 &&

name != SHA256 &&

name != SHA384 &&

name != SHA512)

{

return false;

}

return true;

}

}

/// <summary>

/// Class containing the API for PBKDF2, a wrapper of the CNG/bcrypt.dll implementation.

/// </summary>

public static class BCryptPBKDF2

{

/// <summary>

/// Compute the PBKDF2 function on the given inputs using the CNG implementation in the <c>BCryptKeyDerivation</c> API.

/// </summary>

/// <param name="hashName">The hash function to use, must be one of the strings in <seealso cref="PBKDF2HashAlgorithm"/>.</param>

/// <param name="password">The password, as a byte array (i.e., without a string termination character).</param>

/// <param name="salt">The salt, a cryptographically random value. Should be 16-bytes or longer.</param>

/// <param name="cIterations">The number of iterations of PBKDF2 to apply.</param>

/// <returns>The digest of the password (also sometimes called derived key).  The length of the digest

/// will be equal to the length of the chosen hash function output.</returns>

/// <remarks>

/// See msdn.microsoft.com/en-us/library/windows/desktop/hh448506 for a description

/// of the wrapped function.  Larger values of cIterations will cause the function to use more

/// CPU time, and will also increase the workfactor for an attacker in a brute-force attack.

/// </remarks>

public static byte[] ComputeHash(string hashName, byte[] password, byte[] salt, Int64 cIterations)

{

if (cIterations < 1)

throw new ArgumentException("Iteration count must be greater than zero.", "cIterations");

if (salt == null)

throw new ArgumentException("Salt must be non-null", "salt");

if (password == null)

throw new ArgumentException("Password must be non-null", "password");

 

if(!PBKDF2HashAlgorithm.ValidateHashName(hashName))

throw new ArgumentException("Invalid hash name for PBKDF2");

 

byte[] digest = null;

 

double vers = Environment.OSVersion.Version.Major + Environment.OSVersion.Version.Minor * 0.1;

 

if(vers > 6.1)

{

// The BCryptKeyDerivation API is only supported on Win8/Server 2012 and above

digest = BCryptNative.PBKDF2BCryptKeyDerivation(hashName, password, salt, (UInt64) cIterations);

}

else

{

// Fall back to BCryptDeriveKeyPBKDF2, which is roughly 2x slower on systems without the KeyDerivation API

digest = BCryptNative.PBKDF2BCryptDeriveKeyPBKDF2(hashName, password, salt, (UInt64)cIterations);

}

 

return digest;

}

 

}

}

 

Now you have to P/invoke the BCrypt API's and calls PBKDF2 via the BCryptKeyDerivation API, where you can specify which of HMAC-SHA256, HMAC-SHA384 or HMAC-SHA512 to use.

See full details below.

// Copyright (c) Microsoft Corporation.  All rights reserved.

using System;

using System.Diagnostics;

using System.Diagnostics.CodeAnalysis;

using System.Runtime.CompilerServices;

using System.Runtime.ConstrainedExecution;

using System.Runtime.InteropServices;

using System.Security;

using System.Security.Cryptography;

using System.Text;

using Microsoft.Win32.SafeHandles;

 

namespace Security.Cryptography

{

//

// Public facing enumerations

//

 

/// <summary>

///     Padding modes

/// </summary>

[SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags", Justification = "Public use of the enum is not as flags")]

[SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue", Justification = "The native BCRYPT_PAD_NONE value is 1, not 0, and this is for interop.")]

public enum AsymmetricPaddingMode

{

/// <summary>

///     No padding

/// </summary>

None = 1,                       // BCRYPT_PAD_NONE

 

/// <summary>

///     PKCS #1 padding

/// </summary>

Pkcs1 = 2,                      // BCRYPT_PAD_PKCS1

 

/// <summary>

///     Optimal Asymmetric Encryption Padding

/// </summary>

Oaep = 4,                       // BCRYPT_PAD_OAEP

 

/// <summary>

///     Probabilistic Signature Scheme padding

/// </summary>

Pss = 8                         // BCRYPT_PAD_PSS

}

 

/// <summary>

///     Native wrappers for bcrypt CNG APIs.

///

///     The general pattern for this interop layer is that the BCryptNative type exports a wrapper method

///     for consumers of the interop methods.  This wrapper method puts a managed face on the raw

///     P/Invokes, by translating from native structures to managed types and converting from error

///     codes to exceptions.

/// </summary>

internal static class BCryptNative

{

//

// Enumerations

//

 

/// <summary>

///     Well known algorithm names

/// </summary>

internal static class AlgorithmName

{

internal const string Aes = "AES";                          // BCRYPT_AES_ALGORITHM

internal const string Rng = "RNG";                          // BCRYPT_RNG_ALGORITHM

internal const string Rsa = "RSA";                          // BCRYPT_RSA_ALGORITHM

internal const string TripleDes = "3DES";                   // BCRYPT_3DES_ALOGORITHM

internal const string Sha1 = "SHA1";                        // BCRYPT_SHA1_ALGORITHM

internal const string Sha256 = "SHA256";                    // BCRYPT_SHA256_ALGORITHM

internal const string Sha384 = "SHA384";                    // BCRYPT_SHA384_ALGORITHM

internal const string Sha512 = "SHA512";                    // BCRYPT_SHA512_ALGORITHM

internal const string Pbkdf2 = "PBKDF2";                    // BCRYPT_PBKDF2_ALGORITHM

}

 

/// <summary>

///     Flags for BCryptOpenAlgorithmProvider

/// </summary>

[Flags]

internal enum AlgorithmProviderOptions

{

None                = 0x00000000,

HmacAlgorithm       = 0x00000008,                           // BCRYPT_ALG_HANDLE_HMAC_FLAG

}

 

/// <summary>

///     Flags for use with the BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO structure

/// </summary>

[Flags]

internal enum AuthenticatedCipherModeInfoFlags

{

None                = 0x00000000,

ChainCalls          = 0x00000001,                           // BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG

InProgress          = 0x00000002,                           // BCRYPT_AUTH_MODE_IN_PROGRESS_FLAG

}

 

/// <summary>

///     Well known chaining modes

/// </summary>

internal static class ChainingMode

{

internal const string Cbc = "ChainingModeCBC";              // BCRYPT_CHAIN_MODE_CBC

internal const string Ccm = "ChainingModeCCM";              // BCRYPT_CHAIN_MODE_CCM

internal const string Cfb = "ChainingModeCFB";              // BCRYPT_CHAIN_MODE_CFB

internal const string Ecb = "ChainingModeECB";              // BCRYPT_CHAIN_MODE_ECB

internal const string Gcm = "ChainingModeGCM";              // BCRYPT_CHAIN_MODE_GCM

}

 

/// <summary>

///     Result codes from BCrypt APIs

/// </summary>

internal enum ErrorCode

{

Success = 0x00000000,                                       // STATUS_SUCCESS

AuthenticationTagMismatch = unchecked((int)0xC000A002),     // STATUS_AUTH_TAG_MISMATCH

BufferToSmall = unchecked((int)0xC0000023),                 // STATUS_BUFFER_TOO_SMALL

}

 

internal static class HashPropertyName

{

internal const string HashLength = "HashDigestLength";      // BCRYPT_HASH_LENGTH

}

 

/// <summary>

///     Magic numbers for different key blobs

/// </summary>

internal enum KeyBlobMagicNumber

{

RsaPublic = 0x31415352,                                     // BCRYPT_RSAPUBLIC_MAGIC

RsaPrivate = 0x32415352,                                    // BCRYPT_RSAPRIVATE_MAGIC

KeyDataBlob = 0x4d42444b,                                   // BCRYPT_KEY_DATA_BLOB_MAGIC

}

 

/// <summary>

///     Well known key blob tyes

/// </summary>

internal static class KeyBlobType

{

internal const string KeyDataBlob = "KeyDataBlob";                  // BCRYPT_KEY_DATA_BLOB

internal const string RsaFullPrivateBlob = "RSAFULLPRIVATEBLOB";    // BCRYPT_RSAFULLPRIVATE_BLOB

internal const string RsaPrivateBlob = "RSAPRIVATEBLOB";            // BCRYPT_RSAPRIVATE_BLOB

internal const string RsaPublicBlob = "RSAPUBLICBLOB";              // BCRYPT_PUBLIC_KEY_BLOB

}

 

/// <summary>

///     Well known BCrypt object property names

/// </summary>

internal static class ObjectPropertyName

{

internal const string AuthTagLength = "AuthTagLength";      // BCRYPT_AUTH_TAG_LENGTH

internal const string BlockLength = "BlockLength";          // BCRYPT_BLOCK_LENGTH

internal const string ChainingMode = "ChainingMode";        // BCRYPT_CHAINING_MODE

internal const string InitializationVector = "IV";          // BCRYPT_INITIALIZATION_VECTOR

internal const string KeyLength = "KeyLength";              // BCRYPT_KEY_LENGTH

internal const string ObjectLength = "ObjectLength";        // BCRYPT_OBJECT_LENGTH

}

 

/// <summary>

/// BCrypt parameter types (used in parameter lists)

/// </summary>

internal enum ParameterTypes

{

KdfHashAlgorithm = 0x0,

KdfSalt = 0xF,

KdfIterationCount = 0x10

}

 

/// <summary>

///     Well known BCrypt provider names

/// </summary>

internal static class ProviderName

{

internal const string MicrosoftPrimitiveProvider = "Microsoft Primitive Provider";      // MS_PRIMITIVE_PROVIDER

}

 

//

// Structures

//

 

[StructLayout(LayoutKind.Sequential)]

[SuppressMessage("Microsoft.Design", "CA1049:TypesThatOwnNativeResourcesShouldBeDisposable", Justification = "The resouces lifetime is owned by the containing type - as a value type, the pointers will be copied and are not owned by the value type itself.")]

internal struct BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO

{

internal int cbSize;

internal int dwInfoVersion;

 

[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources", Justification = "The handle is not owned by the value type")]

internal IntPtr pbNonce;            // byte *

internal int cbNonce;

 

[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources", Justification = "The handle is not owned by the value type")]

internal IntPtr pbAuthData;         // byte *

internal int cbAuthData;

 

[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources", Justification = "The handle is not owned by the value type")]

internal IntPtr pbTag;              // byte *

internal int cbTag;

 

[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources", Justification = "The handle is not owned by the value type")]

internal IntPtr pbMacContext;       // byte *

internal int cbMacContext;

 

internal int cbAAD;

internal long cbData;

internal AuthenticatedCipherModeInfoFlags dwFlags;

}

 

[StructLayout(LayoutKind.Sequential)]

internal struct BCRYPT_KEY_DATA_BLOB

{

internal KeyBlobMagicNumber dwMagic;

internal int dwVersion;

internal int cbKeyData;

}

 

[StructLayout(LayoutKind.Sequential)]

internal struct BCRYPT_KEY_LENGTHS_STRUCT

{

internal int dwMinLength;

internal int dwMaxLength;

internal int dwIncrement;

}

 

[StructLayout(LayoutKind.Sequential)]

internal struct BCRYPT_OAEP_PADDING_INFO

{

[MarshalAs(UnmanagedType.LPWStr)]

internal string pszAlgId;

 

internal IntPtr pbLabel;

 

internal int cbLabel;

}

 

[StructLayout(LayoutKind.Sequential)]

internal struct BCRYPT_PKCS1_PADDING_INFO

{

[MarshalAs(UnmanagedType.LPWStr)]

internal string pszAlgId;

}

 

[StructLayout(LayoutKind.Sequential)]

internal struct BCRYPT_PSS_PADDING_INFO

{

[MarshalAs(UnmanagedType.LPWStr)]

internal string pszAlgId;

 

internal int cbSalt;

}

 

[StructLayout(LayoutKind.Sequential)]

internal struct BCRYPT_RSAKEY_BLOB

{

internal KeyBlobMagicNumber Magic;

internal int BitLength;

internal int cbPublicExp;

internal int cbModulus;

internal int cbPrime1;

internal int cbPrime2;

}

 

[StructLayout(LayoutKind.Sequential)]

internal struct BCryptBuffer

{

internal int cbBuffer;

internal int BufferType;

internal IntPtr pvBuffer;       // PVOID

}

 

[StructLayout(LayoutKind.Sequential)]

internal struct BCryptBufferDesc

{

internal int ulVersion;

internal int cBuffers;

internal IntPtr pBuffers;       // PBCryptBuffer

}

 

//

// P/Invokes

//

 

[SuppressUnmanagedCodeSecurity]

private static class UnsafeNativeMethods

{

[DllImport("bcrypt.dll")]

internal static extern ErrorCode BCryptCreateHash(SafeBCryptAlgorithmHandle hAlgorithm,

[Out] out SafeBCryptHashHandle hHash,

IntPtr pbHashObject,              // byte *

int cbHashObject,

[In, MarshalAs(UnmanagedType.LPArray)]byte[] pbSecret,

int cbSecret,

int dwFlags);

 

// Overload of BCryptDecrypt for use in standard decryption

[DllImport("bcrypt.dll")]

internal static extern ErrorCode BCryptDecrypt(SafeBCryptKeyHandle hKey,

[In, MarshalAs(UnmanagedType.LPArray)] byte[] pbInput,

int cbInput,

IntPtr pPaddingInfo,

[In, Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbIV,

int cbIV,

[Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbOutput,

int cbOutput,

[Out] out int pcbResult,

int dwFlags);

 

// Overload of BCryptDecrypt for use with authenticated decryption

[DllImport("bcrypt.dll")]

internal static extern ErrorCode BCryptDecrypt(SafeBCryptKeyHandle hKey,

[In, MarshalAs(UnmanagedType.LPArray)] byte[] pbInput,

int cbInput,

[In, Out] ref BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO pPaddingInfo,

[In, Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbIV,

int cbIV,

[Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbOutput,

int cbOutput,

[Out] out int pcbResult,

int dwFlags);

 

// Overload of BCryptEncrypt for use in standard encryption

[DllImport("bcrypt.dll")]

internal static extern ErrorCode BCryptEncrypt(SafeBCryptKeyHandle hKey,

[In, MarshalAs(UnmanagedType.LPArray)] byte[] pbInput,

int cbInput,

IntPtr pPaddingInfo,

[In, Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbIV,

int cbIV,

[Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbOutput,

int cbOutput,

[Out] out int pcbResult,

int dwFlags);

 

// Overload of BCryptEncrypt for use with authenticated encryption

[DllImport("bcrypt.dll")]

internal static extern ErrorCode BCryptEncrypt(SafeBCryptKeyHandle hKey,

[In, MarshalAs(UnmanagedType.LPArray)] byte[] pbInput,

int cbInput,

[In, Out] ref BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO pPaddingInfo,

[In, Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbIV,

int cbIV,

[Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbOutput,

int cbOutput,

[Out] out int pcbResult,

int dwFlags);

 

[DllImport("bcrypt.dll")]

internal static extern ErrorCode BCryptFinishHash(SafeBCryptHashHandle hHash,

[Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbOutput,

int cbOutput,

int dwFlags);

 

[DllImport("bcrypt.dll")]

internal static extern ErrorCode BCryptGenRandom(SafeBCryptAlgorithmHandle hAlgorithm,

[In, Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbBuffer,

int cbBuffer,

int dwFlags);

 

[DllImport("bcrypt.dll", EntryPoint = "BCryptGetProperty")]

internal static extern ErrorCode BCryptGetAlgorithmProperty(SafeBCryptAlgorithmHandle hObject,

[MarshalAs(UnmanagedType.LPWStr)] string pszProperty,

[In, Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbOutput,

int cbOutput,

[In, Out] ref int pcbResult,

int flags);

 

[DllImport("bcrypt.dll", EntryPoint = "BCryptGetProperty")]

internal static extern ErrorCode BCryptGetHashProperty(SafeBCryptHashHandle hObject,

[MarshalAs(UnmanagedType.LPWStr)] string pszProperty,

[In, Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbOutput,

int cbOutput,

[In, Out] ref int pcbResult,

int flags);

 

[DllImport("bcrypt.dll")]

internal static extern ErrorCode BCryptHashData(SafeBCryptHashHandle hHash,

[In, MarshalAs(UnmanagedType.LPArray)] byte[] pbInput,

int cbInput,

int dwFlags);

 

[DllImport("bcrypt.dll")]

internal static extern ErrorCode BCryptImportKey(SafeBCryptAlgorithmHandle hAlgorithm,

IntPtr hImportKey,

[MarshalAs(UnmanagedType.LPWStr)] string pszBlobType,

[Out] out SafeBCryptKeyHandle phKey,

[In, Out] IntPtr pbKeyObject,          // BYTE *

int cbKeyObject,

[In, MarshalAs(UnmanagedType.LPArray)] byte[] pbInput,

int cbInput,

int dwFlags);

 

[DllImport("bcrypt.dll")]

internal static extern ErrorCode BCryptOpenAlgorithmProvider([Out] out SafeBCryptAlgorithmHandle phAlgorithm,

[MarshalAs(UnmanagedType.LPWStr)] string pszAlgId,

[MarshalAs(UnmanagedType.LPWStr)] string pszImplementation,

AlgorithmProviderOptions dwFlags);

 

[DllImport("bcrypt.dll", EntryPoint = "BCryptSetProperty")]

internal static extern ErrorCode BCryptSetAlgorithmProperty(SafeBCryptAlgorithmHandle hObject,

[MarshalAs(UnmanagedType.LPWStr)] string pszProperty,

[In, MarshalAs(UnmanagedType.LPArray)] byte[] pbInput,

int cbInput,

int dwFlags);

 

[DllImport("bcrypt.dll", EntryPoint = "BCryptSetProperty")]

internal static extern ErrorCode BCryptSetHashProperty(SafeBCryptHashHandle hObject,

[MarshalAs(UnmanagedType.LPWStr)] string pszProperty,

[In, MarshalAs(UnmanagedType.LPArray)] byte[] pbInput,

int cbInput,

int dwFlags);

 

[DllImport("bcrypt.dll", EntryPoint = "BCryptKeyDerivation")]

internal static extern ErrorCode BCryptKeyDerivation(SafeBCryptKeyHandle hKey,

[In] ref BCryptBufferDesc pParamList,

[Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbDerivedKey,

int cbDerivedKey,

[In, Out] ref int pcbResult,

int dwFlags);

 

[DllImport("bcrypt.dll", EntryPoint = "BCryptGenerateSymmetricKey")]

internal static extern ErrorCode BCryptGenerateSymmetricKey(SafeBCryptAlgorithmHandle hAlgorithm,

[Out] out SafeBCryptKeyHandle phKey,

[Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbKeyObjectOptional,

int cbKeyObject,

[Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbSecret,

int cbSecret,

int dwFlags);

 

[DllImport("bcrypt.dll", EntryPoint = "BCryptDeriveKeyPBKDF2")]

internal static extern ErrorCode BCryptDeriveKeyPBKDF2(SafeBCryptAlgorithmHandle hPrf,

[In, MarshalAs(UnmanagedType.LPArray)] byte[] pbPassword,

int cbPassword,

[In, MarshalAs(UnmanagedType.LPArray)] byte[] pbSalt,

int cbSalt,

ulong cIterations,

[Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbDerivedKey,

int cbDerivedKey,

int dwFlags);

 

}

 

/// <summary>

///     Adapter to wrap specific BCryptGetProperty P/Invokes with a generic BCrypt handle type

/// </summary>

[SecurityCritical]

private delegate ErrorCode BCryptPropertyGetter<T>(T hObject,

string pszProperty,

byte[] pbOutput,

int cbOutput,

ref int pcbResult,

int dwFlags) where T : SafeHandle;

 

/// <summary>

///     Adapter to wrap specific BCryptSetProperty P/Invokes with a generic BCrypt handle type

/// </summary>

[SecurityCritical]

private delegate ErrorCode BCryptPropertySetter<T>(T hObject,

string pszProperty,

byte[] pbInput,

int cbInput,

int dwFlags) where T : SafeHandle;

 

//

// Wrapper APIs

//

 

[SecurityCritical]

internal static SafeBCryptHashHandle CreateHash(SafeBCryptAlgorithmHandle algorithm,

byte[] secret)

{

Debug.Assert(algorithm != null, "algorithm != null");

Debug.Assert(!algorithm.IsClosed && !algorithm.IsInvalid, "!algorithm.IsClosed && !algorithm.IsInvalid");

 

IntPtr hashObject = IntPtr.Zero;

SafeBCryptHashHandle hash = null;

 

RuntimeHelpers.PrepareConstrainedRegions();

try

{

// Figure out how big of a buffer is needed for the hash object and allocate it

int hashObjectSize = GetInt32Property(algorithm, ObjectPropertyName.ObjectLength);

 

RuntimeHelpers.PrepareConstrainedRegions();

try { }

finally

{

hashObject = Marshal.AllocCoTaskMem(hashObjectSize);

}

 

// Create the hash object

ErrorCode error = UnsafeNativeMethods.BCryptCreateHash(algorithm,

out hash,

hashObject,

hashObjectSize,

secret,

secret != null ? secret.Length : 0,

0);

if (error != ErrorCode.Success)

{

throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

}

 

// Transfer ownership of the buffer to the safe handle

hash.DataBuffer = hashObject;

 

return hash;

}

finally

{

// If the safe hash handle never took ownership of the data buffer, free it now.

if (hashObject != IntPtr.Zero)

{

if (hash == null || hash.DataBuffer == IntPtr.Zero)

{

Marshal.FreeCoTaskMem(hashObject);

}

}

}

}

 

/// <summary>

///     Get the results of a hashing operation

/// </summary>

[SecurityCritical]

internal static byte[] FinishHash(SafeBCryptHashHandle hash)

{

Debug.Assert(hash != null, "hash != null");

Debug.Assert(!hash.IsClosed && !hash.IsInvalid, "!hash.IsClosed && !hash.IsInvalid");

 

int hashSize = GetInt32Property(hash, HashPropertyName.HashLength);

byte[] result = new byte[hashSize];

 

ErrorCode error = UnsafeNativeMethods.BCryptFinishHash(hash, result, result.Length, 0);

if (error != ErrorCode.Success)

{

throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

}

 

return result;

}

 

/// <summary>

///     Fill a buffer with radom bytes

/// </summary>

[SecurityCritical]

[SecuritySafeCritical]

internal static void GenerateRandomBytes(SafeBCryptAlgorithmHandle algorithm, byte[] buffer)

{

Debug.Assert(algorithm != null, "algorithm != null");

Debug.Assert(!algorithm.IsClosed && !algorithm.IsInvalid, "!algorithm.IsClosed && !algorithm.IsInvalid");

Debug.Assert(buffer != null, "buffer != null");

 

ErrorCode error = UnsafeNativeMethods.BCryptGenRandom(algorithm,

buffer,

buffer.Length,

0);

if (error != ErrorCode.Success)

{

throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

}

}

 

/// <summary>

///     Get an integer valued named property from a BCrypt object.

/// </summary>

[SecurityCritical]

internal static int GetInt32Property<T>(T bcryptObject, string property) where T : SafeHandle

{

Debug.Assert(bcryptObject != null, "bcryptObject != null");

Debug.Assert(!bcryptObject.IsClosed && !bcryptObject.IsInvalid, "!bcryptObject.IsClosed && !bcryptObject.IsInvalid");

Debug.Assert(!String.IsNullOrEmpty(property), "!String.IsNullOrEmpty(property)");

 

return BitConverter.ToInt32(GetProperty(bcryptObject, property), 0);

}

 

/// <summary>

///     Get a string valued named property from a BCrypt object

/// </summary>

[SecurityCritical]

internal static string GetStringProperty<T>(T bcryptObject, string property) where T : SafeHandle

{

Debug.Assert(bcryptObject != null, "bcryptObject != null");

Debug.Assert(!bcryptObject.IsClosed && !bcryptObject.IsInvalid, "!bcryptObject.IsClosed && !bcryptObject.IsInvalid");

Debug.Assert(!String.IsNullOrEmpty(property), "!String.IsNullOrEmpty(property)");

 

byte[] rawProperty = GetProperty(bcryptObject, property);

 

if (rawProperty == null)

{

return null;

}

else if (rawProperty.Length == 0)

{

return string.Empty;

}

else

{

unsafe

{

fixed (byte *pPropertyBytes = rawProperty)

{

return Marshal.PtrToStringUni(new IntPtr(pPropertyBytes));

}

}

}

}

 

/// <summary>

///     Get a property from a BCrypt which is returned as a structure

/// </summary>

[SecurityCritical]

[SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Internal critical API")]

internal static TProperty GetValueTypeProperty<THandle, TProperty>(THandle bcryptObject, string property)

where THandle : SafeHandle

where TProperty : struct

{

Debug.Assert(bcryptObject != null, "bcryptObject != null");

Debug.Assert(!bcryptObject.IsClosed && !bcryptObject.IsInvalid, "!bcryptObject.IsClosed && !bcryptObject.IsInvalid");

Debug.Assert(!String.IsNullOrEmpty(property), "!String.IsNullOrEmpty(property)");

 

byte[] rawProperty = GetProperty(bcryptObject, property);

 

if (rawProperty == null || rawProperty.Length == 0)

{

return default(TProperty);

}

else

{

Debug.Assert(Marshal.SizeOf(typeof(TProperty)) <= rawProperty.Length, "Unexpected property size");

unsafe

{

fixed (byte* pPropertyBytes = rawProperty)

{

return (TProperty)Marshal.PtrToStructure(new IntPtr(pPropertyBytes), typeof(TProperty));

}

}

}

}

 

/// <summary>

///     Get the value of a named property from a BCrypt object

/// </summary>

[SecurityCritical]

internal static byte[] GetProperty<T>(T bcryptObject, string property) where T : SafeHandle

{

Debug.Assert(bcryptObject != null, "bcryptObject != null");

Debug.Assert(!bcryptObject.IsClosed && !bcryptObject.IsInvalid, "!bcryptObject.IsClosed && !bcryptObject.IsInvalid");

Debug.Assert(!String.IsNullOrEmpty(property), "!String.IsNullOrEmpty(property)");

 

// Figure out which P/Invoke to use for the specific SafeHandle type we were given. For now we

// only need to get properties of BCrypt algorithms, so we only check for SafeBCryptAlgorithmHandles.

BCryptPropertyGetter<T> propertyGetter = null;

if (typeof(T) == typeof(SafeBCryptAlgorithmHandle))

{

propertyGetter = new BCryptPropertyGetter<SafeBCryptAlgorithmHandle>(UnsafeNativeMethods.BCryptGetAlgorithmProperty) as BCryptPropertyGetter<T>;

}

else if (typeof(T) == typeof(SafeBCryptHashHandle))

{

propertyGetter = new BCryptPropertyGetter<SafeBCryptHashHandle>(UnsafeNativeMethods.BCryptGetHashProperty) as BCryptPropertyGetter<T>;

}

 

Debug.Assert(propertyGetter != null, "Unknown bcrypt object type");

 

// Figure out how big of a buffer is needed to hold the property

int propertySize = 0;

ErrorCode error = propertyGetter(bcryptObject, property, null, 0, ref propertySize, 0);

if (error != ErrorCode.Success && error != ErrorCode.BufferToSmall)

{

throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

}

 

// Get the property value

byte[] propertyValue = new byte[propertySize];

error = propertyGetter(bcryptObject,

property,

propertyValue,

propertyValue.Length,

ref propertySize,

0);

if (error != ErrorCode.Success)

{

throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

}

 

return propertyValue;

}

 

/// <summary>

///     Add some data to a hash in progress

/// </summary>

[SecurityCritical]

internal static void HashData(SafeBCryptHashHandle hash, byte[] data)

{

Debug.Assert(hash != null, "hash != null");

Debug.Assert(!hash.IsClosed && !hash.IsInvalid, "!hash.IsClosed && !hash.IsInvalid");

Debug.Assert(data != null, "data != null");

 

ErrorCode error = UnsafeNativeMethods.BCryptHashData(hash, data, data.Length, 0);

 

if (error != ErrorCode.Success)

{

throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

}

}

 

/// <summary>

///     Import a raw symmetric key into a key handle

/// </summary>

[SecurityCritical]

internal static SafeBCryptKeyHandle ImportSymmetricKey(SafeBCryptAlgorithmHandle algorithm, byte[] key)

{

Debug.Assert(algorithm != null, "algorithm != null");

Debug.Assert(!algorithm.IsClosed && !algorithm.IsInvalid, "!algorithm.IsClosed && !algorithm.IsInvalid");

Debug.Assert(key != null, "buffer != null");

 

IntPtr keyDataBuffer = IntPtr.Zero;

SafeBCryptKeyHandle keyHandle = null;

 

RuntimeHelpers.PrepareConstrainedRegions();

try

{

// Build up the key blob structure in memory.  BCryptImportKey requries a

// BCRYPT_KEY_DATA_BLOB header immediately followed by the raw key data.

byte[] keyBlob = new byte[Marshal.SizeOf(typeof(BCRYPT_KEY_DATA_BLOB)) + key.Length];

unsafe

{

fixed (byte* pbKeyBlob = keyBlob)

{

BCRYPT_KEY_DATA_BLOB* pkeyDataBlob = (BCRYPT_KEY_DATA_BLOB*)pbKeyBlob;

pkeyDataBlob->dwMagic = KeyBlobMagicNumber.KeyDataBlob;

pkeyDataBlob->dwVersion = 1;

pkeyDataBlob->cbKeyData = key.Length;

}

}

Buffer.BlockCopy(key, 0, keyBlob, Marshal.SizeOf(typeof(BCRYPT_KEY_DATA_BLOB)), key.Length);

 

// Figure out how big of a key data buffer we need and allocate space on the native heap for

// it.  We cannot use a managed array here because the address needs to stay constant for

// the lifetime of the algorithm handle.  Pinning for a potentially long lifetime is

// undesirable, so we use a native heap allocation instead.

int keyDataSize = GetInt32Property(algorithm, ObjectPropertyName.ObjectLength);

 

RuntimeHelpers.PrepareConstrainedRegions();

try { }

finally

{

keyDataBuffer = Marshal.AllocCoTaskMem(keyDataSize);

}

 

// Import the key

ErrorCode error = UnsafeNativeMethods.BCryptImportKey(algorithm,

IntPtr.Zero,

KeyBlobType.KeyDataBlob,

out keyHandle,

keyDataBuffer,

keyDataSize,

keyBlob,

keyBlob.Length,

0);

if (error != ErrorCode.Success)

{

throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

}

 

// Give the key ownership of the key data buffer

keyHandle.DataBuffer = keyDataBuffer;

 

return keyHandle;

}

finally

{

// If we allocated a key data buffer, but never transfered ownership to the key handle, then

// we need to free it now otherwise it will leak.

if (keyDataBuffer != IntPtr.Zero)

{

if (keyHandle == null ||keyHandle.DataBuffer == IntPtr.Zero)

{

Marshal.FreeCoTaskMem(keyDataBuffer);

}

}

}

}

 

/// <summary>

///     Initialize a BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO structure (in place of the

///     BCRYPT_INIT_AUTH_MODE_INFO macro)

/// </summary>

[SecurityCritical]

[SecuritySafeCritical]

internal static void InitializeAuthnenticatedCipherModeInfo(ref BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo)

{

authInfo.cbSize = Marshal.SizeOf(typeof(BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO));

authInfo.dwInfoVersion = 1; // BCRYPT_INIT_AUTH_MODE_INFO_VERSION

}

 

/// <summary>

///     Map a managed cipher mode to a BCrypt chaining mode

/// </summary>

internal static string MapChainingMode(CipherMode mode)

{

switch (mode)

{

case CipherMode.CBC:

return ChainingMode.Cbc;

case CipherMode.CFB:

return ChainingMode.Cfb;

case CipherMode.ECB:

return ChainingMode.Ecb;

default:

throw new ArgumentException(Properties.Resources.UnsupportedCipherMode, "mode");

}

}

 

/// <summary>

///     Map a BCrypt chaining mode to a managed cipher mode

/// </summary>

internal static CipherMode MapChainingMode(string mode)

{

Debug.Assert(mode != null, "mode != null");

 

if (String.Equals(mode, ChainingMode.Cbc, StringComparison.Ordinal))

{

return CipherMode.CBC;

}

else if (String.Equals(mode, ChainingMode.Cfb, StringComparison.Ordinal))

{

return CipherMode.CFB;

}

else if (String.Equals(mode, ChainingMode.Ecb, StringComparison.Ordinal))

{

return CipherMode.ECB;

}

else

{

throw new ArgumentException(Properties.Resources.UnsupportedCipherMode, "mode");

}

}

 

/// <summary>

///     Open a handle to a BCrypt algorithm provider

/// </summary>

[SecurityCritical]

internal static SafeBCryptAlgorithmHandle OpenAlgorithm(string algorithm, string implementation)

{

return OpenAlgorithm(algorithm, implementation, AlgorithmProviderOptions.None);

}

 

[SecurityCritical]

internal static SafeBCryptAlgorithmHandle OpenAlgorithm(string algorithm,

string implementation,

AlgorithmProviderOptions options)

{

Debug.Assert(!String.IsNullOrEmpty(algorithm), "!String.IsNullOrEmpty(algorithm)");

// Note that implementation may be NULL (in which case the default provider will be used)

 

SafeBCryptAlgorithmHandle algorithmHandle = null;

ErrorCode error = UnsafeNativeMethods.BCryptOpenAlgorithmProvider(out algorithmHandle,

algorithm,

implementation,

options);

if (error != ErrorCode.Success)

{

throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

}

 

return algorithmHandle;

}

 

/// <summary>

///     Set an integer valued property on a BCrypt object

/// </summary>

[SecurityCritical]

internal static void SetInt32Property<T>(T bcryptObject, string property, int value) where T : SafeHandle

{

Debug.Assert(bcryptObject != null, "bcryptObject != null");

Debug.Assert(!bcryptObject.IsClosed && !bcryptObject.IsInvalid, "!bcryptObject.IsClosed && !bcryptObject.IsInvalid");

Debug.Assert(!String.IsNullOrEmpty(property), "!String.IsNullOrEmpty(property)");

 

SetProperty(bcryptObject, property, BitConverter.GetBytes(value));

}

 

/// <summary>

///     Set a string valued property on a BCrypt object

/// </summary>

[SecurityCritical]

internal static void SetStringProperty<T>(T bcryptObject, string property, string value) where T : SafeHandle

{

Debug.Assert(bcryptObject != null, "bcryptObject != null");

Debug.Assert(!bcryptObject.IsClosed && !bcryptObject.IsInvalid, "!bcryptObject.IsClosed && !bcryptObject.IsInvalid");

Debug.Assert(!String.IsNullOrEmpty(property), "!String.IsNullOrEmpty(property)");

Debug.Assert(value != null, "value != null");

 

SetProperty(bcryptObject, property, Encoding.Unicode.GetBytes(value));

}

 

/// <summary>

///     Set a named property value on a BCrypt object

/// </summary>

[SecurityCritical]

internal static void SetProperty<T>(T bcryptObject, string property, byte[] value) where T : SafeHandle

{

Debug.Assert(bcryptObject != null, "bcryptObject != null");

Debug.Assert(!bcryptObject.IsClosed && !bcryptObject.IsInvalid, "!bcryptObject.IsClosed && !bcryptObject.IsInvalid");

Debug.Assert(!String.IsNullOrEmpty(property), "!String.IsNullOrEmpty(property)");

Debug.Assert(value != null, "value != null");

 

// Figure out which P/Invoke to use for the specific handle type we were given. For now we

// only need to set properties of BCrypt algorithms, so we only check for SafeBCryptAlgorithmHandles.

BCryptPropertySetter<T> propertySetter = null;

if (typeof(T) == typeof(SafeBCryptAlgorithmHandle))

{

propertySetter = new BCryptPropertySetter<SafeBCryptAlgorithmHandle>(UnsafeNativeMethods.BCryptSetAlgorithmProperty) as BCryptPropertySetter<T>;

}

else if (typeof(T) == typeof(SafeBCryptHashHandle))

{

propertySetter = new BCryptPropertySetter<SafeBCryptHashHandle>(UnsafeNativeMethods.BCryptSetHashProperty) as BCryptPropertySetter<T>;

}

 

Debug.Assert(propertySetter != null, "Unknown object type");

 

// Set the property

ErrorCode error = propertySetter(bcryptObject,

property,

value,

value.Length,

0);

if (error != ErrorCode.Success)

{

throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

}

}

 

/// <summary>

///     Decrypt some blocks of data

/// </summary>

[SecurityCritical]

[SecuritySafeCritical]

internal static byte[] SymmetricDecrypt(SafeBCryptKeyHandle key, byte[] iv, byte[] input)

{

Debug.Assert(key != null, "key != null");

Debug.Assert(!key.IsClosed && !key.IsInvalid, "!key.IsClosed && !key.IsInvalid");

Debug.Assert(input != null, "input != null");

 

// Do the decryption

byte[] output = new byte[input.Length];

int outputSize = 0;

ErrorCode error = UnsafeNativeMethods.BCryptDecrypt(key,

input,

input.Length,

IntPtr.Zero,

iv,

iv != null ? iv.Length : 0,

output,

output.Length,

out outputSize,

0);

if (error != ErrorCode.Success)

{

throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

}

 

// If we didn't use the whole output array, trim down to the portion that was used

if (outputSize != output.Length)

{

byte[] trimmedOutput = new byte[outputSize];

Buffer.BlockCopy(output, 0, trimmedOutput, 0, trimmedOutput.Length);

output = trimmedOutput;

}

 

return output;

}

 

/// <summary>

///     Decrypt some blocks of data using authentication info

/// </summary>

[SecurityCritical]

internal static byte[] SymmetricDecrypt(SafeBCryptKeyHandle key,

byte[] input,

byte[] chainData,

ref BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authenticationInfo)

{

Debug.Assert(key != null, "key != null");

Debug.Assert(!key.IsClosed && !key.IsInvalid, "!key.IsClosed && !key.IsInvalid");

 

// Do the decryption

byte[] output = new byte[input != null ? input.Length : 0];

int outputSize = 0;

ErrorCode error = UnsafeNativeMethods.BCryptDecrypt(key,

input,

input != null ? input.Length : 0,

ref authenticationInfo,

chainData,

chainData != null ? chainData.Length : 0,

output,

output.Length,

out outputSize,

0);

if (error != ErrorCode.Success)

{

throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

}

 

// If we didn't use the whole output array, trim down to the portion that was used

if (outputSize != output.Length)

{

byte[] trimmedOutput = new byte[outputSize];

Buffer.BlockCopy(output, 0, trimmedOutput, 0, trimmedOutput.Length);

output = trimmedOutput;

}

 

return output;

}

 

/// <summary>

///     Encrypt some blocks of data

/// </summary>

[SecurityCritical]

[SecuritySafeCritical]

internal static byte[] SymmetricEncrypt(SafeBCryptKeyHandle key, byte[] iv, byte[] input)

{

Debug.Assert(key != null, "key != null");

Debug.Assert(!key.IsClosed && !key.IsInvalid, "!key.IsClosed && !key.IsInvalid");

Debug.Assert(input != null, "input != null");

 

// Do the encryption

byte[] output = new byte[input.Length];

int outputSize = 0;

ErrorCode error = UnsafeNativeMethods.BCryptEncrypt(key,

input,

input != null ? input.Length : 0,

IntPtr.Zero,

iv,

iv != null ? iv.Length : 0,

output,

output.Length,

out outputSize,

0);

if (error != ErrorCode.Success)

{

throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

}

 

// If we didn't use the whole output array, trim down to the portion that was used

if (outputSize != output.Length)

{

byte[] trimmedOutput = new byte[outputSize];

Buffer.BlockCopy(output, 0, trimmedOutput, 0, trimmedOutput.Length);

output = trimmedOutput;

}

 

return output;

}

 

/// <summary>

///     Encrypt some blocks of data using authentication information

/// </summary>

[SecurityCritical]

[SecuritySafeCritical]

internal static byte[] SymmetricEncrypt(SafeBCryptKeyHandle key,

byte[] input,

byte[] chainData,

ref BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authenticationInfo)

{

Debug.Assert(key != null, "key != null");

Debug.Assert(!key.IsClosed && !key.IsInvalid, "!key.IsClosed && !key.IsInvalid");

 

// Do the encryption

byte[] output = new byte[input != null ? input.Length : 0];

int outputSize = 0;

ErrorCode error = UnsafeNativeMethods.BCryptEncrypt(key,

input,

input != null ? input.Length : 0,

ref authenticationInfo,

chainData,

chainData != null ? chainData.Length : 0,

output,

output.Length,

out outputSize,

0);

if (error != ErrorCode.Success)

{

throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

}

 

// If we didn't use the whole output array, trim down to the portion that was used

if (outputSize != output.Length)

{

byte[] trimmedOutput = new byte[outputSize];

Buffer.BlockCopy(output, 0, trimmedOutput, 0, trimmedOutput.Length);

output = trimmedOutput;

}

 

return output;

}

 

/// <summary>

///     Calls PBKDF2 via the <c>BCryptKeyDerivation</c> API.  <param name="hashName"/> specifies which of HMAC-SHA256, HMAC-SHA384 or HMAC-SHA512 to use.

///     See the <see cref="AlgorithmName"/> class for supported hash functions.  <param name="password"/> is the password, and <param name="salt"/>

///     is the salt and <param name="iterations"/> is the iteration count.

/// </summary>

[SecurityCritical]

[SecuritySafeCritical]

internal static byte[] PBKDF2BCryptKeyDerivation(string hashName,

byte[] password,

byte[] salt,

ulong iterations)

{

// Open a hash object to get the digest length

SafeBCryptAlgorithmHandle hPrf = OpenAlgorithm(hashName, null);

int hashLength = GetInt32Property(hPrf, HashPropertyName.HashLength);

hPrf.Close();

 

// Create a "key" object from the password

SafeBCryptAlgorithmHandle hPbkdf2 = OpenAlgorithm(AlgorithmName.Pbkdf2, null);

 

SafeBCryptKeyHandle hKey = new SafeBCryptKeyHandle();

ErrorCode error = UnsafeNativeMethods.BCryptGenerateSymmetricKey(hPbkdf2, out hKey, null, 0, password, password.Length, 0);

if (error != ErrorCode.Success)

{

throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

}

hPbkdf2.Close();

 

// Prepare the param buffer

BCryptBuffer[] buffer = new BCryptBuffer[3];

buffer[0].BufferType = (int) ParameterTypes.KdfHashAlgorithm;

buffer[0].cbBuffer = hashName.Length*2;                 // *2 since a WCHAR is 2-bytes

buffer[0].pvBuffer = Marshal.StringToCoTaskMemUni(hashName);

 

buffer[1].BufferType = (int) ParameterTypes.KdfSalt;

buffer[1].cbBuffer = salt.Length;

buffer[1].pvBuffer = Marshal.AllocCoTaskMem(salt.Length);

Marshal.Copy(salt, 0, buffer[1].pvBuffer, salt.Length);

 

buffer[2].BufferType = (int) ParameterTypes.KdfIterationCount;

buffer[2].cbBuffer = sizeof(ulong);

buffer[2].pvBuffer = Marshal.AllocCoTaskMem(buffer[2].cbBuffer);

Marshal.Copy(BitConverter.GetBytes(iterations), 0, buffer[2].pvBuffer, buffer[2].cbBuffer);

 

BCryptBufferDesc pParamList = new BCryptBufferDesc();

pParamList.ulVersion = 0;       //BCRYPTBUFFER_VERSION

pParamList.cBuffers = 3;

GCHandle gch = GCHandle.Alloc(buffer, GCHandleType.Pinned);

pParamList.pBuffers = gch.AddrOfPinnedObject();

 

// Derive the key

byte[] derivedKey = new byte[hashLength];

int pcbResult = 0;

error = UnsafeNativeMethods.BCryptKeyDerivation(hKey, ref pParamList, derivedKey, derivedKey.Length, ref pcbResult, 0);

if (error != ErrorCode.Success)

{

throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

}

if(pcbResult != hashLength)

{

throw new CryptographicException("Invalid result from BCryptKeyDerivation (PBKDF2).  Derived key is too short.");

}

 

hKey.Close();

Marshal.FreeCoTaskMem(buffer[0].pvBuffer);

Marshal.FreeCoTaskMem(buffer[1].pvBuffer);

Marshal.FreeCoTaskMem(buffer[2].pvBuffer);

gch.Free();

 

return derivedKey;

}

 

 

 

/// <summary>

///     Call PBKDF2 via the BCryptDeriveKeyPBKDF2 API. <param name="hashName"/> specifies which of HMAC-SHA256, HMAC-SHA384 or HMAC-SHA512 to use

///     See the <see cref="AlgorithmName"/> class for supported hash functions. <param name="password"/> is the password, and <param name="salt"/>

///     is the salt and <param name="iterations"/> is the iteration count.

/// </summary>

[SecurityCritical]

[SecurityTreatAsSafe]

internal static byte[] PBKDF2BCryptDeriveKeyPBKDF2(string hashName,

byte[] password,

byte[] salt,

ulong iterations)

{

// Open handle for HMAC-hashName, with the default algorithm provider

SafeBCryptAlgorithmHandle hPrf = OpenAlgorithm(hashName, null, AlgorithmProviderOptions.HmacAlgorithm);

 

// Get the hash length

int hashLength = GetInt32Property(hPrf, HashPropertyName.HashLength);

byte[] derivedKey = new byte[hashLength];

 

ErrorCode error = UnsafeNativeMethods.BCryptDeriveKeyPBKDF2(hPrf, password, password.Length, salt, salt.Length, iterations, derivedKey, derivedKey.Length, 0);

if (error != ErrorCode.Success)

{

throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

}

 

hPrf.Close();

 

return derivedKey;

}

 

 

}       // end class BCryptNative

 

/// <summary>

///     SafeHandle for a native BCRYPT_ALG_HANDLE

/// </summary>

internal sealed class SafeBCryptAlgorithmHandle : SafeHandleZeroOrMinusOneIsInvalid

{

private SafeBCryptAlgorithmHandle() : base(true)

{

return;

}

 

[DllImport("bcrypt.dll")]

[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]

[SuppressUnmanagedCodeSecurity]

[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "SafeHandle release P/Invoke")]

private static extern BCryptNative.ErrorCode BCryptCloseAlgorithmProvider(IntPtr hAlgorithm, int flags);

 

protected override bool ReleaseHandle()

{

return BCryptCloseAlgorithmProvider(handle, 0) == BCryptNative.ErrorCode.Success;

}

}

 

/// <summary>

///     SafeHandle for a BCRYPT_HASH_HANDLE.

/// </summary>

internal sealed class SafeBCryptHashHandle : SafeHandleWithBuffer

{

[DllImport("bcrypt.dll")]

[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]

[SuppressUnmanagedCodeSecurity]

[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "SafeHandle release P/Invoke")]

private static extern BCryptNative.ErrorCode BCryptDestroyHash(IntPtr hHash);

 

[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]

protected override bool ReleaseNativeHandle()

{

return BCryptDestroyHash(handle) == BCryptNative.ErrorCode.Success;

}

}

 

/// <summary>

///     SafeHandle for a native BCRYPT_KEY_HANDLE.

/// </summary>

internal sealed class SafeBCryptKeyHandle : SafeHandleWithBuffer

{

[DllImport("bcrypt.dll")]

[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]

[SuppressUnmanagedCodeSecurity]

[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "SafeHandle release P/Invoke")]

private static extern BCryptNative.ErrorCode BCryptDestroyKey(IntPtr hKey);

 

[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]

protected override bool ReleaseNativeHandle()

{

return BCryptDestroyKey(handle) == BCryptNative.ErrorCode.Success;

}

}

}

 

More Info:

+ Presently there are no plans as of .NET 4.7 to implement PBKDF2 with SHA256 or above.

+ Having said this, there are proposals that in future versions of .NET framework, SHA256 & above implementations can be done.

Microsoft.AspNetCore has implementations of PBKDF2. Please see https://github.com/aspnet/DataProtection/tree/dev/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2 for a reference.

I built a sample PbkDf2 class that has the implementations of: HMACSHA1, HMACSHA256 & HMACSHA512.

 

using System;

using System.Diagnostics;

using System.Security.Cryptography;

using System.Text;

 

namespace PBKDF2

{

public enum KeyDerivationPrf { HMACSHA1, HMACSHA256, HMACSHA512 };

internal interface IPbkdf2Provider

{

byte[] DeriveKey(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested);

}

 

internal sealed class ManagedPbkdf2Provider : IPbkdf2Provider

{

public byte[] DeriveKey(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested)

{

Debug.Assert(password != null);

Debug.Assert(salt != null);

Debug.Assert(iterationCount > 0);

Debug.Assert(numBytesRequested > 0);

 

// PBKDF2 is defined in NIST SP800-132, Sec. 5.3.

// csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf

 

byte[] retVal = new byte[numBytesRequested];

int numBytesWritten = 0;

int numBytesRemaining = numBytesRequested;

 

// For each block index, U_0 := Salt || block_index

byte[] saltWithBlockIndex = new byte[checked(salt.Length + sizeof(uint))];

Buffer.BlockCopy(salt, 0, saltWithBlockIndex, 0, salt.Length);

 

using (var hashAlgorithm = PrfToManagedHmacAlgorithm(prf, password))

{

for (uint blockIndex = 1; numBytesRemaining > 0; blockIndex++)

{

// write the block index out as big-endian

saltWithBlockIndex[saltWithBlockIndex.Length - 4] = (byte)(blockIndex >> 24);

saltWithBlockIndex[saltWithBlockIndex.Length - 3] = (byte)(blockIndex >> 16);

saltWithBlockIndex[saltWithBlockIndex.Length - 2] = (byte)(blockIndex >> 8);

saltWithBlockIndex[saltWithBlockIndex.Length - 1] = (byte)blockIndex;

 

// U_1 = PRF(U_0) = PRF(Salt || block_index)

// T_blockIndex = U_1

byte[] U_iter = hashAlgorithm.ComputeHash(saltWithBlockIndex); // this is U_1

byte[] T_blockIndex = U_iter;

 

for (int iter = 1; iter < iterationCount; iter++)

{

U_iter = hashAlgorithm.ComputeHash(U_iter);

XorBuffers(src: U_iter, dest: T_blockIndex);

// At this point, the 'U_iter' variable actually contains U_{iter+1} (due to indexing differences).

}

 

// At this point, we're done iterating on this block, so copy the transformed block into retVal.

int numBytesToCopy = Math.Min(numBytesRemaining, T_blockIndex.Length);

Buffer.BlockCopy(T_blockIndex, 0, retVal, numBytesWritten, numBytesToCopy);

numBytesWritten += numBytesToCopy;

numBytesRemaining -= numBytesToCopy;

}

}

 

// retVal := T_1 || T_2 || ... || T_n, where T_n may be truncated to meet the desired output length

return retVal;

}

 

private static KeyedHashAlgorithm PrfToManagedHmacAlgorithm(KeyDerivationPrf prf, string password)

{

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

try

{

switch (prf)

{

case KeyDerivationPrf.HMACSHA1:

return new HMACSHA1(passwordBytes);

case KeyDerivationPrf.HMACSHA256:

return new HMACSHA256(passwordBytes);

case KeyDerivationPrf.HMACSHA512:

return new HMACSHA512(passwordBytes);

default:

throw new CryptographicException("Unrecognized PRF.");

}

}

finally

{

// The HMAC ctor makes a duplicate of this key; we clear original buffer to limit exposure to the GC.

Array.Clear(passwordBytes, 0, passwordBytes.Length);

}

}

 

private static void XorBuffers(byte[] src, byte[] dest)

{

// Note: dest buffer is mutated.

Debug.Assert(src.Length == dest.Length);

for (int i = 0; i < src.Length; i++)

{

dest[i] ^= src[i];

}

}

}

}