Merge from internal DataProtection repo.

This commit is contained in:
GrabYourPitchforks 2014-04-15 18:03:41 -07:00
parent 26fedbb999
commit 1959aa9e7f
32 changed files with 647 additions and 312 deletions

View File

@ -3,12 +3,11 @@ using System.Security.Cryptography;
namespace Microsoft.AspNet.Security.DataProtection
{
internal static unsafe class Algorithms
internal unsafe static class Algorithms
{
public static readonly BCryptAlgorithmHandle AESAlgorithmHandle = CreateAESAlgorithmHandle();
public static readonly BCryptAlgorithmHandle HMACSHA256AlgorithmHandle = CreateHMACSHA256AlgorithmHandle();
public static readonly BCryptAlgorithmHandle HMACSHA512AlgorithmHandle = CreateHMACSHA512AlgorithmHandle();
public static readonly BCryptAlgorithmHandle SP800108AlgorithmHandle = CreateSP800108AlgorithmHandle();
private static BCryptAlgorithmHandle CreateAESAlgorithmHandle()
{
@ -23,7 +22,7 @@ namespace Microsoft.AspNet.Security.DataProtection
// change it to use CBC chaining; it already uses PKCS7 padding by default
fixed (char* pCbcMode = Constants.BCRYPT_CHAIN_MODE_CBC)
{
status = UnsafeNativeMethods.BCryptSetProperty(algHandle, Constants.BCRYPT_CHAINING_MODE, (IntPtr) pCbcMode, (uint) ((Constants.BCRYPT_CHAIN_MODE_CBC.Length + 1 /* trailing null */)*sizeof (char)), dwFlags: 0);
status = UnsafeNativeMethods.BCryptSetProperty(algHandle, Constants.BCRYPT_CHAINING_MODE, (IntPtr)pCbcMode, (uint)((Constants.BCRYPT_CHAIN_MODE_CBC.Length + 1 /* trailing null */) * sizeof(char)), dwFlags: 0);
}
if (status != 0)
{
@ -32,11 +31,11 @@ namespace Microsoft.AspNet.Security.DataProtection
return algHandle;
}
internal static BCryptAlgorithmHandle CreateGenericHMACHandleFromPrimitiveProvider(string algorithmName)
private static BCryptAlgorithmHandle CreateHMACSHA256AlgorithmHandle()
{
// create the HMACSHA-256 instance
BCryptAlgorithmHandle algHandle;
int status = UnsafeNativeMethods.BCryptOpenAlgorithmProvider(out algHandle, algorithmName, Constants.MS_PRIMITIVE_PROVIDER, dwFlags: BCryptAlgorithmFlags.BCRYPT_ALG_HANDLE_HMAC_FLAG);
int status = UnsafeNativeMethods.BCryptOpenAlgorithmProvider(out algHandle, Constants.BCRYPT_SHA256_ALGORITHM, Constants.MS_PRIMITIVE_PROVIDER, dwFlags: BCryptAlgorithmFlags.BCRYPT_ALG_HANDLE_HMAC_FLAG);
if (status != 0 || algHandle == null || algHandle.IsInvalid)
{
throw new CryptographicException(status);
@ -45,23 +44,11 @@ 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
return CreateGenericHMACHandleFromPrimitiveProvider(Constants.BCRYPT_SHA512_ALGORITHM);
}
private static BCryptAlgorithmHandle CreateSP800108AlgorithmHandle()
{
// create the SP800-108 instance
BCryptAlgorithmHandle algHandle;
int status = UnsafeNativeMethods.BCryptOpenAlgorithmProvider(out algHandle, Constants.BCRYPT_SP800108_CTR_HMAC_ALGORITHM, Constants.MS_PRIMITIVE_PROVIDER, dwFlags: 0);
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);
@ -70,4 +57,4 @@ namespace Microsoft.AspNet.Security.DataProtection
return algHandle;
}
}
}
}

View File

@ -23,4 +23,4 @@ namespace Microsoft.AspNet.Security.DataProtection
pHeader.dwVersion = BCRYPT_KEY_DATA_BLOB_VERSION1;
}
}
}
}

View File

@ -10,4 +10,4 @@ namespace Microsoft.AspNet.Security.DataProtection
BCRYPT_CAPI_AES_FLAG = 0x00000010,
BCRYPT_HASH_REUSABLE_FLAG = 0x00000020,
}
}
}

View File

@ -17,4 +17,4 @@ namespace Microsoft.AspNet.Security.DataProtection
return (UnsafeNativeMethods.BCryptCloseAlgorithmProvider(handle, dwFlags: 0) == 0);
}
}
}
}

View File

@ -12,4 +12,4 @@ namespace Microsoft.AspNet.Security.DataProtection
public BCryptKeyDerivationBufferType BufferType; // Buffer type
public IntPtr pvBuffer; // Pointer to buffer
}
}
}

View File

@ -20,4 +20,4 @@ namespace Microsoft.AspNet.Security.DataProtection
bufferDesc.ulVersion = BCRYPTBUFFER_VERSION;
}
}
}
}

View File

@ -8,4 +8,4 @@ namespace Microsoft.AspNet.Security.DataProtection
{
BCRYPT_BLOCK_PADDING = 0x00000001,
}
}
}

View File

@ -9,4 +9,4 @@ namespace Microsoft.AspNet.Security.DataProtection
BCRYPT_RNG_USE_ENTROPY_IN_BUFFER = 0x00000001,
BCRYPT_USE_SYSTEM_PREFERRED_RNG = 0x00000002,
}
}
}

View File

@ -17,4 +17,4 @@ namespace Microsoft.AspNet.Security.DataProtection
return (UnsafeNativeMethods.BCryptDestroyHash(handle) == 0);
}
}
}
}

View File

@ -23,4 +23,4 @@ namespace Microsoft.AspNet.Security.DataProtection
KDF_SALT = 0xF,
KDF_ITERATION_COUNT = 0x10,
}
}
}

View File

@ -17,4 +17,4 @@ namespace Microsoft.AspNet.Security.DataProtection
return (UnsafeNativeMethods.BCryptDestroyKey(handle) == 0);
}
}
}
}

View File

