diff --git a/src/Microsoft.AspNet.Security.DataProtection/Algorithms.cs b/src/Microsoft.AspNet.Security.DataProtection/Algorithms.cs
new file mode 100644
index 0000000000..850a7415f7
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/Algorithms.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Security.Cryptography;
+
+namespace Microsoft.AspNet.Security.DataProtection {
+
+ 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() {
+ // create the AES instance
+ BCryptAlgorithmHandle algHandle;
+ int status = UnsafeNativeMethods.BCryptOpenAlgorithmProvider(out algHandle, Constants.BCRYPT_AES_ALGORITHM, Constants.MS_PRIMITIVE_PROVIDER, dwFlags: 0);
+ if (status != 0 || algHandle == null || algHandle.IsInvalid) {
+ throw new CryptographicException(status);
+ }
+
+ // 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);
+ }
+ if (status != 0) {
+ throw new CryptographicException(status);
+ }
+
+ return algHandle;
+ }
+ private static BCryptAlgorithmHandle CreateHMACSHA256AlgorithmHandle() {
+ // 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);
+ if (status != 0 || algHandle == null || algHandle.IsInvalid) {
+ throw new CryptographicException(status);
+ }
+
+ return algHandle;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/BCRYPT_KEY_DATA_BLOB_HEADER.cs b/src/Microsoft.AspNet.Security.DataProtection/BCRYPT_KEY_DATA_BLOB_HEADER.cs
new file mode 100644
index 0000000000..25fbecc1d2
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/BCRYPT_KEY_DATA_BLOB_HEADER.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.AspNet.Security.DataProtection {
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375524(v=vs.85).aspx
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct BCRYPT_KEY_DATA_BLOB_HEADER {
+ // from bcrypt.h
+ private const uint BCRYPT_KEY_DATA_BLOB_MAGIC = 0x4d42444b; //Key Data Blob Magic (KDBM)
+ private const uint BCRYPT_KEY_DATA_BLOB_VERSION1 = 0x1;
+
+ public uint dwMagic;
+ public uint dwVersion;
+ public uint cbKeyData;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Initialize(ref BCRYPT_KEY_DATA_BLOB_HEADER pHeader) {
+ pHeader.dwMagic = BCRYPT_KEY_DATA_BLOB_MAGIC;
+ pHeader.dwVersion = BCRYPT_KEY_DATA_BLOB_VERSION1;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/BCryptAlgorithmFlags.cs b/src/Microsoft.AspNet.Security.DataProtection/BCryptAlgorithmFlags.cs
new file mode 100644
index 0000000000..aa091e9c25
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/BCryptAlgorithmFlags.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Microsoft.AspNet.Security.DataProtection {
+ // from bcrypt.h
+ [Flags]
+ internal enum BCryptAlgorithmFlags {
+ BCRYPT_ALG_HANDLE_HMAC_FLAG = 0x00000008,
+ BCRYPT_CAPI_AES_FLAG = 0x00000010,
+ BCRYPT_HASH_REUSABLE_FLAG = 0x00000020,
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/BCryptAlgorithmHandle.cs b/src/Microsoft.AspNet.Security.DataProtection/BCryptAlgorithmHandle.cs
new file mode 100644
index 0000000000..6346215a9a
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/BCryptAlgorithmHandle.cs
@@ -0,0 +1,16 @@
+using System;
+using Microsoft.Win32.SafeHandles;
+
+namespace Microsoft.AspNet.Security.DataProtection {
+ internal sealed class BCryptAlgorithmHandle : SafeHandleZeroOrMinusOneIsInvalid {
+ // Called by P/Invoke when returning SafeHandles
+ private BCryptAlgorithmHandle()
+ : base(ownsHandle: true) {
+ }
+
+ // Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you.
+ protected override bool ReleaseHandle() {
+ return (UnsafeNativeMethods.BCryptCloseAlgorithmProvider(handle, dwFlags: 0) == 0);
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/BCryptBuffer.cs b/src/Microsoft.AspNet.Security.DataProtection/BCryptBuffer.cs
new file mode 100644
index 0000000000..3ffbc5156f
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/BCryptBuffer.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.AspNet.Security.DataProtection {
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375368(v=vs.85).aspx
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct BCryptBuffer {
+ public uint cbBuffer; // Length of buffer, in bytes
+ public BCryptKeyDerivationBufferType BufferType; // Buffer type
+ public IntPtr pvBuffer; // Pointer to buffer
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/BCryptBufferDesc.cs b/src/Microsoft.AspNet.Security.DataProtection/BCryptBufferDesc.cs
new file mode 100644
index 0000000000..0e5336f496
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/BCryptBufferDesc.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.AspNet.Security.DataProtection {
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375370(v=vs.85).aspx
+ [StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct BCryptBufferDesc {
+ private const int BCRYPTBUFFER_VERSION = 0;
+
+ public uint ulVersion; // Version number
+ public uint cBuffers; // Number of buffers
+ public BCryptBuffer* pBuffers; // Pointer to array of buffers
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Initialize(ref BCryptBufferDesc bufferDesc) {
+ bufferDesc.ulVersion = BCRYPTBUFFER_VERSION;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/BCryptEncryptFlags.cs b/src/Microsoft.AspNet.Security.DataProtection/BCryptEncryptFlags.cs
new file mode 100644
index 0000000000..55a2af8300
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/BCryptEncryptFlags.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Microsoft.AspNet.Security.DataProtection {
+ // from bcrypt.h
+ [Flags]
+ internal enum BCryptEncryptFlags {
+ BCRYPT_BLOCK_PADDING = 0x00000001,
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/BCryptGenRandomFlags.cs b/src/Microsoft.AspNet.Security.DataProtection/BCryptGenRandomFlags.cs
new file mode 100644
index 0000000000..250d7e0ee6
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/BCryptGenRandomFlags.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace Microsoft.AspNet.Security.DataProtection {
+ // from bcrypt.h
+ [Flags]
+ internal enum BCryptGenRandomFlags {
+ BCRYPT_RNG_USE_ENTROPY_IN_BUFFER = 0x00000001,
+ BCRYPT_USE_SYSTEM_PREFERRED_RNG = 0x00000002,
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/BCryptHashHandle.cs b/src/Microsoft.AspNet.Security.DataProtection/BCryptHashHandle.cs
new file mode 100644
index 0000000000..317a5f4bf1
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/BCryptHashHandle.cs
@@ -0,0 +1,16 @@
+using System;
+using Microsoft.Win32.SafeHandles;
+
+namespace Microsoft.AspNet.Security.DataProtection {
+ internal sealed class BCryptHashHandle : SafeHandleZeroOrMinusOneIsInvalid {
+ // Called by P/Invoke when returning SafeHandles
+ private BCryptHashHandle()
+ : base(ownsHandle: true) {
+ }
+
+ // Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you.
+ protected override bool ReleaseHandle() {
+ return (UnsafeNativeMethods.BCryptDestroyHash(handle) == 0);
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/BCryptKeyDerivationBufferType.cs b/src/Microsoft.AspNet.Security.DataProtection/BCryptKeyDerivationBufferType.cs
new file mode 100644
index 0000000000..0fb2e84347
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/BCryptKeyDerivationBufferType.cs
@@ -0,0 +1,24 @@
+using System;
+
+namespace Microsoft.AspNet.Security.DataProtection {
+ // from bcrypt.h
+ internal enum BCryptKeyDerivationBufferType {
+ KDF_HASH_ALGORITHM = 0x0,
+ KDF_SECRET_PREPEND = 0x1,
+ KDF_SECRET_APPEND = 0x2,
+ KDF_HMAC_KEY = 0x3,
+ KDF_TLS_PRF_LABEL = 0x4,
+ KDF_TLS_PRF_SEED = 0x5,
+ KDF_SECRET_HANDLE = 0x6,
+ KDF_TLS_PRF_PROTOCOL = 0x7,
+ KDF_ALGORITHMID = 0x8,
+ KDF_PARTYUINFO = 0x9,
+ KDF_PARTYVINFO = 0xA,
+ KDF_SUPPPUBINFO = 0xB,
+ KDF_SUPPPRIVINFO = 0xC,
+ KDF_LABEL = 0xD,
+ KDF_CONTEXT = 0xE,
+ KDF_SALT = 0xF,
+ KDF_ITERATION_COUNT = 0x10,
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/BCryptKeyHandle.cs b/src/Microsoft.AspNet.Security.DataProtection/BCryptKeyHandle.cs
new file mode 100644
index 0000000000..cd6e48fc88
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/BCryptKeyHandle.cs
@@ -0,0 +1,16 @@
+using System;
+using Microsoft.Win32.SafeHandles;
+
+namespace Microsoft.AspNet.Security.DataProtection {
+ internal sealed class BCryptKeyHandle : SafeHandleZeroOrMinusOneIsInvalid {
+ // Called by P/Invoke when returning SafeHandles
+ private BCryptKeyHandle()
+ : base(ownsHandle: true) {
+ }
+
+ // Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you.
+ protected override bool ReleaseHandle() {
+ return (UnsafeNativeMethods.BCryptDestroyKey(handle) == 0);
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/BCryptUtil.cs b/src/Microsoft.AspNet.Security.DataProtection/BCryptUtil.cs
new file mode 100644
index 0000000000..4ba8237436
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/BCryptUtil.cs
@@ -0,0 +1,221 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Security.Cryptography;
+using Microsoft.AspNet.Security.DataProtection.Util;
+
+namespace Microsoft.AspNet.Security.DataProtection {
+ internal unsafe static class BCryptUtil {
+
+ // constant-time buffer comparison
+ [MethodImpl(MethodImplOptions.NoOptimization)]
+ public static bool BuffersAreEqualSecure(byte* p1, byte* p2, uint count) {
+ bool retVal = true;
+ while (count-- > 0) {
+ retVal &= (*(p1++) == *(p2++));
+ }
+ return retVal;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void CheckOverflowUnderflow(int input) {
+ var unused = checked((uint)input);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void CheckOverflowUnderflow(uint input) {
+ var unused = checked((int)input);
+ }
+
+ // helper function to wrap BCryptCreateHash
+ public static BCryptHashHandle CreateHash(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);
+ if (status != 0 || retVal == null || retVal.IsInvalid) {
+ throw new CryptographicException(status);
+ }
+
+ return retVal;
+ }
+
+ // helper function to wrap BCryptEncrypt; returns number of bytes written to 'output'
+ // assumes the output buffer is large enough to hold the ciphertext + any necessary padding
+ public static int DecryptWithPadding(BCryptKeyHandle keyHandle, byte* input, int inputLength, byte* iv, int ivLength, byte* output, int outputLength) {
+ CheckOverflowUnderflow(inputLength);
+ CheckOverflowUnderflow(ivLength);
+ CheckOverflowUnderflow(outputLength);
+
+ // BCryptEncrypt destroys the 'iv' parameter, so we need to pass a duplicate instead of the original
+ if (ivLength > Constants.MAX_STACKALLOC_BYTES) {
+ throw new InvalidOperationException();
+ }
+ byte* pDuplicatedIV = stackalloc byte[ivLength];
+ BufferUtil.BlockCopy(from: (IntPtr)iv, to: (IntPtr)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);
+ if (status != 0) {
+ throw new CryptographicException(status);
+ }
+
+ 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) {
+ 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
+ byte* pBuffer = stackalloc byte[TOTAL_NUM_BYTES_TO_DERIVE]; // will be freed with frame pops
+ byte* pNewEncryptionKey = pBuffer;
+ byte* pNewHmacKey = &pNewEncryptionKey[ENCRYPTION_KEY_SIZE_IN_BYTES];
+ byte* pNewKdfSubkey = &pNewHmacKey[HMAC_KEY_SIZE_IN_BYTES];
+
+ try {
+ fixed (char* pszPrfAlgorithmName = Constants.BCRYPT_SHA512_ALGORITHM) {
+ // 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)));
+ }
+
+ // .. and the header ..
+ BCryptBufferDesc bufferDesc = default(BCryptBufferDesc);
+ BCryptBufferDesc.Initialize(ref bufferDesc);
+ bufferDesc.cBuffers = numBuffers;
+ bufferDesc.pBuffers = pBCryptBuffers;
+
+ 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);
+ }
+ }
+ }
+
+ // 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);
+ }
+ }
+
+ // helper function to wrap BCryptDuplicateHash
+ public static BCryptHashHandle DuplicateHash(BCryptHashHandle hashHandle) {
+ BCryptHashHandle retVal;
+ int status = UnsafeNativeMethods.BCryptDuplicateHash(hashHandle, out retVal, IntPtr.Zero, 0, dwFlags: 0);
+ if (status != 0 || retVal == null || retVal.IsInvalid) {
+ throw new CryptographicException(status);
+ }
+
+ return retVal;
+ }
+
+ // helper function to wrap BCryptEncrypt; returns number of bytes written to 'output'
+ // assumes the output buffer is large enough to hold the ciphertext + any necessary padding
+ public static int EncryptWithPadding(BCryptKeyHandle keyHandle, byte* input, int inputLength, byte* iv, int ivLength, byte* output, int outputLength) {
+ CheckOverflowUnderflow(inputLength);
+ CheckOverflowUnderflow(ivLength);
+ CheckOverflowUnderflow(outputLength);
+
+ // BCryptEncrypt destroys the 'iv' parameter, so we need to pass a duplicate instead of the original
+ if (ivLength > Constants.MAX_STACKALLOC_BYTES) {
+ throw new InvalidOperationException();
+ }
+ byte* pDuplicatedIV = stackalloc byte[ivLength];
+ BufferUtil.BlockCopy(from: (IntPtr)iv, to: (IntPtr)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);
+ if (status != 0) {
+ throw new CryptographicException(status);
+ }
+
+ return checked((int)retVal);
+ }
+
+ // helper function that's similar to RNGCryptoServiceProvider, but works directly with pointers
+ public static void GenRandom(byte* buffer, int bufferBytes) {
+ CheckOverflowUnderflow(bufferBytes);
+
+ int status = UnsafeNativeMethods.BCryptGenRandom(IntPtr.Zero, buffer, (uint)bufferBytes, BCryptGenRandomFlags.BCRYPT_USE_SYSTEM_PREFERRED_RNG);
+ if (status != 0) {
+ throw new CryptographicException(status);
+ }
+ }
+
+ // helper function that wraps BCryptHashData / BCryptFinishHash
+ public static void HashData(BCryptHashHandle hashHandle, byte* input, int inputBytes, byte* output, int outputBytes) {
+ CheckOverflowUnderflow(inputBytes);
+ CheckOverflowUnderflow(outputBytes);
+
+ 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);
+ if (status != 0) {
+ throw new CryptographicException(status);
+ }
+ }
+
+ // helper function that wraps BCryptImportKey with a key data blob
+ public static BCryptKeyHandle ImportKey(BCryptAlgorithmHandle algHandle, byte* key, int keyBytes) {
+ CheckOverflowUnderflow(keyBytes);
+
+ byte[] heapAllocatedKeyDataBlob = null;
+ 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
+ }
+
+ int status;
+ BCryptKeyHandle retVal;
+ fixed (byte* pHeapAllocatedKeyDataBlob = heapAllocatedKeyDataBlob) {
+ // The header is first
+ 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;
+ }
+ BCRYPT_KEY_DATA_BLOB_HEADER.Initialize(ref *pKeyDataBlobHeader);
+ pKeyDataBlobHeader->cbKeyData = (uint)keyBytes;
+
+ // the raw material immediately follows the header
+ 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);
+ }
+ finally {
+ // zero out the key we just copied
+ BufferUtil.ZeroMemory(pKeyDataRawMaterial, keyBytes);
+ }
+ }
+
+ if (status != 0 || retVal == null || retVal.IsInvalid) {
+ throw new CryptographicException(status);
+ }
+ return retVal;
+ }
+
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/Constants.cs b/src/Microsoft.AspNet.Security.DataProtection/Constants.cs
new file mode 100644
index 0000000000..c6ca8cbb7c
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/Constants.cs
@@ -0,0 +1,83 @@
+using System;
+
+namespace Microsoft.AspNet.Security.DataProtection {
+ // from bcrypt.h
+ internal static class Constants {
+ internal const int MAX_STACKALLOC_BYTES = 256; // greatest number of bytes that we'll ever allow to stackalloc in a single frame
+
+ // BCrypt(Import/Export)Key BLOB types
+ internal const string BCRYPT_OPAQUE_KEY_BLOB = "OpaqueKeyBlob";
+ internal const string BCRYPT_KEY_DATA_BLOB = "KeyDataBlob";
+ internal const string BCRYPT_AES_WRAP_KEY_BLOB = "Rfc3565KeyWrapBlob";
+
+ // Microsoft built-in providers.
+ internal const string MS_PRIMITIVE_PROVIDER = "Microsoft Primitive Provider";
+ internal const string MS_PLATFORM_CRYPTO_PROVIDER = "Microsoft Platform Crypto Provider";
+
+ // Common algorithm identifiers.
+ internal const string BCRYPT_RSA_ALGORITHM = "RSA";
+ internal const string BCRYPT_RSA_SIGN_ALGORITHM = "RSA_SIGN";
+ internal const string BCRYPT_DH_ALGORITHM = "DH";
+ internal const string BCRYPT_DSA_ALGORITHM = "DSA";
+ internal const string BCRYPT_RC2_ALGORITHM = "RC2";
+ internal const string BCRYPT_RC4_ALGORITHM = "RC4";
+ internal const string BCRYPT_AES_ALGORITHM = "AES";
+ internal const string BCRYPT_DES_ALGORITHM = "DES";
+ internal const string BCRYPT_DESX_ALGORITHM = "DESX";
+ internal const string BCRYPT_3DES_ALGORITHM = "3DES";
+ internal const string BCRYPT_3DES_112_ALGORITHM = "3DES_112";
+ internal const string BCRYPT_MD2_ALGORITHM = "MD2";
+ internal const string BCRYPT_MD4_ALGORITHM = "MD4";
+ internal const string BCRYPT_MD5_ALGORITHM = "MD5";
+ internal const string BCRYPT_SHA1_ALGORITHM = "SHA1";
+ internal const string BCRYPT_SHA256_ALGORITHM = "SHA256";
+ internal const string BCRYPT_SHA384_ALGORITHM = "SHA384";
+ internal const string BCRYPT_SHA512_ALGORITHM = "SHA512";
+ internal const string BCRYPT_AES_GMAC_ALGORITHM = "AES-GMAC";
+ internal const string BCRYPT_AES_CMAC_ALGORITHM = "AES-CMAC";
+ internal const string BCRYPT_ECDSA_P256_ALGORITHM = "ECDSA_P256";
+ internal const string BCRYPT_ECDSA_P384_ALGORITHM = "ECDSA_P384";
+ internal const string BCRYPT_ECDSA_P521_ALGORITHM = "ECDSA_P521";
+ internal const string BCRYPT_ECDH_P256_ALGORITHM = "ECDH_P256";
+ internal const string BCRYPT_ECDH_P384_ALGORITHM = "ECDH_P384";
+ internal const string BCRYPT_ECDH_P521_ALGORITHM = "ECDH_P521";
+ internal const string BCRYPT_RNG_ALGORITHM = "RNG";
+ internal const string BCRYPT_RNG_FIPS186_DSA_ALGORITHM = "FIPS186DSARNG";
+ internal const string BCRYPT_RNG_DUAL_EC_ALGORITHM = "DUALECRNG";
+ internal const string BCRYPT_SP800108_CTR_HMAC_ALGORITHM = "SP800_108_CTR_HMAC";
+ internal const string BCRYPT_SP80056A_CONCAT_ALGORITHM = "SP800_56A_CONCAT";
+ internal const string BCRYPT_PBKDF2_ALGORITHM = "PBKDF2";
+ internal const string BCRYPT_CAPI_KDF_ALGORITHM = "CAPI_KDF";
+
+ // BCryptGetProperty strings
+ internal const string BCRYPT_OBJECT_LENGTH = "ObjectLength";
+ internal const string BCRYPT_ALGORITHM_NAME = "AlgorithmName";
+ internal const string BCRYPT_PROVIDER_HANDLE = "ProviderHandle";
+ internal const string BCRYPT_CHAINING_MODE = "ChainingMode";
+ internal const string BCRYPT_BLOCK_LENGTH = "BlockLength";
+ internal const string BCRYPT_KEY_LENGTH = "KeyLength";
+ internal const string BCRYPT_KEY_OBJECT_LENGTH = "KeyObjectLength";
+ internal const string BCRYPT_KEY_STRENGTH = "KeyStrength";
+ internal const string BCRYPT_KEY_LENGTHS = "KeyLengths";
+ internal const string BCRYPT_BLOCK_SIZE_LIST = "BlockSizeList";
+ internal const string BCRYPT_EFFECTIVE_KEY_LENGTH = "EffectiveKeyLength";
+ internal const string BCRYPT_HASH_LENGTH = "HashDigestLength";
+ internal const string BCRYPT_HASH_OID_LIST = "HashOIDList";
+ internal const string BCRYPT_PADDING_SCHEMES = "PaddingSchemes";
+ internal const string BCRYPT_SIGNATURE_LENGTH = "SignatureLength";
+ internal const string BCRYPT_HASH_BLOCK_LENGTH = "HashBlockLength";
+ internal const string BCRYPT_AUTH_TAG_LENGTH = "AuthTagLength";
+ internal const string BCRYPT_PRIMITIVE_TYPE = "PrimitiveType";
+ internal const string BCRYPT_IS_KEYED_HASH = "IsKeyedHash";
+ internal const string BCRYPT_IS_REUSABLE_HASH = "IsReusableHash";
+ internal const string BCRYPT_MESSAGE_BLOCK_LENGTH = "MessageBlockLength";
+
+ // Property Strings
+ internal const string BCRYPT_CHAIN_MODE_NA = "ChainingModeN/A";
+ internal const string BCRYPT_CHAIN_MODE_CBC = "ChainingModeCBC";
+ internal const string BCRYPT_CHAIN_MODE_ECB = "ChainingModeECB";
+ internal const string BCRYPT_CHAIN_MODE_CFB = "ChainingModeCFB";
+ internal const string BCRYPT_CHAIN_MODE_CCM = "ChainingModeCCM";
+ internal const string BCRYPT_CHAIN_MODE_GCM = "ChainingModeGCM";
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/CryptographicException.cs b/src/Microsoft.AspNet.Security.DataProtection/CryptographicException.cs
new file mode 100644
index 0000000000..1737c90f38
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/CryptographicException.cs
@@ -0,0 +1,15 @@
+using System;
+
+#if !NET45
+namespace System.Security.Cryptography {
+ internal sealed class CryptographicException : Exception {
+ internal CryptographicException(string message)
+ : base(message) {
+
+ }
+
+ internal CryptographicException(int unused) {
+ }
+ }
+}
+#endif
diff --git a/src/Microsoft.AspNet.Security.DataProtection/DataProtectionProvider.cs b/src/Microsoft.AspNet.Security.DataProtection/DataProtectionProvider.cs
new file mode 100644
index 0000000000..75320aee41
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/DataProtectionProvider.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Globalization;
+using System.Reflection;
+using Microsoft.AspNet.Security.DataProtection.Resources;
+using Microsoft.AspNet.Security.DataProtection.Util;
+
+namespace Microsoft.AspNet.Security.DataProtection {
+ public unsafe static class DataProtectionProvider {
+
+ const int MASTER_KEY_REQUIRED_LENGTH = 512 / 8;
+
+ private static readonly byte[] MASTER_SUBKEY_GENERATOR = GetMasterSubkeyGenerator();
+
+ private static byte[] GetMasterSubkeyGenerator() {
+ TypeInfo typeInfo = 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;
+ }
+
+ ///
+ /// Creates a new IDataProtectorFactory with a randomly-generated master key.
+ ///
+ public static IDataProtectionProvider CreateNew() {
+ byte* masterKey = stackalloc byte[MASTER_KEY_REQUIRED_LENGTH];
+ try {
+ BCryptUtil.GenRandom(masterKey, MASTER_KEY_REQUIRED_LENGTH);
+ return CreateImpl(masterKey, MASTER_KEY_REQUIRED_LENGTH);
+ }
+ finally {
+ BufferUtil.ZeroMemory(masterKey, MASTER_KEY_REQUIRED_LENGTH);
+ }
+ }
+
+ ///
+ /// Creates a new IDataProtectorFactory with the provided master key.
+ ///
+ public static IDataProtectionProvider CreateFromKey(byte[] masterKey) {
+ if (masterKey == null) {
+ throw new ArgumentNullException("masterKey");
+ }
+ if (masterKey.Length < MASTER_KEY_REQUIRED_LENGTH) {
+ string errorMessage = String.Format(CultureInfo.CurrentCulture, Res.DataProtectorFactory_MasterKeyTooShort, MASTER_KEY_REQUIRED_LENGTH);
+ throw new ArgumentOutOfRangeException("masterKey", errorMessage);
+ }
+
+ fixed (byte* pMasterKey = masterKey) {
+ return CreateImpl(pMasterKey, masterKey.Length);
+ }
+ }
+
+ private static DataProtectionProviderImpl CreateImpl(byte* masterKey, int masterKeyLengthInBytes) {
+ // We don't use the master key directly. We derive a master subkey via HMAC_{master_key}(MASTER_SUBKEY_GENERATOR).
+ byte* masterSubkey = stackalloc byte[MASTER_KEY_REQUIRED_LENGTH];
+ try {
+ using (var hashHandle = BCryptUtil.CreateHash(Algorithms.HMACSHA512AlgorithmHandle, masterKey, masterKeyLengthInBytes)) {
+ BCryptUtil.HashData(hashHandle, masterKey, masterKeyLengthInBytes, masterSubkey, MASTER_KEY_REQUIRED_LENGTH);
+ }
+ BCryptKeyHandle kdfSubkeyHandle = BCryptUtil.ImportKey(Algorithms.SP800108AlgorithmHandle, masterSubkey, MASTER_KEY_REQUIRED_LENGTH);
+ return new DataProtectionProviderImpl(kdfSubkeyHandle);
+ }
+ finally {
+ BufferUtil.ZeroMemory(masterSubkey, MASTER_KEY_REQUIRED_LENGTH);
+ }
+ }
+
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/DataProtectionProviderImpl.cs b/src/Microsoft.AspNet.Security.DataProtection/DataProtectionProviderImpl.cs
new file mode 100644
index 0000000000..f78cde3fc6
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/DataProtectionProviderImpl.cs
@@ -0,0 +1,26 @@
+using System;
+
+namespace Microsoft.AspNet.Security.DataProtection {
+ internal unsafe sealed class DataProtectionProviderImpl : IDataProtectionProvider {
+
+ private readonly BCryptKeyHandle _kdfSubkeyHandle;
+
+ public DataProtectionProviderImpl(BCryptKeyHandle kdfSubkeyHandle) {
+ _kdfSubkeyHandle = kdfSubkeyHandle;
+ }
+
+ public IDataProtector CreateProtector(string purpose) {
+ BCryptKeyHandle newAesKeyHandle;
+ BCryptHashHandle newHmacHashHandle;
+ BCryptKeyHandle newKdfSubkeyHandle;
+
+ BCryptUtil.DeriveKeysSP800108(Algorithms.SP800108AlgorithmHandle, _kdfSubkeyHandle, purpose, Algorithms.AESAlgorithmHandle, out newAesKeyHandle, Algorithms.HMACSHA256AlgorithmHandle, out newHmacHashHandle, out newKdfSubkeyHandle);
+ return new DataProtectorImpl(newAesKeyHandle, newHmacHashHandle, newKdfSubkeyHandle);
+ }
+
+ public void Dispose() {
+ _kdfSubkeyHandle.Dispose();
+ }
+
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/DataProtectorImpl.cs b/src/Microsoft.AspNet.Security.DataProtection/DataProtectorImpl.cs
new file mode 100644
index 0000000000..1aeacc31b8
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/DataProtectorImpl.cs
@@ -0,0 +1,160 @@
+using System;
+using System.Diagnostics;
+using System.Security.Cryptography;
+using Microsoft.AspNet.Security.DataProtection.Resources;
+using Microsoft.AspNet.Security.DataProtection.Util;
+
+namespace Microsoft.AspNet.Security.DataProtection {
+ 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 readonly BCryptKeyHandle _aesKeyHandle;
+ private readonly BCryptHashHandle _hmacHashHandle;
+ private readonly BCryptKeyHandle _kdfSubkeyHandle;
+
+ public DataProtectorImpl(BCryptKeyHandle aesKeyHandle, BCryptHashHandle hmacHashHandle, BCryptKeyHandle kdfSubkeyHandle) {
+ _aesKeyHandle = aesKeyHandle;
+ _hmacHashHandle = hmacHashHandle;
+ _kdfSubkeyHandle = kdfSubkeyHandle;
+ }
+
+ private static int CalculateTotalProtectedDataSize(int unprotectedDataSize) {
+ Debug.Assert(unprotectedDataSize >= 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 */);
+ }
+
+ private static CryptographicException CreateGenericCryptographicException() {
+ return new CryptographicException(Res.DataProtectorImpl_BadEncryptedData);
+ }
+
+ public IDataProtector CreateSubProtector(string purpose) {
+ BCryptKeyHandle newAesKeyHandle;
+ BCryptHashHandle newHmacHashHandle;
+ BCryptKeyHandle newKdfSubkeyHandle;
+
+ BCryptUtil.DeriveKeysSP800108(Algorithms.SP800108AlgorithmHandle, _kdfSubkeyHandle, purpose, Algorithms.AESAlgorithmHandle, out newAesKeyHandle, Algorithms.HMACSHA256AlgorithmHandle, out newHmacHashHandle, out newKdfSubkeyHandle);
+ return new DataProtectorImpl(newAesKeyHandle, newHmacHashHandle, newKdfSubkeyHandle);
+ }
+
+ public void Dispose() {
+ _aesKeyHandle.Dispose();
+ _hmacHashHandle.Dispose();
+ _kdfSubkeyHandle.Dispose();
+ }
+
+ public byte[] Protect(byte[] unprotectedData) {
+ if (unprotectedData == null) {
+ throw new ArgumentNullException("unprotectedData");
+ }
+
+ // When this method finishes, protectedData will contain { IV || ciphertext || HMAC(IV || ciphertext) }
+ byte[] protectedData = new byte[CalculateTotalProtectedDataSize(unprotectedData.Length)];
+
+ fixed (byte* pProtectedData = protectedData) {
+ // first, generate a random IV for CBC mode encryption
+ byte* pIV = pProtectedData;
+ BCryptUtil.GenRandom(pIV, AES_BLOCK_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) {
+ int actualCiphertextLength = BCryptUtil.EncryptWithPadding(_aesKeyHandle, pPlaintext, unprotectedData.Length, pIV, AES_BLOCK_LENGTH_IN_BYTES, pCiphertext, expectedCiphertextLength);
+ if (actualCiphertextLength != expectedCiphertextLength) {
+ throw new InvalidOperationException("Unexpected error while encrypting data.");
+ }
+ }
+
+ // finally, calculate an HMAC over { IV || ciphertext }
+ byte* pMac = &pCiphertext[expectedCiphertextLength];
+ using (var clonedHashHandle = BCryptUtil.DuplicateHash(_hmacHashHandle)) {
+ // 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);
+ }
+ }
+
+ return protectedData;
+ }
+
+ public byte[] Unprotect(byte[] protectedData) {
+ if (protectedData == null) {
+ throw new ArgumentNullException("protectedData");
+ }
+
+ byte[] retVal = null;
+ try {
+ retVal = UnprotectImpl(protectedData);
+ }
+ catch {
+ // swallow all exceptions; we'll homogenize
+ }
+
+ if (retVal != null) {
+ return retVal;
+ }
+ else {
+ throw CreateGenericCryptographicException();
+ }
+ }
+
+ private byte[] UnprotectImpl(byte[] protectedData) {
+ 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) {
+ return null;
+ }
+
+ fixed (byte* pProtectedData = protectedData) {
+ // 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* pSuppliedMac = &pCiphertext[ciphertextLength];
+
+ // first, ensure that the MAC is valid
+ byte* pCalculatedMac = stackalloc byte[MAC_LENGTH_IN_BYTES];
+ 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);
+ }
+ if (!BCryptUtil.BuffersAreEqualSecure(pSuppliedMac, pCalculatedMac, MAC_LENGTH_IN_BYTES)) {
+ return null; // MAC check failed
+ }
+
+ // next, perform the actual decryption
+ // we don't know the actual plaintext length, but we know it must be strictly less than the ciphertext length
+ int plaintextBufferLength = ciphertextLength;
+ byte[] heapAllocatedPlaintext = null;
+ if (ciphertextLength > Constants.MAX_STACKALLOC_BYTES) {
+ heapAllocatedPlaintext = new byte[plaintextBufferLength];
+ }
+
+ fixed (byte* pHeapAllocatedPlaintext = heapAllocatedPlaintext) {
+ byte* pPlaintextBuffer = pHeapAllocatedPlaintext;
+ if (pPlaintextBuffer == null) {
+ byte* temp = stackalloc byte[plaintextBufferLength]; // will be released when frame pops
+ pPlaintextBuffer = temp;
+ }
+
+ int actualPlaintextLength = BCryptUtil.DecryptWithPadding(_aesKeyHandle, pCiphertext, ciphertextLength, pIV, AES_BLOCK_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;
+ }
+ }
+ }
+
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/IDataProtectionProvider.cs b/src/Microsoft.AspNet.Security.DataProtection/IDataProtectionProvider.cs
new file mode 100644
index 0000000000..520359930e
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/IDataProtectionProvider.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Microsoft.AspNet.Security.DataProtection {
+ public interface IDataProtectionProvider : IDisposable {
+ ///
+ /// Given a purpose, returns a new IDataProtector that has unique cryptographic keys tied to this purpose.
+ ///
+ /// The consumer of the IDataProtector.
+ /// An IDataProtector.
+ IDataProtector CreateProtector(string purpose);
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/IDataProtector.cs b/src/Microsoft.AspNet.Security.DataProtection/IDataProtector.cs
new file mode 100644
index 0000000000..c932d4522c
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/IDataProtector.cs
@@ -0,0 +1,33 @@
+using System;
+
+namespace Microsoft.AspNet.Security.DataProtection {
+ ///
+ /// Represents an object that can perform cryptographic operations.
+ ///
+ public interface IDataProtector : IDisposable {
+ ///
+ /// Given a subpurpose, returns a new IDataProtector that has unique cryptographic keys tied both the purpose
+ /// that was used to create this IDataProtector instance and the purpose that is provided as a parameter
+ /// to this method.
+ ///
+ /// The sub-consumer of the IDataProtector.
+ /// An IDataProtector.
+ IDataProtector CreateSubProtector(string purpose);
+
+ ///
+ /// Cryptographically protects some input data.
+ ///
+ /// The data to be protected.
+ /// An array containing cryptographically protected data.
+ /// To retrieve the original data, call Unprotect on the protected data.
+ byte[] Protect(byte[] unprotectedData);
+
+ ///
+ /// Retrieves the original data that was protected by a call to Protect.
+ ///
+ /// The protected data to be decrypted.
+ /// The original data.
+ /// Throws CryptographicException if the protectedData parameter has been tampered with.
+ byte[] Unprotect(byte[] protectedData);
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/Properties/AssemblyInfo.cs b/src/Microsoft.AspNet.Security.DataProtection/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..38a4928ff0
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/Properties/AssemblyInfo.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Reflection;
+using System.Resources;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Microsoft.AspNet.Security.DataProtection")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyProduct("Microsoft.AspNet.Security.DataProtection")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("130d9afa-6535-42bf-ba70-610b677d5acf")]
+
+[assembly: AssemblyCompany("Microsoft Corporation")]
+[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+[assembly: NeutralResourcesLanguage("en-US")]
+
+// for OOB servicing
+[assembly: AssemblyMetadata("Serviceable", "True")]
diff --git a/src/Microsoft.AspNet.Security.DataProtection/Resources/Res.Designer.cs b/src/Microsoft.AspNet.Security.DataProtection/Resources/Res.Designer.cs
new file mode 100644
index 0000000000..d62ce5ee40
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/Resources/Res.Designer.cs
@@ -0,0 +1,82 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.34003
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Microsoft.AspNet.Security.DataProtection.Resources {
+ using System;
+ using System.Reflection;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Res {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Res() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.AspNet.Security.DataProtection.Res.resources", typeof(Res).GetTypeInfo().Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The master key is too short. It must be at least {0} bytes in length..
+ ///
+ internal static string DataProtectorFactory_MasterKeyTooShort {
+ get {
+ return ResourceManager.GetString("DataProtectorFactory_MasterKeyTooShort", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The data to decrypt is invalid..
+ ///
+ internal static string DataProtectorImpl_BadEncryptedData {
+ get {
+ return ResourceManager.GetString("DataProtectorImpl_BadEncryptedData", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/Resources/Res.resx b/src/Microsoft.AspNet.Security.DataProtection/Resources/Res.resx
new file mode 100644
index 0000000000..f28f1d7003
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/Resources/Res.resx
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ The master key is too short. It must be at least {0} bytes in length.
+
+
+ The data to decrypt is invalid.
+
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Security.DataProtection/SafeHandleZeroOrMinusOneIsInvalid.cs b/src/Microsoft.AspNet.Security.DataProtection/SafeHandleZeroOrMinusOneIsInvalid.cs
new file mode 100644
index 0000000000..244f118df0
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/SafeHandleZeroOrMinusOneIsInvalid.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Runtime.InteropServices;
+
+#if !NET45
+namespace Microsoft.Win32.SafeHandles {
+ internal abstract class SafeHandleZeroOrMinusOneIsInvalid : SafeHandle {
+ // Called by P/Invoke when returning SafeHandles
+ protected SafeHandleZeroOrMinusOneIsInvalid(bool ownsHandle)
+ : base(IntPtr.Zero, ownsHandle) {
+ }
+
+ public override bool IsInvalid {
+ get {
+ return (handle == IntPtr.Zero || handle == (IntPtr)(-1));
+ }
+ }
+ }
+}
+#endif
diff --git a/src/Microsoft.AspNet.Security.DataProtection/UnsafeNativeMethods.cs b/src/Microsoft.AspNet.Security.DataProtection/UnsafeNativeMethods.cs
new file mode 100644
index 0000000000..34689a9917
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/UnsafeNativeMethods.cs
@@ -0,0 +1,159 @@
+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 unsafe static class UnsafeNativeMethods {
+
+ private const string BCRYPT_LIB = "bcrypt.dll";
+ private const string KERNEL32_LIB = "kernel32.dll";
+
+ /*
+ * BCRYPT.DLL
+ */
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375377(v=vs.85).aspx
+ internal static extern int BCryptCloseAlgorithmProvider(
+ [In] IntPtr hAlgorithm,
+ [In] uint dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375383(v=vs.85).aspx
+ internal static extern int BCryptCreateHash(
+ [In] BCryptAlgorithmHandle hAlgorithm,
+ [Out] out BCryptHashHandle phHash,
+ [In] IntPtr pbHashObject,
+ [In] uint cbHashObject,
+ [In] byte* pbSecret,
+ [In] uint cbSecret,
+ [In] uint dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375391(v=vs.85).aspx
+ internal static extern int BCryptDecrypt(
+ [In] BCryptKeyHandle hKey,
+ [In] byte* pbInput,
+ [In] uint cbInput,
+ [In] IntPtr pPaddingInfo,
+ [In] byte* pbIV,
+ [In] uint cbIV,
+ [In] byte* pbOutput,
+ [In] uint cbOutput,
+ [Out] out uint pcbResult,
+ [In] BCryptEncryptFlags 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(
+ [In] IntPtr hHash);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375404(v=vs.85).aspx
+ internal static extern int BCryptDestroyKey(
+ [In] IntPtr hKey);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375413(v=vs.85).aspx
+ internal static extern int BCryptDuplicateHash(
+ [In] BCryptHashHandle hHash,
+ [Out] out BCryptHashHandle phNewHash,
+ [In] IntPtr pbHashObject,
+ [In] uint cbHashObject,
+ [In] uint dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375421(v=vs.85).aspx
+ internal static extern int BCryptEncrypt(
+ [In] BCryptKeyHandle hKey,
+ [In] byte* pbInput,
+ [In] uint cbInput,
+ [In] IntPtr pPaddingInfo,
+ [In] byte* pbIV,
+ [In] uint cbIV,
+ [In] byte* pbOutput,
+ [In] uint cbOutput,
+ [Out] out uint pcbResult,
+ [In] BCryptEncryptFlags dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375443(v=vs.85).aspx
+ internal static extern int BCryptFinishHash(
+ [In] BCryptHashHandle hHash,
+ [In] byte* pbOutput,
+ [In] uint cbOutput,
+ [In] uint dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375458(v=vs.85).aspx
+ internal static extern int BCryptGenRandom(
+ [In] IntPtr hAlgorithm,
+ [In] byte* pbBuffer,
+ [In] uint cbBuffer,
+ [In] BCryptGenRandomFlags dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375468(v=vs.85).aspx
+ internal static extern int BCryptHashData(
+ [In] BCryptHashHandle hHash,
+ [In] byte* pbInput,
+ [In] uint cbInput,
+ [In] uint dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375475(v=vs.85).aspx
+ internal static extern int BCryptImportKey(
+ [In] BCryptAlgorithmHandle hAlgorithm,
+ [In] IntPtr hImportKey, // unused
+ [In, MarshalAs(UnmanagedType.LPWStr)] string pszBlobType,
+ [Out] out BCryptKeyHandle phKey,
+ [In] IntPtr pbKeyObject, // unused
+ [In] uint cbKeyObject,
+ [In] byte* pbInput,
+ [In] uint cbInput,
+ [In] uint dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/hh448506(v=vs.85).aspx
+ internal static extern int BCryptKeyDerivation(
+ [In] BCryptKeyHandle hKey,
+ [In] BCryptBufferDesc* pParameterList,
+ [In] byte* pbDerivedKey,
+ [In] uint cbDerivedKey,
+ [Out] out uint pcbResult,
+ [In] uint dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375479(v=vs.85).aspx
+ internal static extern int BCryptOpenAlgorithmProvider(
+ [Out] out BCryptAlgorithmHandle phAlgorithm,
+ [In, MarshalAs(UnmanagedType.LPWStr)] string pszAlgId,
+ [In, MarshalAs(UnmanagedType.LPWStr)] string pszImplementation,
+ [In] BCryptAlgorithmFlags dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375504(v=vs.85).aspx
+ internal static extern int BCryptSetProperty(
+ [In] SafeHandle hObject,
+ [In, MarshalAs(UnmanagedType.LPWStr)] string pszProperty,
+ [In] IntPtr pbInput,
+ [In] uint cbInput,
+ [In] uint dwFlags);
+
+ /*
+ * KERNEL32.DLL
+ */
+
+ [DllImport(KERNEL32_LIB, CallingConvention = CallingConvention.Winapi)]
+ internal static extern void RtlZeroMemory(
+ [In] IntPtr Destination,
+ [In] UIntPtr /* SIZE_T */ Length);
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/Util/BufferUtil.cs b/src/Microsoft.AspNet.Security.DataProtection/Util/BufferUtil.cs
new file mode 100644
index 0000000000..36a781aa17
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/Util/BufferUtil.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Microsoft.AspNet.Security.DataProtection.Util {
+ 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) {
+ 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) {
+ BlockCopySlow((byte*)from, (byte*)to, byteCount);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void BlockCopySlow(byte* from, byte* to, uint byteCount) {
+ // slow, but works
+ while (byteCount-- != 0) {
+ *(to++) = *(from++);
+ }
+ }
+
+ ///
+ /// Creates a new managed byte[] from unmanaged memory.
+ ///
+ public static byte[] ToManagedByteArray(byte* ptr, int byteCount) {
+ return ToManagedByteArray(ptr, checked((uint)byteCount));
+ }
+
+ ///
+ /// Creates a new managed byte[] from unmanaged memory.
+ ///
+ public static byte[] ToManagedByteArray(byte* ptr, uint byteCount) {
+ if (byteCount == 0) {
+ return _emptyArray; // degenerate case
+ }
+ else {
+ byte[] bytes = new byte[byteCount];
+ fixed (byte* pBytes = bytes) {
+ BlockCopy(from: (IntPtr)ptr, to: (IntPtr)pBytes, byteCount: byteCount);
+ }
+ return bytes;
+ }
+ }
+
+ ///
+ /// Clears a memory buffer.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ZeroMemory(byte* buffer, int byteCount) {
+ ZeroMemory(buffer, checked((uint)byteCount));
+ }
+
+ ///
+ /// Clears a memory buffer.
+ ///
+ [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
+ }
+
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/project.json b/src/Microsoft.AspNet.Security.DataProtection/project.json
new file mode 100644
index 0000000000..ea163de588
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/project.json
@@ -0,0 +1,10 @@
+{
+ "version": "0.1-alpha-*",
+ "configurations": {
+ "net45" : {},
+ "k10" : {}
+ },
+ "compilationOptions": {
+ "allowUnsafe": true
+ }
+}
\ No newline at end of file