Add preliminary PBKDF2 routines to unblock identity work.
Win7 and Win8 optimizations will be committed as part of the larger DataProtection overhaul.
This commit is contained in:
parent
23d2830960
commit
4588b1c898
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the PRF which should be used for the key derivation algorithm.
|
||||
/// </summary>
|
||||
public enum KeyDerivationPrf
|
||||
{
|
||||
/// <summary>
|
||||
/// SHA-1 (FIPS PUB 180-4)
|
||||
/// </summary>
|
||||
Sha1,
|
||||
|
||||
/// <summary>
|
||||
/// SHA-256 (FIPS PUB 180-4)
|
||||
/// </summary>
|
||||
Sha256,
|
||||
|
||||
/// <summary>
|
||||
/// SHA-512 (FIPS PUB 180-4)
|
||||
/// </summary>
|
||||
Sha512,
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal interface used for abstracting away the PBKDF2 implementation since the implementation is OS-specific.
|
||||
/// </summary>
|
||||
internal interface IPbkdf2Provider
|
||||
{
|
||||
byte[] DeriveKey(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal base class used for abstracting away the PBKDF2 implementation since the implementation is OS-specific.
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue