Win8Pbkdf2Provider.cs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. // Copyright (c) .NET Foundation. All rights reserved.
  2. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  3. using System;
  4. using System.Diagnostics;
  5. using System.Runtime.CompilerServices;
  6. using System.Text;
  7. using Microsoft.AspNetCore.Cryptography.Cng;
  8. using Microsoft.AspNetCore.Cryptography.SafeHandles;
  9. namespace Microsoft.AspNetCore.Cryptography.KeyDerivation.PBKDF2
  10. {
  11. /// <summary>
  12. /// A PBKDF2 provider which utilizes the Win8 API BCryptKeyDerivation.
  13. /// </summary>
  14. internal sealed unsafe class Win8Pbkdf2Provider : IPbkdf2Provider
  15. {
  16. public byte[] DeriveKey(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested)
  17. {
  18. Debug.Assert(password != null);
  19. Debug.Assert(salt != null);
  20. Debug.Assert(iterationCount > 0);
  21. Debug.Assert(numBytesRequested > 0);
  22. string algorithmName = PrfToCngAlgorithmId(prf);
  23. fixed (byte* pbHeapAllocatedSalt = salt)
  24. {
  25. byte dummy; // CLR doesn't like pinning zero-length buffers, so this provides a valid memory address when working with zero-length buffers
  26. byte* pbSalt = (pbHeapAllocatedSalt != null) ? pbHeapAllocatedSalt : &dummy;
  27. byte[] retVal = new byte[numBytesRequested];
  28. using (BCryptKeyHandle keyHandle = PasswordToPbkdfKeyHandle(password, CachedAlgorithmHandles.PBKDF2, prf))
  29. {
  30. fixed (byte* pbRetVal = retVal)
  31. {
  32. DeriveKeyCore(keyHandle, algorithmName, pbSalt, (uint)salt.Length, (ulong)iterationCount, pbRetVal, (uint)retVal.Length);
  33. }
  34. return retVal;
  35. }
  36. }
  37. }
  38. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  39. public static uint GetTotalByteLengthIncludingNullTerminator(string input)
  40. {
  41. if (input == null)
  42. {
  43. // degenerate case
  44. return 0;
  45. }
  46. else
  47. {
  48. uint numChars = (uint)input.Length + 1U; // no overflow check necessary since Length is signed
  49. return checked(numChars * sizeof(char));
  50. }
  51. }
  52. private static BCryptKeyHandle PasswordToPbkdfKeyHandle(string password, BCryptAlgorithmHandle pbkdf2AlgHandle, KeyDerivationPrf prf)
  53. {
  54. byte dummy; // CLR doesn't like pinning zero-length buffers, so this provides a valid memory address when working with zero-length buffers
  55. // Convert password string to bytes.
  56. // Allocate on the stack whenever we can to save allocations.
  57. int cbPasswordBuffer = Encoding.UTF8.GetMaxByteCount(password.Length);
  58. fixed (byte* pbHeapAllocatedPasswordBuffer = (cbPasswordBuffer > Constants.MAX_STACKALLOC_BYTES) ? new byte[cbPasswordBuffer] : null)
  59. {
  60. byte* pbPasswordBuffer = pbHeapAllocatedPasswordBuffer;
  61. if (pbPasswordBuffer == null)
  62. {
  63. if (cbPasswordBuffer == 0)
  64. {
  65. pbPasswordBuffer = &dummy;
  66. }
  67. else
  68. {
  69. byte* pbStackAllocPasswordBuffer = stackalloc byte[cbPasswordBuffer]; // will be released when the frame unwinds
  70. pbPasswordBuffer = pbStackAllocPasswordBuffer;
  71. }
  72. }
  73. try
  74. {
  75. int cbPasswordBufferUsed; // we're not filling the entire buffer, just a partial buffer
  76. fixed (char* pszPassword = password)
  77. {
  78. cbPasswordBufferUsed = Encoding.UTF8.GetBytes(pszPassword, password.Length, pbPasswordBuffer, cbPasswordBuffer);
  79. }
  80. return PasswordToPbkdfKeyHandleStep2(pbkdf2AlgHandle, pbPasswordBuffer, (uint)cbPasswordBufferUsed, prf);
  81. }
  82. finally
  83. {
  84. UnsafeBufferUtil.SecureZeroMemory(pbPasswordBuffer, cbPasswordBuffer);
  85. }
  86. }
  87. }
  88. private static BCryptKeyHandle PasswordToPbkdfKeyHandleStep2(BCryptAlgorithmHandle pbkdf2AlgHandle, byte* pbPassword, uint cbPassword, KeyDerivationPrf prf)
  89. {
  90. const uint PBKDF2_MAX_KEYLENGTH_IN_BYTES = 2048; // GetSupportedKeyLengths() on a Win8 box; value should never be lowered in any future version of Windows
  91. if (cbPassword <= PBKDF2_MAX_KEYLENGTH_IN_BYTES)
  92. {
  93. // Common case: the password is small enough to be consumed directly by the PBKDF2 algorithm.
  94. return pbkdf2AlgHandle.GenerateSymmetricKey(pbPassword, cbPassword);
  95. }
  96. else
  97. {
  98. // Rare case: password is very long; we must hash manually.
  99. // PBKDF2 uses the PRFs in HMAC mode, and when the HMAC input key exceeds the hash function's
  100. // block length the key is hashed and run back through the key initialization function.
  101. BCryptAlgorithmHandle prfAlgorithmHandle; // cached; don't dispose
  102. switch (prf)
  103. {
  104. case KeyDerivationPrf.HMACSHA1:
  105. prfAlgorithmHandle = CachedAlgorithmHandles.SHA1;
  106. break;
  107. case KeyDerivationPrf.HMACSHA256:
  108. prfAlgorithmHandle = CachedAlgorithmHandles.SHA256;
  109. break;
  110. case KeyDerivationPrf.HMACSHA512:
  111. prfAlgorithmHandle = CachedAlgorithmHandles.SHA512;
  112. break;
  113. default:
  114. throw CryptoUtil.Fail("Unrecognized PRF.");
  115. }
  116. // Final sanity check: don't hash the password if the HMAC key initialization function wouldn't have done it for us.
  117. if (cbPassword <= prfAlgorithmHandle.GetHashBlockLength() /* in bytes */)
  118. {
  119. return pbkdf2AlgHandle.GenerateSymmetricKey(pbPassword, cbPassword);
  120. }
  121. // Hash the password and use the hash as input to PBKDF2.
  122. uint cbPasswordDigest = prfAlgorithmHandle.GetHashDigestLength();
  123. CryptoUtil.Assert(cbPasswordDigest > 0, "cbPasswordDigest > 0");
  124. fixed (byte* pbPasswordDigest = new byte[cbPasswordDigest])
  125. {
  126. try
  127. {
  128. using (var hashHandle = prfAlgorithmHandle.CreateHash())
  129. {
  130. hashHandle.HashData(pbPassword, cbPassword, pbPasswordDigest, cbPasswordDigest);
  131. }
  132. return pbkdf2AlgHandle.GenerateSymmetricKey(pbPasswordDigest, cbPasswordDigest);
  133. }
  134. finally
  135. {
  136. UnsafeBufferUtil.SecureZeroMemory(pbPasswordDigest, cbPasswordDigest);
  137. }
  138. }
  139. }
  140. }
  141. private static void DeriveKeyCore(BCryptKeyHandle pbkdf2KeyHandle, string hashAlgorithm, byte* pbSalt, uint cbSalt, ulong iterCount, byte* pbDerivedBytes, uint cbDerivedBytes)
  142. {
  143. // First, build the buffers necessary to pass (hash alg, salt, iter count) into the KDF
  144. BCryptBuffer* pBuffers = stackalloc BCryptBuffer[3];
  145. pBuffers[0].BufferType = BCryptKeyDerivationBufferType.KDF_ITERATION_COUNT;
  146. pBuffers[0].pvBuffer = (IntPtr)(&iterCount);
  147. pBuffers[0].cbBuffer = sizeof(ulong);
  148. pBuffers[1].BufferType = BCryptKeyDerivationBufferType.KDF_SALT;
  149. pBuffers[1].pvBuffer = (IntPtr)pbSalt;
  150. pBuffers[1].cbBuffer = cbSalt;
  151. fixed (char* pszHashAlgorithm = hashAlgorithm)
  152. {
  153. pBuffers[2].BufferType = BCryptKeyDerivationBufferType.KDF_HASH_ALGORITHM;
  154. pBuffers[2].pvBuffer = (IntPtr)pszHashAlgorithm;
  155. pBuffers[2].cbBuffer = GetTotalByteLengthIncludingNullTerminator(hashAlgorithm);
  156. // Add the header which points to the buffers
  157. BCryptBufferDesc bufferDesc = default(BCryptBufferDesc);
  158. BCryptBufferDesc.Initialize(ref bufferDesc);
  159. bufferDesc.cBuffers = 3;
  160. bufferDesc.pBuffers = pBuffers;
  161. // Finally, import the KDK into the KDF algorithm, then invoke the KDF
  162. uint numBytesDerived;
  163. int ntstatus = UnsafeNativeMethods.BCryptKeyDerivation(
  164. hKey: pbkdf2KeyHandle,
  165. pParameterList: &bufferDesc,
  166. pbDerivedKey: pbDerivedBytes,
  167. cbDerivedKey: cbDerivedBytes,
  168. pcbResult: out numBytesDerived,
  169. dwFlags: 0);
  170. UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
  171. // Final sanity checks before returning control to caller.
  172. CryptoUtil.Assert(numBytesDerived == cbDerivedBytes, "numBytesDerived == cbDerivedBytes");
  173. }
  174. }
  175. private static string PrfToCngAlgorithmId(KeyDerivationPrf prf)
  176. {
  177. switch (prf)
  178. {
  179. case KeyDerivationPrf.HMACSHA1:
  180. return Constants.BCRYPT_SHA1_ALGORITHM;
  181. case KeyDerivationPrf.HMACSHA256:
  182. return Constants.BCRYPT_SHA256_ALGORITHM;
  183. case KeyDerivationPrf.HMACSHA512:
  184. return Constants.BCRYPT_SHA512_ALGORITHM;
  185. default:
  186. throw CryptoUtil.Fail("Unrecognized PRF.");
  187. }
  188. }
  189. }
  190. }