@ -1,13 +1,21 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNet.Security.DataProtection.Util;
namespace Microsoft.AspNet.Security.DataProtection
{
internal static unsafe class BCryptUtil
internal unsafe static class BCryptUtil
{
// from dpapi.h
const uint CRYPTPROTECTMEMORY_BLOCK_SIZE = 16;
const uint CRYPTPROTECTMEMORY_SAME_PROCESS = 0x00;
private static readonly UTF8Encoding _secureUtf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
// constant-time buffer comparison
[MethodImpl(MethodImplOptions.NoOptimization)]
public static bool BuffersAreEqualSecure(byte* p1, byte* p2, uint count)
@ -23,22 +31,22 @@ namespace Microsoft.AspNet.Security.DataProtection
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CheckOverflowUnderflow(int input)
{
var unused = checked((uint) input);
var unused = checked((uint)input);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CheckOverflowUnderflow(uint input)
{
var unused = checked((int) input);
var unused = checked((int)input);
}
// helper function to wrap BCryptCreateHash
public static BCryptHashHandle CreateHash(BCryptAlgorithmHandle algorithmHandle, byte* key, int keyLengthInBytes)
// helper function to wrap BCryptCreateHash, passing in a key used for HMAC
public static BCryptHashHandle CreateHMACHandle(BCryptAlgorithmHandle algorithmHandle, byte* key, int keyLengthInBytes)
{
CheckOverflowUnderflow(keyLengthInBytes);
BCryptHashHandle retVal;
int status = UnsafeNativeMethods.BCryptCreateHash(algorithmHandle, out retVal, IntPtr.Zero, 0, key, (uint) keyLengthInBytes, dwFlags: 0);
int status = UnsafeNativeMethods.BCryptCreateHash(algorithmHandle, out retVal, IntPtr.Zero, 0, key, (uint)keyLengthInBytes, dwFlags: 0);
if (status != 0 || retVal == null || retVal.IsInvalid)
{
throw new CryptographicException(status);
@ -61,24 +69,24 @@ namespace Microsoft.AspNet.Security.DataProtection
throw new InvalidOperationException();
}
byte* pDuplicatedIV = stackalloc byte[ivLength];
BufferUtil.BlockCopy(from: (IntPtr) iv, to: (IntPtr) pDuplicatedIV, byteCount: ivLength);
BufferUtil.BlockCopy(from: iv, to: pDuplicatedIV, byteCount: ivLength);
uint retVal;
int status = UnsafeNativeMethods.BCryptDecrypt(keyHandle, input, (uint) inputLength, IntPtr.Zero, pDuplicatedIV, (uint) ivLength, output, (uint) outputLength, out retVal, BCryptEncryptFlags.BCRYPT_BLOCK_PADDING);
int status = UnsafeNativeMethods.BCryptDecrypt(keyHandle, input, (uint)inputLength, IntPtr.Zero, pDuplicatedIV, (uint)ivLength, output, (uint)outputLength, out retVal, BCryptEncryptFlags.BCRYPT_BLOCK_PADDING);
if (status != 0)
{
throw new CryptographicException(status);
}
return checked((int) retVal);
return checked((int)retVal);
}
// helper function to wrap BCryptKeyDerivation using SP800-108-CTR-HMAC-SHA512
public static void DeriveKeysSP800108(BCryptAlgorithmHandle kdfAlgorithmHandle, BCryptKeyHandle keyHandle, string purpose, BCryptAlgorithmHandle encryptionAlgorithmHandle, out BCryptKeyHandle encryptionKeyHandle, BCryptAlgorithmHandle hashAlgorithmHandle, out BCryptHashHandle hmacHandle, out BCryptKeyHandle kdfKeyHandle)
public static void DeriveKeysSP800108(byte[] protectedKdk, string purpose, BCryptAlgorithmHandle encryptionAlgorithmHandle, out BCryptKeyHandle encryptionKeyHandle, BCryptAlgorithmHandle hashAlgorithmHandle, out BCryptHashHandle hmacHandle, out byte[] kdfSubkey)
{
const int ENCRYPTION_KEY_SIZE_IN_BYTES = 256/8;
const int HMAC_KEY_SIZE_IN_BYTES = 256/8;
const int KDF_SUBKEY_SIZE_IN_BYTES = 512/8;
const int ENCRYPTION_KEY_SIZE_IN_BYTES = 256 / 8;
const int HMAC_KEY_SIZE_IN_BYTES = 256 / 8;
const int KDF_SUBKEY_SIZE_IN_BYTES = 512 / 8;
const int TOTAL_NUM_BYTES_TO_DERIVE = ENCRYPTION_KEY_SIZE_IN_BYTES + HMAC_KEY_SIZE_IN_BYTES + KDF_SUBKEY_SIZE_IN_BYTES;
// keep our buffers on the stack while we're generating key material
@ -87,50 +95,27 @@ namespace Microsoft.AspNet.Security.DataProtection
byte* pNewHmacKey = &pNewEncryptionKey[ENCRYPTION_KEY_SIZE_IN_BYTES];
byte* pNewKdfSubkey = &pNewHmacKey[HMAC_KEY_SIZE_IN_BYTES];
try
protectedKdk = (byte[])protectedKdk.Clone(); // CryptUnprotectMemory mutates its input, so we preserve the original
fixed (byte* pKdk = protectedKdk)
{
fixed (char* pszPrfAlgorithmName = Constants.BCRYPT_SHA512_ALGORITHM)
try
{
// Create a buffer to hold the hash algorithm name, currently hardcoded to HMACSHA512
uint numBuffers = 1;
BCryptBuffer* pBCryptBuffers = stackalloc BCryptBuffer[2];
pBCryptBuffers[0].BufferType = BCryptKeyDerivationBufferType.KDF_HASH_ALGORITHM;
pBCryptBuffers[0].pvBuffer = (IntPtr) pszPrfAlgorithmName;
pBCryptBuffers[0].cbBuffer = (uint) ((Constants.BCRYPT_SHA512_ALGORITHM.Length + 1)*sizeof (char)); // per http://msdn.microsoft.com/en-us/library/windows/desktop/aa375368(v=vs.85).aspx, need to include terminating null
fixed (char* pszPurpose = (String.IsNullOrEmpty(purpose) ? (string) null : purpose))
{
// Create a buffer to hold the purpose string if it is specified (we'll treat it as UTF-16LE)
if (pszPurpose != null)
{
numBuffers = 2;
pBCryptBuffers[1].BufferType = BCryptKeyDerivationBufferType.KDF_LABEL;
pBCryptBuffers[1].pvBuffer = (IntPtr) pszPurpose;
pBCryptBuffers[1].cbBuffer = checked((uint) (purpose.Length*sizeof (char)));
}
// Since the KDK is pinned, the GC won't move around the array containing the plaintext key before we
// have the opportunity to clear its contents.
UnprotectMemoryWithinThisProcess(pKdk, (uint)protectedKdk.Length);
// .. and the header ..
BCryptBufferDesc bufferDesc = default(BCryptBufferDesc);
BCryptBufferDesc.Initialize(ref bufferDesc);
bufferDesc.cBuffers = numBuffers;
bufferDesc.pBuffers = pBCryptBuffers;
byte[] purposeBytes = (!String.IsNullOrEmpty(purpose)) ? _secureUtf8Encoding.GetBytes(purpose) : null;
SP800_108Helper.DeriveKeys(pKdk, protectedKdk.Length, purposeBytes, pBuffer, TOTAL_NUM_BYTES_TO_DERIVE);
uint numBytesDerived;
int status = UnsafeNativeMethods.BCryptKeyDerivation(keyHandle, &bufferDesc, pBuffer, TOTAL_NUM_BYTES_TO_DERIVE, out numBytesDerived, dwFlags: 0);
if (status != 0 || numBytesDerived != TOTAL_NUM_BYTES_TO_DERIVE)
{
throw new CryptographicException(status);
}
}
// Split into AES, HMAC, and KDF subkeys
encryptionKeyHandle = ImportKey(encryptionAlgorithmHandle, pNewEncryptionKey, ENCRYPTION_KEY_SIZE_IN_BYTES);
hmacHandle = CreateHMACHandle(hashAlgorithmHandle, pNewHmacKey, HMAC_KEY_SIZE_IN_BYTES);
kdfSubkey = BufferUtil.ToProtectedManagedByteArray(pNewKdfSubkey, KDF_SUBKEY_SIZE_IN_BYTES);
}
finally
{
BufferUtil.SecureZeroMemory(pKdk, protectedKdk.Length);
}
// At this point, we have all the bytes we need.
encryptionKeyHandle = ImportKey(encryptionAlgorithmHandle, pNewEncryptionKey, ENCRYPTION_KEY_SIZE_IN_BYTES);
hmacHandle = CreateHash(hashAlgorithmHandle, pNewHmacKey, HMAC_KEY_SIZE_IN_BYTES);
kdfKeyHandle = ImportKey(kdfAlgorithmHandle, pNewKdfSubkey, KDF_SUBKEY_SIZE_IN_BYTES);
}
finally
{
BufferUtil.ZeroMemory(pBuffer, TOTAL_NUM_BYTES_TO_DERIVE);
}
}
@ -161,16 +146,16 @@ namespace Microsoft.AspNet.Security.DataProtection
throw new InvalidOperationException();
}
byte* pDuplicatedIV = stackalloc byte[ivLength];
BufferUtil.BlockCopy(from: (IntPtr) iv, to: (IntPtr) pDuplicatedIV, byteCount: ivLength);
BufferUtil.BlockCopy(from: iv, to: pDuplicatedIV, byteCount: ivLength);
uint retVal;
int status = UnsafeNativeMethods.BCryptEncrypt(keyHandle, input, (uint) inputLength, IntPtr.Zero, pDuplicatedIV, (uint) ivLength, output, (uint) outputLength, out retVal, BCryptEncryptFlags.BCRYPT_BLOCK_PADDING);
int status = UnsafeNativeMethods.BCryptEncrypt(keyHandle, input, (uint)inputLength, IntPtr.Zero, pDuplicatedIV, (uint)ivLength, output, (uint)outputLength, out retVal, BCryptEncryptFlags.BCRYPT_BLOCK_PADDING);
if (status != 0)
{
throw new CryptographicException(status);
}
return checked((int) retVal);
return checked((int)retVal);
}
// helper function to take a key, apply a purpose, and generate a new subkey ("entropy") for DPAPI-specific scenarios
@ -183,7 +168,7 @@ namespace Microsoft.AspNet.Security.DataProtection
BCryptHashHandle hashHandle;
fixed (byte* pPreviousKey = previousKey)
{
hashHandle = CreateHash(Algorithms.HMACSHA256AlgorithmHandle, pPreviousKey, previousKey.Length);
hashHandle = CreateHMACHandle(Algorithms.HMACSHA256AlgorithmHandle, pPreviousKey, previousKey.Length);
}
// hash the purpose string, treating it as UTF-16LE
@ -206,7 +191,7 @@ namespace Microsoft.AspNet.Security.DataProtection
{
CheckOverflowUnderflow(bufferBytes);
int status = UnsafeNativeMethods.BCryptGenRandom(IntPtr.Zero, buffer, (uint) bufferBytes, BCryptGenRandomFlags.BCRYPT_USE_SYSTEM_PREFERRED_RNG);
int status = UnsafeNativeMethods.BCryptGenRandom(IntPtr.Zero, buffer, (uint)bufferBytes, BCryptGenRandomFlags.BCRYPT_USE_SYSTEM_PREFERRED_RNG);
if (status != 0)
{
throw new CryptographicException(status);
@ -219,13 +204,13 @@ namespace Microsoft.AspNet.Security.DataProtection
CheckOverflowUnderflow(inputBytes);
CheckOverflowUnderflow(outputBytes);
int status = UnsafeNativeMethods.BCryptHashData(hashHandle, input, (uint) inputBytes, dwFlags: 0);
int status = UnsafeNativeMethods.BCryptHashData(hashHandle, input, (uint)inputBytes, dwFlags: 0);
if (status != 0)
{
throw new CryptographicException(status);
}
status = UnsafeNativeMethods.BCryptFinishHash(hashHandle, output, (uint) outputBytes, dwFlags: 0);
status = UnsafeNativeMethods.BCryptFinishHash(hashHandle, output, (uint)outputBytes, dwFlags: 0);
if (status != 0)
{
throw new CryptographicException(status);
@ -238,7 +223,7 @@ namespace Microsoft.AspNet.Security.DataProtection
CheckOverflowUnderflow(keyBytes);
byte[] heapAllocatedKeyDataBlob = null;
int numBytesRequiredForKeyDataBlob = checked(keyBytes + sizeof (BCRYPT_KEY_DATA_BLOB_HEADER));
int numBytesRequiredForKeyDataBlob = checked(keyBytes + sizeof(BCRYPT_KEY_DATA_BLOB_HEADER));
if (numBytesRequiredForKeyDataBlob > Constants.MAX_STACKALLOC_BYTES)
{
heapAllocatedKeyDataBlob = new byte[numBytesRequiredForKeyDataBlob]; // allocate on heap if we cannot allocate on stack
@ -248,28 +233,28 @@ namespace Microsoft.AspNet.Security.DataProtection
BCryptKeyHandle retVal;
fixed (byte* pHeapAllocatedKeyDataBlob = heapAllocatedKeyDataBlob)
{
// The header is first
BCRYPT_KEY_DATA_BLOB_HEADER* pKeyDataBlobHeader = (BCRYPT_KEY_DATA_BLOB_HEADER*) pHeapAllocatedKeyDataBlob;
// The header is first; if it wasn't heap-allocated we can stack-allocate now
BCRYPT_KEY_DATA_BLOB_HEADER* pKeyDataBlobHeader = (BCRYPT_KEY_DATA_BLOB_HEADER*)pHeapAllocatedKeyDataBlob;
if (pKeyDataBlobHeader == null)
{
byte* temp = stackalloc byte[numBytesRequiredForKeyDataBlob]; // won't be released until frame pops
pKeyDataBlobHeader = (BCRYPT_KEY_DATA_BLOB_HEADER*) temp;
pKeyDataBlobHeader = (BCRYPT_KEY_DATA_BLOB_HEADER*)temp;
}
BCRYPT_KEY_DATA_BLOB_HEADER.Initialize(ref *pKeyDataBlobHeader);
pKeyDataBlobHeader->cbKeyData = (uint) keyBytes;
pKeyDataBlobHeader->cbKeyData = (uint)keyBytes;
// the raw material immediately follows the header
byte* pKeyDataRawMaterial = (byte*) (&pKeyDataBlobHeader[1]);
byte* pKeyDataRawMaterial = (byte*)(&pKeyDataBlobHeader[1]);
try
{
BufferUtil.BlockCopy(from: (IntPtr) key, to: (IntPtr) pKeyDataRawMaterial, byteCount: keyBytes);
status = UnsafeNativeMethods.BCryptImportKey(algHandle, IntPtr.Zero, Constants.BCRYPT_KEY_DATA_BLOB, out retVal, IntPtr.Zero, 0, (byte*) pKeyDataBlobHeader, (uint) numBytesRequiredForKeyDataBlob, dwFlags: 0);
BufferUtil.BlockCopy(from: key, to: pKeyDataRawMaterial, byteCount: keyBytes);
status = UnsafeNativeMethods.BCryptImportKey(algHandle, IntPtr.Zero, Constants.BCRYPT_KEY_DATA_BLOB, out retVal, IntPtr.Zero, 0, (byte*)pKeyDataBlobHeader, (uint)numBytesRequiredForKeyDataBlob, dwFlags: 0);
}
finally
{
// zero out the key we just copied
BufferUtil.ZeroMemory(pKeyDataRawMaterial, keyBytes);
BufferUtil.SecureZeroMemory(pKeyDataRawMaterial, keyBytes);
}
}
@ -279,5 +264,29 @@ namespace Microsoft.AspNet.Security.DataProtection
}
return retVal;
}
internal static void ProtectMemoryWithinThisProcess(byte* pBuffer, uint bufferLength)
{
Debug.Assert(pBuffer != null);
Debug.Assert(bufferLength % CRYPTPROTECTMEMORY_BLOCK_SIZE == 0, "Input buffer size must be a multiple of CRYPTPROTECTMEMORY_BLOCK_SIZE.");
bool success = UnsafeNativeMethods.CryptProtectMemory(pBuffer, bufferLength, CRYPTPROTECTMEMORY_SAME_PROCESS);
if (!success)
{
throw new CryptographicException(Marshal.GetLastWin32Error());
}
}
internal static void UnprotectMemoryWithinThisProcess(byte* pBuffer, uint bufferLength)
{
Debug.Assert(pBuffer != null);
Debug.Assert(bufferLength % CRYPTPROTECTMEMORY_BLOCK_SIZE == 0, "Input buffer size must be a multiple of CRYPTPROTECTMEMORY_BLOCK_SIZE.");
bool success = UnsafeNativeMethods.CryptUnprotectMemory(pBuffer, bufferLength, CRYPTPROTECTMEMORY_SAME_PROCESS);
if (!success)
{
throw new CryptographicException(Marshal.GetLastWin32Error());
}
}
}
}
}

