diff --git a/src/Microsoft.AspNet.Security.DataProtection/CryptoUtil.cs b/src/Microsoft.AspNet.Security.DataProtection/CryptoUtil.cs
new file mode 100644
index 0000000000..29fabca02e
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/CryptoUtil.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Security.Cryptography;
+
+namespace Microsoft.AspNet.Security.DataProtection.Cng
+{
+ internal unsafe static class CryptoUtil
+ {
+ // This isn't a typical Debug.Assert; the check is always performed, even in retail builds.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Assert(bool condition, string message)
+ {
+ if (!condition)
+ {
+ Fail(message);
+ }
+ }
+
+ // This isn't a typical Debug.Fail; an error always occurs, even in retail builds.
+ // This method doesn't return, but since the CLR doesn't allow specifying a 'never'
+ // return type, we mimic it by specifying our return type as Exception. That way
+ // callers can write 'throw Fail(...);' to make the C# compiler happy, as the
+ // throw keyword is implicitly of type O.
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static Exception Fail(string message)
+ {
+ Debug.Fail(message);
+ throw new CryptographicException("Assertion failed: " + message);
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/KeyDerivation.cs b/src/Microsoft.AspNet.Security.DataProtection/KeyDerivation.cs
new file mode 100644
index 0000000000..548e0e7f65
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/KeyDerivation.cs
@@ -0,0 +1,38 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNet.Security.DataProtection.Cng.PBKDF2;
+
+namespace Microsoft.AspNet.Security.DataProtection.Cng
+{
+ public static class KeyDerivation
+ {
+ public static byte[] Pbkdf2(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested)
+ {
+ // parameter checking
+ if (password == null)
+ {
+ throw new ArgumentNullException("password");
+ }
+ if (salt == null)
+ {
+ throw new ArgumentNullException("salt");
+ }
+ if (prf < KeyDerivationPrf.Sha1 || prf > KeyDerivationPrf.Sha512)
+ {
+ throw new ArgumentOutOfRangeException("prf");
+ }
+ if (iterationCount <= 0)
+ {
+ throw new ArgumentOutOfRangeException("iterationCount");
+ }
+ if (numBytesRequested <= 0)
+ {
+ throw new ArgumentOutOfRangeException("numBytesRequested");
+ }
+
+ return Pbkdf2Util.Pbkdf2Provider.DeriveKey(password, salt, prf, iterationCount, numBytesRequested);
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/KeyDerivationPrf.cs b/src/Microsoft.AspNet.Security.DataProtection/KeyDerivationPrf.cs
new file mode 100644
index 0000000000..600383eb7a
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/KeyDerivationPrf.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNet.Security.DataProtection.Cng
+{
+ ///
+ /// Specifies the PRF which should be used for the key derivation algorithm.
+ ///
+ public enum KeyDerivationPrf
+ {
+ ///
+ /// SHA-1 (FIPS PUB 180-4)
+ ///
+ Sha1,
+
+ ///
+ /// SHA-256 (FIPS PUB 180-4)
+ ///
+ Sha256,
+
+ ///
+ /// SHA-512 (FIPS PUB 180-4)
+ ///
+ Sha512,
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/PBKDF2/IPbkdf2Provider.cs b/src/Microsoft.AspNet.Security.DataProtection/PBKDF2/IPbkdf2Provider.cs
new file mode 100644
index 0000000000..a9e499b80e
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/PBKDF2/IPbkdf2Provider.cs
@@ -0,0 +1,15 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNet.Security.DataProtection.Cng.PBKDF2
+{
+ ///
+ /// Internal interface used for abstracting away the PBKDF2 implementation since the implementation is OS-specific.
+ ///
+ internal interface IPbkdf2Provider
+ {
+ byte[] DeriveKey(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested);
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/PBKDF2/ManagedPbkdf2Provider.cs b/src/Microsoft.AspNet.Security.DataProtection/PBKDF2/ManagedPbkdf2Provider.cs
new file mode 100644
index 0000000000..3fc75f67fd
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/PBKDF2/ManagedPbkdf2Provider.cs
@@ -0,0 +1,102 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics;
+using System.Security.Cryptography;
+
+namespace Microsoft.AspNet.Security.DataProtection.Cng.PBKDF2
+{
+ ///
+ /// A PBKDF2 provider which utilizes the managed hash algorithm classes as PRFs.
+ /// This isn't the preferred provider since the implementation is slow, but it is provided as a fallback.
+ ///
+ internal sealed class ManagedPbkdf2Provider : IPbkdf2Provider
+ {
+ public byte[] DeriveKey(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested)
+ {
+ Debug.Assert(password != null);
+ Debug.Assert(salt != null);
+ Debug.Assert(iterationCount > 0);
+ Debug.Assert(numBytesRequested > 0);
+
+ // PBKDF2 is defined in NIST SP800-132, Sec. 5.3.
+ // http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf
+
+ byte[] retVal = new byte[numBytesRequested];
+ int numBytesWritten = 0;
+ int numBytesRemaining = numBytesRequested;
+
+ // For each block index, U_0 := Salt || block_index
+ byte[] saltWithBlockIndex = new byte[checked(salt.Length + sizeof(uint))];
+ Buffer.BlockCopy(salt, 0, saltWithBlockIndex, 0, salt.Length);
+
+ using (var hashAlgorithm = PrfToManagedHmacAlgorithm(prf, password))
+ {
+ for (uint blockIndex = 1; numBytesRemaining > 0; blockIndex++)
+ {
+ // write the block index out as big-endian
+ saltWithBlockIndex[saltWithBlockIndex.Length - 4] = (byte)(blockIndex >> 24);
+ saltWithBlockIndex[saltWithBlockIndex.Length - 3] = (byte)(blockIndex >> 16);
+ saltWithBlockIndex[saltWithBlockIndex.Length - 2] = (byte)(blockIndex >> 8);
+ saltWithBlockIndex[saltWithBlockIndex.Length - 1] = (byte)blockIndex;
+
+ // U_1 = PRF(U_0) = PRF(Salt || block_index)
+ // T_blockIndex = U_1
+ byte[] U_iter = hashAlgorithm.ComputeHash(saltWithBlockIndex); // this is U_1
+ byte[] T_blockIndex = U_iter;
+
+ for (int iter = 1; iter < iterationCount; iter++)
+ {
+ U_iter = hashAlgorithm.ComputeHash(U_iter);
+ XorBuffers(src: U_iter, dest: T_blockIndex);
+ // At this point, the 'U_iter' variable actually contains U_{iter+1} (due to indexing differences).
+ }
+
+ // At this point, we're done iterating on this block, so copy the transformed block into retVal.
+ int numBytesToCopy = Math.Min(numBytesRemaining, T_blockIndex.Length);
+ Buffer.BlockCopy(T_blockIndex, 0, retVal, numBytesWritten, numBytesToCopy);
+ numBytesWritten += numBytesToCopy;
+ numBytesRemaining -= numBytesToCopy;
+ }
+ }
+
+ // retVal := T_1 || T_2 || ... || T_n, where T_n may be truncated to meet the desired output length
+ return retVal;
+ }
+
+ private static KeyedHashAlgorithm PrfToManagedHmacAlgorithm(KeyDerivationPrf prf, string password)
+ {
+ byte[] passwordBytes = Pbkdf2Util.SecureUtf8Encoding.GetBytes(password);
+ try
+ {
+ switch (prf)
+ {
+ case KeyDerivationPrf.Sha1:
+ return new HMACSHA1(passwordBytes);
+ case KeyDerivationPrf.Sha256:
+ return new HMACSHA256(passwordBytes);
+ case KeyDerivationPrf.Sha512:
+ return new HMACSHA512(passwordBytes);
+ default:
+ throw CryptoUtil.Fail("Unrecognized PRF.");
+ }
+ }
+ finally
+ {
+ // The HMAC ctor makes a duplicate of this key; we clear original buffer to limit exposure to the GC.
+ Array.Clear(passwordBytes, 0, passwordBytes.Length);
+ }
+ }
+
+ private static void XorBuffers(byte[] src, byte[] dest)
+ {
+ // Note: dest buffer is mutated.
+ Debug.Assert(src.Length == dest.Length);
+ for (int i = 0; i < src.Length; i++)
+ {
+ dest[i] ^= src[i];
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/PBKDF2/Pbkdf2Util.cs b/src/Microsoft.AspNet.Security.DataProtection/PBKDF2/Pbkdf2Util.cs
new file mode 100644
index 0000000000..1af12b4bdc
--- /dev/null
+++ b/src/Microsoft.AspNet.Security.DataProtection/PBKDF2/Pbkdf2Util.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Text;
+
+namespace Microsoft.AspNet.Security.DataProtection.Cng.PBKDF2
+{
+ ///
+ /// Internal base class used for abstracting away the PBKDF2 implementation since the implementation is OS-specific.
+ ///
+ internal static class Pbkdf2Util
+ {
+ public static readonly IPbkdf2Provider Pbkdf2Provider = GetPbkdf2Provider();
+ public static readonly UTF8Encoding SecureUtf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: false);
+
+ private static IPbkdf2Provider GetPbkdf2Provider()
+ {
+ // In priority order, our three implementations are Win8, Win7, and "other".
+
+ // TODO: Provide Win7 & Win8 implementations when the new DataProtection stack is fully copied over.
+ return new ManagedPbkdf2Provider();
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Security.DataProtection/project.json b/src/Microsoft.AspNet.Security.DataProtection/project.json
index 66ba19caba..b2e6788017 100644
--- a/src/Microsoft.AspNet.Security.DataProtection/project.json
+++ b/src/Microsoft.AspNet.Security.DataProtection/project.json
@@ -18,6 +18,7 @@
"System.Runtime.Extensions": "4.0.10.0",
"System.Runtime.InteropServices": "4.0.20.0",
"System.Security.Cryptography.Encryption": "4.0.0.0",
+ "System.Security.Cryptography.Hashing.Algorithms": "4.0.0.0",
"System.Text.Encoding.Extensions": "4.0.10.0"
}
}