From adf2adabc00e1ee00b733b7651ec2f45eb3f361d Mon Sep 17 00:00:00 2001 From: Levi Broderick Date: Thu, 6 Mar 2014 19:42:00 -0800 Subject: [PATCH] Add PBKDF2 support to the data protection library. --- .../Algorithms.cs | 20 +++--- .../CryptRand.cs | 25 +++++++ .../PBKDF2.cs | 65 +++++++++++++++++++ .../UnsafeNativeMethods.cs | 13 ++++ 4 files changed, 112 insertions(+), 11 deletions(-) create mode 100644 src/Microsoft.AspNet.Security.DataProtection/CryptRand.cs create mode 100644 src/Microsoft.AspNet.Security.DataProtection/PBKDF2.cs diff --git a/src/Microsoft.AspNet.Security.DataProtection/Algorithms.cs b/src/Microsoft.AspNet.Security.DataProtection/Algorithms.cs index 58985150bb..0af410cea5 100644 --- a/src/Microsoft.AspNet.Security.DataProtection/Algorithms.cs +++ b/src/Microsoft.AspNet.Security.DataProtection/Algorithms.cs @@ -33,11 +33,10 @@ namespace Microsoft.AspNet.Security.DataProtection return algHandle; } - private static BCryptAlgorithmHandle CreateHMACSHA256AlgorithmHandle() + internal static BCryptAlgorithmHandle CreateGenericHMACHandleFromPrimitiveProvider(string algorithmName) { - // create the HMACSHA-256 instance BCryptAlgorithmHandle algHandle; - int status = UnsafeNativeMethods.BCryptOpenAlgorithmProvider(out algHandle, Constants.BCRYPT_SHA256_ALGORITHM, Constants.MS_PRIMITIVE_PROVIDER, dwFlags: BCryptAlgorithmFlags.BCRYPT_ALG_HANDLE_HMAC_FLAG); + int status = UnsafeNativeMethods.BCryptOpenAlgorithmProvider(out algHandle, algorithmName, Constants.MS_PRIMITIVE_PROVIDER, dwFlags: BCryptAlgorithmFlags.BCRYPT_ALG_HANDLE_HMAC_FLAG); if (status != 0 || algHandle == null || algHandle.IsInvalid) { throw new CryptographicException(status); @@ -46,17 +45,16 @@ namespace Microsoft.AspNet.Security.DataProtection return algHandle; } + private static BCryptAlgorithmHandle CreateHMACSHA256AlgorithmHandle() + { + // create the HMACSHA-256 instance + return CreateGenericHMACHandleFromPrimitiveProvider(Constants.BCRYPT_SHA256_ALGORITHM); + } + private static BCryptAlgorithmHandle CreateHMACSHA512AlgorithmHandle() { // create the HMACSHA-512 instance - BCryptAlgorithmHandle algHandle; - int status = UnsafeNativeMethods.BCryptOpenAlgorithmProvider(out algHandle, Constants.BCRYPT_SHA512_ALGORITHM, Constants.MS_PRIMITIVE_PROVIDER, dwFlags: BCryptAlgorithmFlags.BCRYPT_ALG_HANDLE_HMAC_FLAG); - if (status != 0 || algHandle == null || algHandle.IsInvalid) - { - throw new CryptographicException(status); - } - - return algHandle; + return CreateGenericHMACHandleFromPrimitiveProvider(Constants.BCRYPT_SHA512_ALGORITHM); } private static BCryptAlgorithmHandle CreateSP800108AlgorithmHandle() diff --git a/src/Microsoft.AspNet.Security.DataProtection/CryptRand.cs b/src/Microsoft.AspNet.Security.DataProtection/CryptRand.cs new file mode 100644 index 0000000000..d6653ac611 --- /dev/null +++ b/src/Microsoft.AspNet.Security.DataProtection/CryptRand.cs @@ -0,0 +1,25 @@ +using System; + +namespace Microsoft.AspNet.Security.DataProtection +{ + /// + /// Helper class to populate buffers with cryptographically random data. + /// + public static class CryptRand + { + /// + /// Populates a buffer with cryptographically random data. + /// + /// The buffer to populate. + public static unsafe void FillBuffer(ArraySegment buffer) + { + // the ArraySegment<> ctor performs bounds checking + var unused = new ArraySegment(buffer.Array, buffer.Offset, buffer.Count); + + fixed (byte* pBuffer = &buffer.Array[buffer.Offset]) + { + BCryptUtil.GenRandom(pBuffer, buffer.Count); + } + } + } +} diff --git a/src/Microsoft.AspNet.Security.DataProtection/PBKDF2.cs b/src/Microsoft.AspNet.Security.DataProtection/PBKDF2.cs new file mode 100644 index 0000000000..f027571411 --- /dev/null +++ b/src/Microsoft.AspNet.Security.DataProtection/PBKDF2.cs @@ -0,0 +1,65 @@ +using System; +using System.Security.Cryptography; + +namespace Microsoft.AspNet.Security.DataProtection +{ + /// + /// Helper class to derive keys from low-entropy passwords using the PBKDF2 algorithm. + /// + public static class PBKDF2 + { + /// + /// Derives a key from a low-entropy password. + /// + /// The name of the PRF to use for key derivation. + /// The low-entropy password from which to generate a key. + /// The salt used to randomize the key derivation. + /// The number of iterations to perform. + /// The desired byte length of the derived key. + /// A key derived from the provided password. + /// For compatibility with the Rfc2898DeriveBytes class, specify "SHA1" for the algorithmName parameter. + public unsafe static byte[] DeriveKey(string algorithmName, byte[] password, byte[] salt, ulong iterationCount, uint numBytesToDerive) + { + if (String.IsNullOrEmpty(algorithmName)) + { + throw new ArgumentNullException("algorithmName"); + } + if (password == null || password.Length == 0) + { + throw new ArgumentNullException("password"); + } + if (salt == null || salt.Length == 0) + { + throw new ArgumentNullException("salt"); + } + if (iterationCount <= 0) + { + throw new ArgumentOutOfRangeException("iterationCount"); + } + + byte[] derivedKey = new byte[numBytesToDerive]; + int status; + + using (BCryptAlgorithmHandle algHandle = Algorithms.CreateGenericHMACHandleFromPrimitiveProvider(algorithmName)) + { + fixed (byte* pPassword = password) + fixed (byte* pSalt = salt) + fixed (byte* pDerivedKey = derivedKey) + { + status = UnsafeNativeMethods.BCryptDeriveKeyPBKDF2( + algHandle, pPassword, (uint)password.Length, pSalt, (uint)salt.Length, iterationCount, + pDerivedKey, numBytesToDerive, dwFlags: 0); + } + } + + if (status == 0 /* STATUS_SUCCESS */) + { + return derivedKey; + } + else + { + throw new CryptographicException(status); + } + } + } +} diff --git a/src/Microsoft.AspNet.Security.DataProtection/UnsafeNativeMethods.cs b/src/Microsoft.AspNet.Security.DataProtection/UnsafeNativeMethods.cs index 5e3cea28ec..611d1d4084 100644 --- a/src/Microsoft.AspNet.Security.DataProtection/UnsafeNativeMethods.cs +++ b/src/Microsoft.AspNet.Security.DataProtection/UnsafeNativeMethods.cs @@ -52,6 +52,19 @@ namespace Microsoft.AspNet.Security.DataProtection [Out] out uint pcbResult, [In] BCryptEncryptFlags dwFlags); + [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)] + // http://msdn.microsoft.com/en-us/library/windows/desktop/dd433795(v=vs.85).aspx + internal static extern int BCryptDeriveKeyPBKDF2( + [In] BCryptAlgorithmHandle hPrf, + [In] byte* pbPassword, + [In] uint cbPassword, + [In] byte* pbSalt, + [In] uint cbSalt, + [In] ulong cIterations, + [In] byte* pbDerivedKey, + [In] uint cbDerivedKey, + [In] uint dwFlags); + [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)] // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375399(v=vs.85).aspx internal static extern int BCryptDestroyHash(