View File

@ -82,4 +82,4 @@ namespace Microsoft.AspNet.Security.DataProtection
internal const string BCRYPT_CHAIN_MODE_CCM = "ChainingModeCCM";
internal const string BCRYPT_CHAIN_MODE_GCM = "ChainingModeGCM";
}
}
}

View File

@ -1,15 +0,0 @@
using System;
#if !NET45
namespace System.Security.Cryptography {
internal sealed class CryptographicException : Exception {
internal CryptographicException(string message)
: base(message) {
}
internal CryptographicException(int unused) {
}
}
}
#endif

View File

@ -1,5 +1,4 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Microsoft.AspNet.Security.DataProtection
@ -11,4 +10,4 @@ namespace Microsoft.AspNet.Security.DataProtection
public uint cbData;
public byte* pbData;
}
}
}

View File

@ -1,41 +1,38 @@
using System;
using System.Globalization;
using System.Reflection;
using System.Text;
using Microsoft.AspNet.Security.DataProtection;
using Microsoft.AspNet.Security.DataProtection.Util;
namespace Microsoft.AspNet.Security.DataProtection
{
public static unsafe class DataProtectionProvider
/// <summary>
/// Provides methods for creating IDataProtectionProvider instances.
/// </summary>
public unsafe static class DataProtectionProvider
{
private const int MASTER_KEY_REQUIRED_LENGTH = 512/8;
const int MASTER_KEY_REQUIRED_LENGTH = 512 / 8;
private static readonly byte[] MASTER_SUBKEY_GENERATOR = GetMasterSubkeyGenerator();
private static readonly byte[] MASTER_DPAPI_ENTROPY = GetMasterSubkeyGenerator(isDpapi: true);
private static readonly byte[] MASTER_SUBKEY_GENERATOR = Encoding.ASCII.GetBytes("Microsoft.AspNet.Security.DataProtection");
private static byte[] GetMasterSubkeyGenerator(bool isDpapi = false)
/// <summary>
/// Creates a new IDataProtectionProvider backed by DPAPI, where the protected
/// payload can only be decrypted by the current user.
/// </summary>
public static IDataProtectionProvider CreateFromDpapi()
{
TypeInfo typeInfo = ((isDpapi) ? typeof(DpapiDataProtectionProviderImpl) : typeof(DataProtectionProvider)).GetTypeInfo();
byte[] retVal = new byte[sizeof (Guid)*2];
fixed (byte* pRetVal = retVal)
{
Guid* guids = (Guid*) pRetVal;
guids[0] = typeInfo.GUID;
#if NET45
guids[1] = typeInfo.Module.ModuleVersionId;
#else
guids[1] = default(Guid);
#endif
}
return retVal;
return CreateFromDpapi(protectToLocalMachine: false);
}
/// <summary>
/// Creates a new IDataProtectionProvider backed by DPAPI.
/// </summary>
public static IDataProtectionProvider CreateFromDpapi()
/// <param name="protectToLocalMachine">True if protected payloads can be decrypted by any user
/// on the local machine, false if protected payloads should only be able to decrypted by the
/// current user account.</param>
public static IDataProtectionProvider CreateFromDpapi(bool protectToLocalMachine)
{
return new DpapiDataProtectionProviderImpl(MASTER_DPAPI_ENTROPY);
return new DpapiDataProtectionProviderImpl(MASTER_SUBKEY_GENERATOR, protectToLocalMachine);
}
/// <summary>
@ -51,7 +48,7 @@ namespace Microsoft.AspNet.Security.DataProtection
}
finally
{
BufferUtil.ZeroMemory(masterKey, MASTER_KEY_REQUIRED_LENGTH);
BufferUtil.SecureZeroMemory(masterKey, MASTER_KEY_REQUIRED_LENGTH);
}
}
@ -82,17 +79,20 @@ namespace Microsoft.AspNet.Security.DataProtection
byte* masterSubkey = stackalloc byte[MASTER_KEY_REQUIRED_LENGTH];
try
{
using (var hashHandle = BCryptUtil.CreateHash(Algorithms.HMACSHA512AlgorithmHandle, masterKey, masterKeyLengthInBytes))
using (var hashHandle = BCryptUtil.CreateHMACHandle(Algorithms.HMACSHA512AlgorithmHandle, masterKey, masterKeyLengthInBytes))
{
BCryptUtil.HashData(hashHandle, masterKey, masterKeyLengthInBytes, masterSubkey, MASTER_KEY_REQUIRED_LENGTH);
fixed (byte* pMasterSubkeyGenerator = MASTER_SUBKEY_GENERATOR)
{
BCryptUtil.HashData(hashHandle, pMasterSubkeyGenerator, MASTER_SUBKEY_GENERATOR.Length, masterSubkey, MASTER_KEY_REQUIRED_LENGTH);
}
}
BCryptKeyHandle kdfSubkeyHandle = BCryptUtil.ImportKey(Algorithms.SP800108AlgorithmHandle, masterSubkey, MASTER_KEY_REQUIRED_LENGTH);
return new DataProtectionProviderImpl(kdfSubkeyHandle);
byte[] protectedKdk = BufferUtil.ToProtectedManagedByteArray(masterSubkey, MASTER_KEY_REQUIRED_LENGTH);
return new DataProtectionProviderImpl(protectedKdk);
}
finally
{
BufferUtil.ZeroMemory(masterSubkey, MASTER_KEY_REQUIRED_LENGTH);
BufferUtil.SecureZeroMemory(masterSubkey, MASTER_KEY_REQUIRED_LENGTH);
}
}
}
}
}

View File

@ -2,28 +2,28 @@
namespace Microsoft.AspNet.Security.DataProtection
{
internal sealed unsafe class DataProtectionProviderImpl : IDataProtectionProvider
internal unsafe sealed class DataProtectionProviderImpl : IDataProtectionProvider
{
private readonly BCryptKeyHandle _kdfSubkeyHandle;
private readonly byte[] _protectedKdk;
public DataProtectionProviderImpl(BCryptKeyHandle kdfSubkeyHandle)
public DataProtectionProviderImpl(byte[] protectedKdk)
{
_kdfSubkeyHandle = kdfSubkeyHandle;
_protectedKdk = protectedKdk;
}
public IDataProtector CreateProtector(string purpose)
{
BCryptKeyHandle newAesKeyHandle;
BCryptHashHandle newHmacHashHandle;
BCryptKeyHandle newKdfSubkeyHandle;
byte[] newProtectedKdfSubkey;
BCryptUtil.DeriveKeysSP800108(Algorithms.SP800108AlgorithmHandle, _kdfSubkeyHandle, purpose, Algorithms.AESAlgorithmHandle, out newAesKeyHandle, Algorithms.HMACSHA256AlgorithmHandle, out newHmacHashHandle, out newKdfSubkeyHandle);
return new DataProtectorImpl(newAesKeyHandle, newHmacHashHandle, newKdfSubkeyHandle);
BCryptUtil.DeriveKeysSP800108(_protectedKdk, purpose, Algorithms.AESAlgorithmHandle, out newAesKeyHandle, Algorithms.HMACSHA256AlgorithmHandle, out newHmacHashHandle, out newProtectedKdfSubkey);
return new DataProtectorImpl(newAesKeyHandle, newHmacHashHandle, newProtectedKdfSubkey);
}
public void Dispose()
{
_kdfSubkeyHandle.Dispose();
// no-op: we hold no protected resources
}
}
}
}

View File

@ -5,29 +5,37 @@ using Microsoft.AspNet.Security.DataProtection.Util;
namespace Microsoft.AspNet.Security.DataProtection
{
internal sealed unsafe class DataProtectorImpl : IDataProtector
internal unsafe sealed class DataProtectorImpl : IDataProtector
{
private const int AES_BLOCK_LENGTH_IN_BYTES = 128/8;
private const int MAC_LENGTH_IN_BYTES = 256/8;
private const int AES_BLOCK_LENGTH_IN_BYTES = 128 / 8;
private const int AES_IV_LENGTH_IN_BYTES = AES_BLOCK_LENGTH_IN_BYTES;
private const int MAC_LENGTH_IN_BYTES = 256 / 8;
private readonly BCryptKeyHandle _aesKeyHandle;
private readonly BCryptHashHandle _hmacHashHandle;
private readonly BCryptKeyHandle _kdfSubkeyHandle;
private readonly byte[] _protectedKdk;
public DataProtectorImpl(BCryptKeyHandle aesKeyHandle, BCryptHashHandle hmacHashHandle, BCryptKeyHandle kdfSubkeyHandle)
public DataProtectorImpl(BCryptKeyHandle aesKeyHandle, BCryptHashHandle hmacHashHandle, byte[] protectedKdk)
{
_aesKeyHandle = aesKeyHandle;
_hmacHashHandle = hmacHashHandle;
_kdfSubkeyHandle = kdfSubkeyHandle;
_protectedKdk = protectedKdk;
}
private static int CalculateTotalProtectedDataSize(int unprotectedDataSize)
private static int CalculateTotalProtectedDataSize(int unprotectedDataSizeInBytes)
{
Debug.Assert(unprotectedDataSize >= 0);
Debug.Assert(unprotectedDataSizeInBytes >= 0);
// Calculates
int numFullBlocks = unprotectedDataSize/AES_BLOCK_LENGTH_IN_BYTES;
return checked(AES_BLOCK_LENGTH_IN_BYTES /* IV */+ (numFullBlocks + 1)*AES_BLOCK_LENGTH_IN_BYTES /* ciphertext w/ padding */+ MAC_LENGTH_IN_BYTES /* HMAC */);
checked
{
// Padding always rounds the block count up, never down.
// If the input size is already a multiple of the block length, a block is added.
int numBlocks = 1 + unprotectedDataSizeInBytes / AES_BLOCK_LENGTH_IN_BYTES;
return
AES_IV_LENGTH_IN_BYTES /* IV */
+ numBlocks * AES_BLOCK_LENGTH_IN_BYTES /* ciphertext with padding */
+ MAC_LENGTH_IN_BYTES /* MAC */;
}
}
private static CryptographicException CreateGenericCryptographicException()
@ -39,17 +47,16 @@ namespace Microsoft.AspNet.Security.DataProtection
{
BCryptKeyHandle newAesKeyHandle;
BCryptHashHandle newHmacHashHandle;
BCryptKeyHandle newKdfSubkeyHandle;
byte[] newProtectedKdfSubkey;
BCryptUtil.DeriveKeysSP800108(Algorithms.SP800108AlgorithmHandle, _kdfSubkeyHandle, purpose, Algorithms.AESAlgorithmHandle, out newAesKeyHandle, Algorithms.HMACSHA256AlgorithmHandle, out newHmacHashHandle, out newKdfSubkeyHandle);
return new DataProtectorImpl(newAesKeyHandle, newHmacHashHandle, newKdfSubkeyHandle);
BCryptUtil.DeriveKeysSP800108(_protectedKdk, purpose, Algorithms.AESAlgorithmHandle, out newAesKeyHandle, Algorithms.HMACSHA256AlgorithmHandle, out newHmacHashHandle, out newProtectedKdfSubkey);
return new DataProtectorImpl(newAesKeyHandle, newHmacHashHandle, newProtectedKdfSubkey);
}
public void Dispose()
{
_aesKeyHandle.Dispose();
_hmacHashHandle.Dispose();
_kdfSubkeyHandle.Dispose();
}
public byte[] Protect(byte[] unprotectedData)
@ -66,14 +73,14 @@ namespace Microsoft.AspNet.Security.DataProtection
{
// first, generate a random IV for CBC mode encryption
byte* pIV = pProtectedData;
BCryptUtil.GenRandom(pIV, AES_BLOCK_LENGTH_IN_BYTES);
BCryptUtil.GenRandom(pIV, AES_IV_LENGTH_IN_BYTES);
// then, encrypt the plaintext contents
byte* pCiphertext = &pIV[AES_BLOCK_LENGTH_IN_BYTES];
int expectedCiphertextLength = protectedData.Length - AES_BLOCK_LENGTH_IN_BYTES - MAC_LENGTH_IN_BYTES;
fixed (byte* pPlaintext = unprotectedData)
byte* pCiphertext = &pIV[AES_IV_LENGTH_IN_BYTES];
int expectedCiphertextLength = protectedData.Length - AES_IV_LENGTH_IN_BYTES - MAC_LENGTH_IN_BYTES;
fixed (byte* pPlaintext = unprotectedData.AsFixed())
{
int actualCiphertextLength = BCryptUtil.EncryptWithPadding(_aesKeyHandle, pPlaintext, unprotectedData.Length, pIV, AES_BLOCK_LENGTH_IN_BYTES, pCiphertext, expectedCiphertextLength);
int actualCiphertextLength = BCryptUtil.EncryptWithPadding(_aesKeyHandle, pPlaintext, unprotectedData.Length, pIV, AES_IV_LENGTH_IN_BYTES, pCiphertext, expectedCiphertextLength);
if (actualCiphertextLength != expectedCiphertextLength)
{
throw new InvalidOperationException("Unexpected error while encrypting data.");
@ -86,7 +93,7 @@ namespace Microsoft.AspNet.Security.DataProtection
{
// Use a cloned hash handle since IDataProtector instances could be singletons, but BCryptHashHandle instances contain
// state hence aren't thread-safe. Our own perf testing shows that duplicating existing hash handles is very fast.
BCryptUtil.HashData(clonedHashHandle, pProtectedData, AES_BLOCK_LENGTH_IN_BYTES + expectedCiphertextLength, pMac, MAC_LENGTH_IN_BYTES);
BCryptUtil.HashData(clonedHashHandle, pProtectedData, AES_IV_LENGTH_IN_BYTES + expectedCiphertextLength, pMac, MAC_LENGTH_IN_BYTES);
}
}
@ -125,7 +132,7 @@ namespace Microsoft.AspNet.Security.DataProtection
Debug.Assert(protectedData != null);
// is the protected data even long enough to be valid?
if (protectedData.Length < AES_BLOCK_LENGTH_IN_BYTES /* IV */+ AES_BLOCK_LENGTH_IN_BYTES /* min ciphertext size = 1 block */+ MAC_LENGTH_IN_BYTES)
if (protectedData.Length < AES_IV_LENGTH_IN_BYTES /* IV */ + AES_BLOCK_LENGTH_IN_BYTES /* min ciphertext size = 1 block */ + MAC_LENGTH_IN_BYTES)
{
return null;
}
@ -134,8 +141,8 @@ namespace Microsoft.AspNet.Security.DataProtection
{
// calculate pointer offsets
byte* pIV = pProtectedData;
byte* pCiphertext = &pProtectedData[AES_BLOCK_LENGTH_IN_BYTES];
int ciphertextLength = protectedData.Length - AES_BLOCK_LENGTH_IN_BYTES /* IV */- MAC_LENGTH_IN_BYTES /* MAC */;
byte* pCiphertext = &pProtectedData[AES_IV_LENGTH_IN_BYTES];
int ciphertextLength = protectedData.Length - AES_IV_LENGTH_IN_BYTES /* IV */ - MAC_LENGTH_IN_BYTES /* MAC */;
byte* pSuppliedMac = &pCiphertext[ciphertextLength];
// first, ensure that the MAC is valid
@ -143,7 +150,7 @@ namespace Microsoft.AspNet.Security.DataProtection
using (var clonedHashHandle = BCryptUtil.DuplicateHash(_hmacHashHandle))
{
// see comments in Protect(byte[]) for why we duplicate the hash
BCryptUtil.HashData(clonedHashHandle, pProtectedData, AES_BLOCK_LENGTH_IN_BYTES + ciphertextLength, pCalculatedMac, MAC_LENGTH_IN_BYTES);
BCryptUtil.HashData(clonedHashHandle, pProtectedData, AES_IV_LENGTH_IN_BYTES + ciphertextLength, pCalculatedMac, MAC_LENGTH_IN_BYTES);
}
if (!BCryptUtil.BuffersAreEqualSecure(pSuppliedMac, pCalculatedMac, MAC_LENGTH_IN_BYTES))
{
@ -168,18 +175,13 @@ namespace Microsoft.AspNet.Security.DataProtection
pPlaintextBuffer = temp;
}
int actualPlaintextLength = BCryptUtil.DecryptWithPadding(_aesKeyHandle, pCiphertext, ciphertextLength, pIV, AES_BLOCK_LENGTH_IN_BYTES, pPlaintextBuffer, plaintextBufferLength);
int actualPlaintextLength = BCryptUtil.DecryptWithPadding(_aesKeyHandle, pCiphertext, ciphertextLength, pIV, AES_IV_LENGTH_IN_BYTES, pPlaintextBuffer, plaintextBufferLength);
Debug.Assert(actualPlaintextLength >= 0 && actualPlaintextLength < ciphertextLength);
// truncate the return value to accomodate the plaintext size perfectly
byte[] retVal = new byte[actualPlaintextLength];
fixed (byte* pRetVal = retVal)
{
BufferUtil.BlockCopy(from: (IntPtr) pPlaintextBuffer, to: (IntPtr) pRetVal, byteCount: actualPlaintextLength);
}
return retVal;
return BufferUtil.ToManagedByteArray(pPlaintextBuffer, actualPlaintextLength);
}
}
}
}
}
}

View File

@ -6,16 +6,18 @@ namespace Microsoft.AspNet.Security.DataProtection
internal sealed class DpapiDataProtectionProviderImpl : IDataProtectionProvider
{
private readonly byte[] _entropy;
private readonly bool _protectToLocalMachine;
public DpapiDataProtectionProviderImpl(byte[] entropy)
public DpapiDataProtectionProviderImpl(byte[] entropy, bool protectToLocalMachine)
{
Debug.Assert(entropy != null);
_entropy = entropy;
_protectToLocalMachine = protectToLocalMachine;
}
public IDataProtector CreateProtector(string purpose)
{
return new DpapiDataProtectorImpl(BCryptUtil.GenerateDpapiSubkey(_entropy, purpose));
return new DpapiDataProtectorImpl(BCryptUtil.GenerateDpapiSubkey(_entropy, purpose), _protectToLocalMachine);
}
public void Dispose()
@ -23,4 +25,4 @@ namespace Microsoft.AspNet.Security.DataProtection
// no-op; no unmanaged resources to dispose
}
}
}
}

View File

@ -10,15 +10,19 @@ namespace Microsoft.AspNet.Security.DataProtection
internal unsafe sealed class DpapiDataProtectorImpl : IDataProtector
{
// from dpapi.h
private const uint CRYPTPROTECT_LOCAL_MACHINE = 0x4;
private const uint CRYPTPROTECT_UI_FORBIDDEN = 0x1;
// Used as the 'purposes' parameter to DPAPI operations
private readonly byte[] _entropy;
public DpapiDataProtectorImpl(byte[] entropy)
private readonly bool _protectToLocalMachine;
public DpapiDataProtectorImpl(byte[] entropy, bool protectToLocalMachine)
{
Debug.Assert(entropy != null);
_entropy = entropy;
_protectToLocalMachine = protectToLocalMachine;
}
private static CryptographicException CreateGenericCryptographicException(bool isErrorDueToProfileNotLoaded = false)
@ -29,7 +33,7 @@ namespace Microsoft.AspNet.Security.DataProtection
public IDataProtector CreateSubProtector(string purpose)
{
return new DpapiDataProtectorImpl(BCryptUtil.GenerateDpapiSubkey(_entropy, purpose));
return new DpapiDataProtectorImpl(BCryptUtil.GenerateDpapiSubkey(_entropy, purpose), _protectToLocalMachine);
}
public void Dispose()
@ -37,6 +41,18 @@ namespace Microsoft.AspNet.Security.DataProtection
// no-op; no unmanaged resources to dispose
}
private uint GetCryptProtectUnprotectFlags()
{
if (_protectToLocalMachine)
{
return CRYPTPROTECT_LOCAL_MACHINE | CRYPTPROTECT_UI_FORBIDDEN;
}
else
{
return CRYPTPROTECT_UI_FORBIDDEN;
}
}
public byte[] Protect(byte[] unprotectedData)
{
if (unprotectedData == null)
@ -52,14 +68,14 @@ namespace Microsoft.AspNet.Security.DataProtection
try
{
bool success;
fixed (byte* pUnprotectedData = unprotectedData)
fixed (byte* pUnprotectedData = unprotectedData.AsFixed())
{
fixed (byte* pEntropy = _entropy)
{
// no need for checked arithmetic here
DATA_BLOB dataIn = new DATA_BLOB() { cbData = (uint)unprotectedData.Length, pbData = pUnprotectedData };
DATA_BLOB optionalEntropy = new DATA_BLOB() { cbData = (uint)_entropy.Length, pbData = pEntropy };
success = UnsafeNativeMethods.CryptProtectData(&dataIn, IntPtr.Zero, &optionalEntropy, IntPtr.Zero, IntPtr.Zero, CRYPTPROTECT_UI_FORBIDDEN, out dataOut);
success = UnsafeNativeMethods.CryptProtectData(&dataIn, IntPtr.Zero, &optionalEntropy, IntPtr.Zero, IntPtr.Zero, GetCryptProtectUnprotectFlags(), out dataOut);
}
}
@ -104,14 +120,14 @@ namespace Microsoft.AspNet.Security.DataProtection
try
{
bool success;
fixed (byte* pProtectedData = protectedData)
fixed (byte* pProtectedData = protectedData.AsFixed())
{
fixed (byte* pEntropy = _entropy)
{
// no need for checked arithmetic here
DATA_BLOB dataIn = new DATA_BLOB() { cbData = (uint)protectedData.Length, pbData = pProtectedData };
DATA_BLOB optionalEntropy = new DATA_BLOB() { cbData = (uint)_entropy.Length, pbData = pEntropy };
success = UnsafeNativeMethods.CryptUnprotectData(&dataIn, IntPtr.Zero, &optionalEntropy, IntPtr.Zero, IntPtr.Zero, CRYPTPROTECT_UI_FORBIDDEN, out dataOut);
success = UnsafeNativeMethods.CryptUnprotectData(&dataIn, IntPtr.Zero, &optionalEntropy, IntPtr.Zero, IntPtr.Zero, GetCryptProtectUnprotectFlags(), out dataOut);
}
}
@ -139,4 +155,4 @@ namespace Microsoft.AspNet.Security.DataProtection
}
}
}
}
}

View File

@ -2,6 +2,9 @@
namespace Microsoft.AspNet.Security.DataProtection
{
/// <summary>
/// A factory that can provide IDataProtector instances.
/// </summary>
public interface IDataProtectionProvider : IDisposable
{
/// <summary>
@ -11,4 +14,4 @@ namespace Microsoft.AspNet.Security.DataProtection
/// <returns>An IDataProtector.</returns>
IDataProtector CreateProtector(string purpose);
}
}
}

View File

@ -32,4 +32,4 @@ namespace Microsoft.AspNet.Security.DataProtection
/// <remarks>Throws CryptographicException if the <em>protectedData</em> parameter has been tampered with.</remarks>
byte[] Unprotect(byte[] protectedData);
}
}
}

View File

@ -23,6 +23,8 @@
<ItemGroup>
<Compile Include="Algorithms.cs" />
<Compile Include="BCryptAlgorithmFlags.cs" />
<Compile Include="SuppressUnmanagedCodeSecurityAttribute - Copy.cs" />
<Compile Include="SafeLibraryHandle.cs" />
<Compile Include="BCryptAlgorithmHandle.cs" />
<Compile Include="BCryptBuffer.cs" />
<Compile Include="BCryptBufferDesc.cs" />
@ -34,7 +36,6 @@
<Compile Include="BCryptUtil.cs" />
<Compile Include="BCRYPT_KEY_DATA_BLOB_HEADER.cs" />
<Compile Include="Constants.cs" />
<Compile Include="CryptographicException.cs" />
<Compile Include="CryptRand.cs" />
<Compile Include="DataProtectionProvider.cs" />
<Compile Include="DataProtectionProviderImpl.cs" />
@ -44,11 +45,13 @@
<Compile Include="DpapiDataProtectorImpl.cs" />
<Compile Include="IDataProtectionProvider.cs" />
<Compile Include="IDataProtector.cs" />
<Compile Include="PBKDF2.cs" />
<Compile Include="Properties\Res.Designer.cs" />
<Compile Include="SafeHandleZeroOrMinusOneIsInvalid.cs" />
<Compile Include="SP800_108Helper.cs" />
<Compile Include="UnsafeNativeMethods.cs" />
<Compile Include="Util\BufferUtil.cs" />
<Compile Include="Util\ByteArrayExtensions.cs" />
<Compile Include="Util\MemoryUtil.cs" />
</ItemGroup>
<Import Project="$(VSToolsPath)\ProjectK\Microsoft.Web.ProjectK.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -1,65 +0,0 @@
using System;
using System.Security.Cryptography;
namespace Microsoft.AspNet.Security.DataProtection
{
/// <summary>
/// Helper class to derive keys from low-entropy passwords using the PBKDF2 algorithm.
/// </summary>
public static class PBKDF2
{
/// <summary>
/// Derives a key from a low-entropy password.
/// </summary>
/// <param name="algorithmName">The name of the PRF to use for key derivation.</param>
/// <param name="password">The low-entropy password from which to generate a key.</param>
/// <param name="salt">The salt used to randomize the key derivation.</param>
/// <param name="iterationCount">The number of iterations to perform.</param>
/// <param name="numBytesToDerive">The desired byte length of the derived key.</param>
/// <returns>A key derived from the provided password.</returns>
/// <remarks>For compatibility with the Rfc2898DeriveBytes class, specify "SHA1" for the <em>algorithmName</em> parameter.</remarks>
public unsafe static byte[] DeriveKey(string algorithmName, byte[] password, byte[] salt, ulong iterationCount, uint numBytesToDerive)
{
if (String.IsNullOrEmpty(algorithmName))
{
throw new ArgumentException(Res.Common_NullOrEmpty, "algorithmName");
}
if (password == null || password.Length == 0)
{
throw new ArgumentException(Res.Common_NullOrEmpty, "password");
}
if (salt == null || salt.Length == 0)
{
throw new ArgumentException(Res.Common_NullOrEmpty, "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);
}
}
}
}

View File

@ -0,0 +1,190 @@
using System;
using System.Net;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography;
using Microsoft.AspNet.Security.DataProtection.Util;
using Microsoft.Win32.SafeHandles;
namespace Microsoft.AspNet.Security.DataProtection
{
/// <summary>
/// Provides an implementation of the SP800-108-CTR-HMACSHA512 key derivation function.
/// This class assumes at least Windows 7 / Server 2008 R2.
/// </summary>
/// <remarks>
/// More info at http://csrc.nist.gov/publications/nistpubs/800-108/sp800-108.pdf, Sec. 5.1.
/// </remarks>
internal unsafe static class SP800_108Helper
{
private const string BCRYPT_LIB = "bcrypt.dll";
[SuppressUnmanagedCodeSecurity]
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
// http://msdn.microsoft.com/en-us/library/hh448506(v=vs.85).aspx
private delegate int BCryptKeyDerivation(
[In] BCryptKeyHandle hKey,
[In] BCryptBufferDesc* pParameterList,
[In] byte* pbDerivedKey,
[In] uint cbDerivedKey,
[Out] out uint pcbResult,
[In] uint dwFlags);
private static readonly BCryptAlgorithmHandle SP800108AlgorithmHandle;
private delegate void DeriveKeysDelegate(byte* pKdk, int kdkByteLength, byte[] purpose, byte* pOutputBuffer, uint outputBufferByteLength);
private static DeriveKeysDelegate _thunk = CreateThunk(out SP800108AlgorithmHandle);
private static BCryptAlgorithmHandle CreateSP800108AlgorithmHandle()
{
// create the SP800-108 instance
BCryptAlgorithmHandle algHandle;
int status = UnsafeNativeMethods.BCryptOpenAlgorithmProvider(out algHandle, Constants.BCRYPT_SP800108_CTR_HMAC_ALGORITHM, Constants.MS_PRIMITIVE_PROVIDER, dwFlags: 0);
if (status != 0 || algHandle == null || algHandle.IsInvalid)
{
throw new CryptographicException(status);
}
return algHandle;
}
private static DeriveKeysDelegate CreateThunk(out BCryptAlgorithmHandle sp800108AlgorithmHandle)
{
SafeLibraryHandle bcryptLibHandle = SafeLibraryHandle.Open(BCRYPT_LIB);
var win8Thunk = bcryptLibHandle.GetProcAddress<BCryptKeyDerivation>("BCryptKeyDerivation", throwIfNotFound: false);
if (win8Thunk != null)
{
// Permanently reference bcrypt.dll for the lifetime of the AppDomain.
// When the AD goes away the SafeLibraryHandle will automatically be released.
GCHandle.Alloc(bcryptLibHandle);
sp800108AlgorithmHandle = CreateSP800108AlgorithmHandle();
return win8Thunk.DeriveKeysWin8;
}
else
{
sp800108AlgorithmHandle = null;
return DeriveKeysWin7;
}
}
/// <summary>
/// Performs a key derivation using SP800-108-CTR-HMACSHA512.
/// </summary>
/// <param name="pKdk">Pointer to the key derivation key.</param>
/// <param name="kdkByteLength">Length (in bytes) of the key derivation key.</param>
/// <param name="purpose">Purpose to attach to the generated subkey. Corresponds to the 'Label' parameter
/// in the KDF. May be null.</param>
/// <param name="pOutputBuffer">Pointer to a buffer which will receive the subkey.</param>
/// <param name="outputBufferByteLength">Length (in bytes) of the output buffer.</param>
public static void DeriveKeys(byte* pKdk, int kdkByteLength, byte[] purpose, byte* pOutputBuffer, uint outputBufferByteLength)
{
_thunk(pKdk, kdkByteLength, purpose, pOutputBuffer, outputBufferByteLength);
}
// Wraps our own SP800-108 implementation around bcrypt.dll primitives.
private static void DeriveKeysWin7(byte* pKdk, int kdkByteLength, byte[] purpose, byte* pOutputBuffer, uint outputBufferByteLength)
{
const int TEMP_RESULT_OUTPUT_BYTES = 512 / 8; // hardcoded to HMACSHA512
// NOTE: pOutputBuffer and outputBufferByteLength are modified as data is copied from temporary buffers
// to the final output buffer.
// used to hold the output of the HMACSHA512 routine
byte* pTempResultBuffer = stackalloc byte[TEMP_RESULT_OUTPUT_BYTES];
int purposeLength = (purpose != null) ? purpose.Length : 0;
// this will be zero-inited
byte[] dataToBeHashed = new byte[checked(
sizeof(int) /* [i] */
+ purposeLength /* Label */
+ 1 /* 0x00 */
+ 0 /* Context */
+ sizeof(int) /* [L] */)];
fixed (byte* pDataToBeHashed = dataToBeHashed)
{
// Step 1: copy purpose into Label part of data to be hashed
if (purposeLength > 0)
{
fixed (byte* pPurpose = purpose)
{
BufferUtil.BlockCopy(from: pPurpose, to: &pDataToBeHashed[sizeof(int)], byteCount: purposeLength);
}
}
// Step 2: copy [L] into last part of data to be hashed, big-endian
uint numBitsToGenerate = checked(outputBufferByteLength * 8);
MemoryUtil.UnalignedWriteBigEndian(&pDataToBeHashed[dataToBeHashed.Length - sizeof(int)], numBitsToGenerate);
// Step 3: iterate until all desired bytes have been generated
for (int i = 1; outputBufferByteLength > 0; i++)
{
// Step 3a: Copy [i] into the first part of data to be hashed, big-endian
MemoryUtil.UnalignedWriteBigEndian(pDataToBeHashed, (uint)i);
// Step 3b: Hash. Win7 doesn't allow reusing hash algorithm objects after the final hash
// has been computed, so we need to create a new instance of the hash object for each
// iteration. We don't bother with this optimization on Win8 since we call BCryptKeyDerivation
// instead when on that OS.
using (var hashHandle = BCryptUtil.CreateHMACHandle(Algorithms.HMACSHA512AlgorithmHandle, pKdk, kdkByteLength))
{
BCryptUtil.HashData(hashHandle, pDataToBeHashed, dataToBeHashed.Length, pTempResultBuffer, TEMP_RESULT_OUTPUT_BYTES);
}
// Step 3c: Copy bytes from the temporary buffer to the output buffer.
uint numBytesToCopy = Math.Min(outputBufferByteLength, (uint)TEMP_RESULT_OUTPUT_BYTES);
BufferUtil.BlockCopy(from: pTempResultBuffer, to: pOutputBuffer, byteCount: numBytesToCopy);
pOutputBuffer += numBytesToCopy;
outputBufferByteLength -= numBytesToCopy;
}
}
}
// Calls into the Win8 implementation (bcrypt.dll) for the SP800-108 KDF
private static void DeriveKeysWin8(this BCryptKeyDerivation fnKeyDerivation, byte* pKdk, int kdkByteLength, byte[] purpose, byte* pOutputBuffer, uint outputBufferByteLength)
{
// Create a buffer to hold the hash algorithm name
fixed (char* pszPrfAlgorithmName = Constants.BCRYPT_SHA512_ALGORITHM)
{
BCryptBuffer* pBCryptBuffers = stackalloc BCryptBuffer[2];
// The first buffer should contain the PRF algorithm name (hardcoded to HMACSHA512).
// Per http://msdn.microsoft.com/en-us/library/aa375368(v=vs.85).aspx, cbBuffer must include the terminating null char.
pBCryptBuffers[0].BufferType = BCryptKeyDerivationBufferType.KDF_HASH_ALGORITHM;
pBCryptBuffers[0].pvBuffer = (IntPtr)pszPrfAlgorithmName;
pBCryptBuffers[0].cbBuffer = (uint)((Constants.BCRYPT_SHA512_ALGORITHM.Length + 1) * sizeof(char));
uint numBuffers = 1;
fixed (byte* pPurpose = ((purpose != null && purpose.Length != 0) ? purpose : null))
{
if (pPurpose != null)
{
// The second buffer will hold the purpose bytes if they're specified.
pBCryptBuffers[1].BufferType = BCryptKeyDerivationBufferType.KDF_LABEL;
pBCryptBuffers[1].pvBuffer = (IntPtr)pPurpose;
pBCryptBuffers[1].cbBuffer = (uint)purpose.Length;
numBuffers = 2;
}
// Add the header
BCryptBufferDesc bufferDesc = default(BCryptBufferDesc);
BCryptBufferDesc.Initialize(ref bufferDesc);
bufferDesc.cBuffers = numBuffers;
bufferDesc.pBuffers = pBCryptBuffers;
// Finally, perform the calculation and validate that the actual number of bytes derived matches
// the number that the caller requested.
uint numBytesDerived;
int status;
using (BCryptKeyHandle kdkHandle = BCryptUtil.ImportKey(SP800108AlgorithmHandle, pKdk, kdkByteLength))
{
status = fnKeyDerivation(kdkHandle, &bufferDesc, pOutputBuffer, outputBufferByteLength, out numBytesDerived, dwFlags: 0);
}
if (status != 0 || numBytesDerived != outputBufferByteLength)
{
throw new CryptographicException(status);
}
}
}
}
}
}

View File

@ -0,0 +1,121 @@
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security;
#if NET45
using System.Runtime.ConstrainedExecution;
#endif
namespace Microsoft.Win32.SafeHandles
{
/// <summary>
/// Represents a handle to a Windows module (DLL).
/// </summary>
internal sealed class SafeLibraryHandle : SafeHandleZeroOrMinusOneIsInvalid
{
// Called by P/Invoke when returning SafeHandles
private SafeLibraryHandle()
: base(ownsHandle: true) { }
/// <summary>
/// Gets a delegate pointing to a given export from this library.
/// </summary>
public TDelegate GetProcAddress<TDelegate>(string lpProcName, bool throwIfNotFound = true) where TDelegate : class
{
Debug.Assert(typeof(TDelegate).GetTypeInfo().IsSubclassOf(typeof(Delegate)), "TDelegate must be a delegate type!");
IntPtr pfnProc = UnsafeNativeMethods.GetProcAddress(this, lpProcName);
if (pfnProc == IntPtr.Zero)
{
if (throwIfNotFound)
{
UnsafeNativeMethods.ThrowExceptionForLastWin32Error();
}
else
{
return null;
}
}
return (TDelegate)(object)Marshal.GetDelegateForFunctionPointer(pfnProc, typeof(TDelegate));
}
/// <summary>
/// Forbids this library from being unloaded. The library will remain loaded until process termination,
/// regardless of how many times FreeLibrary is called.
/// </summary>
public void ForbidUnload()
{
// from winbase.h
const uint GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS = 0x00000004U;
const uint GET_MODULE_HANDLE_EX_FLAG_PIN = 0x00000001U;
IntPtr unused;
bool retVal = UnsafeNativeMethods.GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_PIN, this, out unused);
if (!retVal)
{
UnsafeNativeMethods.ThrowExceptionForLastWin32Error();
}
}
/// <summary>
/// Opens a library. If 'filename' is not a fully-qualified path, the default search path is used.
/// </summary>
public static SafeLibraryHandle Open(string filename)
{
SafeLibraryHandle handle = UnsafeNativeMethods.LoadLibrary(filename);
if (handle == null || handle.IsInvalid)
{
UnsafeNativeMethods.ThrowExceptionForLastWin32Error();
}
return handle;
}
// Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you.
protected override bool ReleaseHandle()
{
return UnsafeNativeMethods.FreeLibrary(handle);
}
[SuppressUnmanagedCodeSecurity]
private static class UnsafeNativeMethods
{
private const string KERNEL32_LIB = "kernel32.dll";
// http://msdn.microsoft.com/en-us/library/ms683152(v=vs.85).aspx
[return: MarshalAs(UnmanagedType.Bool)]
#if NET45
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
#endif
[DllImport(KERNEL32_LIB, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode)]
internal static extern bool FreeLibrary(IntPtr hModule);
// http://msdn.microsoft.com/en-us/library/ms683200(v=vs.85).aspx
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport(KERNEL32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
internal static extern bool GetModuleHandleEx(
[In] uint dwFlags,
[In] SafeLibraryHandle lpModuleName, // can point to a location within the module if GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS is set
[Out] out IntPtr phModule);
// http://msdn.microsoft.com/en-us/library/ms683212(v=vs.85).aspx
[DllImport(KERNEL32_LIB, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
internal static extern IntPtr GetProcAddress(
[In] SafeLibraryHandle hModule,
[In, MarshalAs(UnmanagedType.LPStr)] string lpProcName);
// http://msdn.microsoft.com/en-us/library/ms684175(v=vs.85).aspx
[DllImport(KERNEL32_LIB, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern SafeLibraryHandle LoadLibrary(
[In, MarshalAs(UnmanagedType.LPWStr)]string lpFileName);
internal static void ThrowExceptionForLastWin32Error()
{
int hr = Marshal.GetHRForLastWin32Error();
Marshal.ThrowExceptionForHR(hr);
}
}
}
}

View File

@ -0,0 +1,10 @@
using System;
using System.Runtime.InteropServices;
#if !NET45
namespace System.Security
{
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
internal sealed class SuppressUnmanagedCodeSecurityAttribute : Attribute { }
}
#endif

View File

@ -1,17 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Security.DataProtection
{
#if NET45
[SuppressUnmanagedCodeSecurity]
#endif
internal static unsafe class UnsafeNativeMethods
internal unsafe static class UnsafeNativeMethods
{
private const string BCRYPT_LIB = "bcrypt.dll";
private const string CRYPT32_LIB = "crypt32.dll";
@ -52,19 +46,6 @@ 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(
@ -177,6 +158,13 @@ namespace Microsoft.AspNet.Security.DataProtection
[In] uint dwFlags,
[Out] out DATA_BLOB pDataOut);
[DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa380262(v=vs.85).aspx
internal static extern bool CryptProtectMemory(
[In] byte* pData,
[In] uint cbData,
[In] uint dwFlags);
[DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa380882(v=vs.85).aspx
internal static extern bool CryptUnprotectData(
@ -188,6 +176,13 @@ namespace Microsoft.AspNet.Security.DataProtection
[In] uint dwFlags,
[Out] out DATA_BLOB pDataOut);
[DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa380890(v=vs.85).aspx
internal static extern bool CryptUnprotectMemory(
[In] byte* pData,
[In] uint cbData,
[In] uint dwFlags);
/*
* KERNEL32.DLL
*/
@ -197,4 +192,4 @@ namespace Microsoft.AspNet.Security.DataProtection
[In] IntPtr Destination,
[In] UIntPtr /* SIZE_T */ Length);
}
}
}

View File

@ -1,40 +1,67 @@
using System;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
namespace Microsoft.AspNet.Security.DataProtection.Util
{
internal static unsafe class BufferUtil
internal unsafe static class BufferUtil
{
private static readonly byte[] _emptyArray = new byte[0];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void BlockCopy(IntPtr from, IntPtr to, int byteCount)
public static void BlockCopy(void* from, void* to, int byteCount)
{
BlockCopy(from, to, checked((uint) byteCount)); // will be checked before invoking the delegate
BlockCopy(from, to, checked((uint)byteCount)); // will be checked before invoking the delegate
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void BlockCopy(IntPtr from, IntPtr to, uint byteCount)
public static void BlockCopy(void* from, void* to, uint byteCount)
{
BlockCopySlow((byte*) from, (byte*) to, byteCount);
if (byteCount != 0)
{
#if NET45
BlockCopySlow((byte*)from, (byte*)to, byteCount);
#else
Buffer.MemoryCopy(source: from, destination: to, destinationSizeInBytes: byteCount, sourceBytesToCopy: byteCount);
#endif
}
}
#if NET45
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void BlockCopySlow(byte* from, byte* to, uint byteCount)
public static void BlockCopySlow(byte* from, byte* to, uint byteCount)
{
// slow, but works
while (byteCount-- != 0)
{
*(to++) = *(from++);
}
}
#endif
/// <summary>
/// Securely clears a memory buffer.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SecureZeroMemory(byte* buffer, int byteCount)
{
SecureZeroMemory(buffer, checked((uint)byteCount));
}
/// <summary>
/// Securely clears a memory buffer.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SecureZeroMemory(byte* buffer, uint byteCount)
{
UnsafeNativeMethods.RtlZeroMemory((IntPtr)buffer, (UIntPtr)byteCount);
}
/// <summary>
/// Creates a new managed byte[] from unmanaged memory.
/// </summary>
public static byte[] ToManagedByteArray(byte* ptr, int byteCount)
{
return ToManagedByteArray(ptr, checked((uint) byteCount));
return ToManagedByteArray(ptr, checked((uint)byteCount));
}
/// <summary>
@ -51,28 +78,33 @@ namespace Microsoft.AspNet.Security.DataProtection.Util
byte[] bytes = new byte[byteCount];
fixed (byte* pBytes = bytes)
{
BlockCopy(from: (IntPtr) ptr, to: (IntPtr) pBytes, byteCount: byteCount);
BlockCopy(from: ptr, to: pBytes, byteCount: byteCount);
}
return bytes;
}
}
/// <summary>
/// Clears a memory buffer.
/// Creates a new managed byte[] from unmanaged memory. The returned value will be protected
/// by CryptProtectMemory.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ZeroMemory(byte* buffer, int byteCount)
public static byte[] ToProtectedManagedByteArray(byte* ptr, int byteCount)
{
ZeroMemory(buffer, checked((uint) byteCount));
}
/// <summary>
/// Clears a memory buffer.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ZeroMemory(byte* buffer, uint byteCount)
{
UnsafeNativeMethods.RtlZeroMemory((IntPtr) buffer, (UIntPtr) byteCount); // don't require 'checked': uint -> UIntPtr always guaranteed to succeed
byte[] bytes = new byte[byteCount];
fixed (byte* pBytes = bytes)
{
try
{
BlockCopy(from: ptr, to: pBytes, byteCount: byteCount);
BCryptUtil.ProtectMemoryWithinThisProcess(pBytes, (uint)byteCount);
}
catch
{
SecureZeroMemory(pBytes, byteCount);
throw;
}
}
return bytes;
}
}
}
}

View File

@ -0,0 +1,23 @@
using System;
using System.Diagnostics;
namespace Microsoft.AspNet.Security.DataProtection.Util
{
/// <summary>
/// Defines helper methods for working with fixed expression blocks.
/// </summary>
internal static class ByteArrayExtensions
{
private static readonly byte[] _dummyBuffer = new byte[1];
// Since the 'fixed' keyword turns a zero-length array into a pointer, we need
// to make sure we're always providing a buffer of length >= 1 so that the
// p/invoke methods we pass the pointers to don't see a null pointer. Callers
// are still responsible for passing a proper length to the p/invoke routines.
public static byte[] AsFixed(this byte[] buffer)
{
Debug.Assert(buffer != null);
return (buffer.Length != 0) ? buffer : _dummyBuffer;
}
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Runtime.CompilerServices;
namespace Microsoft.AspNet.Security.DataProtection.Util
{
internal unsafe static class MemoryUtil
{
/// <summary>
/// Writes an Int32 to a potentially unaligned memory address, big-endian.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void UnalignedWriteBigEndian(byte* address, uint value)
{
*(address++) = (byte)(value >> 24);
*(address++) = (byte)(value >> 16);
*(address++) = (byte)(value >> 8);
*(address) = (byte)value;
}
}
}

View File

@ -11,7 +11,10 @@
"System.Reflection": "4.0.10.0",
"System.Resources.ResourceManager": "4.0.0.0",
"System.Runtime": "4.0.20.0",
"System.Runtime.InteropServices": "4.0.20.0"
"System.Runtime.Extensions": "4.0.10.0",
"System.Runtime.InteropServices": "4.0.20.0",
"System.Security.Cryptography": "4.0.0.0",
"System.Text.Encoding.Extensions": "4.0.10.0"
}
}
},