--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+
+internal static partial class Interop
+{
+ internal static partial class AppleCrypto
+ {
+ internal static unsafe void Pbkdf2(
+ PAL_HashAlgorithm prfAlgorithm,
+ ReadOnlySpan<byte> password,
+ ReadOnlySpan<byte> salt,
+ int iterations,
+ Span<byte> destination)
+ {
+ fixed (byte* pPassword = password)
+ fixed (byte* pSalt = salt)
+ fixed (byte* pDestination = destination)
+ {
+ int ret = AppleCryptoNative_Pbkdf2(
+ prfAlgorithm,
+ pPassword,
+ password.Length,
+ pSalt,
+ salt.Length,
+ iterations,
+ pDestination,
+ destination.Length,
+ out int ccStatus);
+
+ if (ret == 0)
+ {
+ throw Interop.AppleCrypto.CreateExceptionForCCError(
+ ccStatus,
+ Interop.AppleCrypto.CCCryptorStatus);
+ }
+
+ if (ret != 1)
+ {
+ Debug.Fail($"Pbkdf2 failed with invalid input {ret}");
+ throw new CryptographicException();
+ }
+ }
+ }
+
+ [DllImport(Libraries.AppleCryptoNative)]
+ private static extern unsafe int AppleCryptoNative_Pbkdf2(
+ PAL_HashAlgorithm prfAlgorithm,
+ byte* password,
+ int passwordLen,
+ byte* salt,
+ int saltLen,
+ int iterations,
+ byte* derivedKey,
+ int derivedKeyLen,
+ out int errorCode);
+ }
+}
[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpSha512")]
internal static extern IntPtr EvpSha512();
-
[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_GetMaxMdSize")]
private static extern int GetMaxMdSize();
+ [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_Pbkdf2")]
+ private static unsafe extern int Pbkdf2(
+ byte* pPassword,
+ int passwordLength,
+ byte* pSalt,
+ int saltLength,
+ int iterations,
+ IntPtr digestEvp,
+ byte* pDestination,
+ int destinationLength);
+
+ internal static unsafe int Pbkdf2(
+ ReadOnlySpan<byte> password,
+ ReadOnlySpan<byte> salt,
+ int iterations,
+ IntPtr digestEvp,
+ Span<byte> destination)
+ {
+ fixed (byte* pPassword = password)
+ fixed (byte* pSalt = salt)
+ fixed (byte* pDestination = destination)
+ {
+ return Pbkdf2(
+ pPassword,
+ password.Length,
+ pSalt,
+ salt.Length,
+ iterations,
+ digestEvp,
+ pDestination,
+ destination.Length);
+ }
+ }
+
internal static readonly int EVP_MAX_MD_SIZE = GetMaxMdSize();
}
}
public const string Sha256 = "SHA256"; // BCRYPT_SHA256_ALGORITHM
public const string Sha384 = "SHA384"; // BCRYPT_SHA384_ALGORITHM
public const string Sha512 = "SHA512"; // BCRYPT_SHA512_ALGORITHM
+ public const string Pbkdf2 = "PBKDF2"; // BCRYPT_PBKDF2_ALGORITHM
}
internal static class KeyDerivationFunction
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class BCrypt
+ {
+ // Pseudo-handles, as defined in bcrypt.h
+ // TODO: This really should be backed by 'nuint' (see https://github.com/dotnet/roslyn/issues/44110)
+ public enum BCryptAlgPseudoHandle : uint
+ {
+ BCRYPT_MD5_ALG_HANDLE = 0x00000021,
+ BCRYPT_SHA1_ALG_HANDLE = 0x00000031,
+ BCRYPT_SHA256_ALG_HANDLE = 0x00000041,
+ BCRYPT_SHA384_ALG_HANDLE = 0x00000051,
+ BCRYPT_SHA512_ALG_HANDLE = 0x00000061,
+ BCRYPT_PBKDF2_ALG_HANDLE = 0x00000331,
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+using Microsoft.Win32.SafeHandles;
+
+internal partial class Interop
+{
+ internal partial class BCrypt
+ {
+ [DllImport(Libraries.BCrypt, CharSet = CharSet.Unicode)]
+ internal static extern unsafe NTSTATUS BCryptDeriveKeyPBKDF2(
+ SafeBCryptAlgorithmHandle hPrf,
+ byte* pbPassword,
+ int cbPassword,
+ byte* pbSalt,
+ int cbSalt,
+ ulong cIterations,
+ byte* pbDerivedKey,
+ int cbDerivedKey,
+ uint dwFlags);
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Runtime.InteropServices;
+using Microsoft.Win32.SafeHandles;
+
+internal partial class Interop
+{
+ internal partial class BCrypt
+ {
+ [DllImport(Libraries.BCrypt, CharSet = CharSet.Unicode)]
+ internal static unsafe extern NTSTATUS BCryptGenerateSymmetricKey(
+ SafeBCryptAlgorithmHandle hAlgorithm,
+ out SafeBCryptKeyHandle phKey,
+ IntPtr pbKeyObject,
+ int cbKeyObject,
+ byte* pbSecret,
+ int cbSecret,
+ uint dwFlags);
+
+ [DllImport(Libraries.BCrypt, CharSet = CharSet.Unicode)]
+ internal static unsafe extern NTSTATUS BCryptGenerateSymmetricKey(
+ nuint hAlgorithm,
+ out SafeBCryptKeyHandle phKey,
+ IntPtr pbKeyObject,
+ int cbKeyObject,
+ byte* pbSecret,
+ int cbSecret,
+ uint dwFlags);
+ }
+}
{
[DllImport(Libraries.BCrypt, CharSet = CharSet.Unicode)]
internal static unsafe extern NTSTATUS BCryptHash(nuint hAlgorithm, byte* pbSecret, int cbSecret, byte* pbInput, int cbInput, byte* pbOutput, int cbOutput);
-
- // Pseudo-handles, as defined in bcrypt.h
- // TODO: This really should be backed by 'nuint' (see https://github.com/dotnet/roslyn/issues/44110)
- public enum BCryptAlgPseudoHandle : uint
- {
- BCRYPT_MD5_ALG_HANDLE = 0x00000021,
- BCRYPT_SHA1_ALG_HANDLE = 0x00000031,
- BCRYPT_SHA256_ALG_HANDLE = 0x00000041,
- BCRYPT_SHA384_ALG_HANDLE = 0x00000051,
- BCRYPT_SHA512_ALG_HANDLE = 0x00000061,
- }
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+using Microsoft.Win32.SafeHandles;
+
+internal partial class Interop
+{
+ internal partial class BCrypt
+ {
+ [DllImport(Libraries.BCrypt, CharSet = CharSet.Unicode)]
+ internal static unsafe extern NTSTATUS BCryptKeyDerivation(
+ SafeBCryptKeyHandle hKey,
+ BCryptBufferDesc* pParameterList,
+ byte* pbDerivedKey,
+ int cbDerivedKey,
+ out uint pcbResult,
+ int dwFlags);
+ }
+}
}
/// <summary>
- /// NCrypt buffer descriptors
+ /// NCrypt or BCrypt buffer descriptors
/// </summary>
- internal enum NCryptBufferDescriptors : int
+ internal enum CngBufferDescriptors : int
{
+ KDF_HASH_ALGORITHM = 0,
+ KDF_SECRET_PREPEND = 1,
+ KDF_SECRET_APPEND = 2,
+ KDF_HMAC_KEY = 3,
+ KDF_TLS_PRF_LABEL = 4,
+ KDF_TLS_PRF_SEED = 5,
+ KDF_SECRET_HANDLE = 6,
+ KDF_TLS_PRF_PROTOCOL = 7,
+ KDF_ALGORITHMID = 8,
+ KDF_PARTYUINFO = 9,
+ KDF_PARTYVINFO = 10,
+ KDF_SUPPPUBINFO = 11,
+ KDF_SUPPPRIVINFO = 12,
+ KDF_LABEL = 13,
+ KDF_CONTEXT = 14,
+ KDF_SALT = 15,
+ KDF_ITERATION_COUNT = 16,
NCRYPTBUFFER_ECC_CURVE_NAME = 60,
}
internal struct BCryptBuffer
{
internal int cbBuffer; // Length of buffer, in bytes
- internal NCryptBufferDescriptors BufferType; // Buffer type
+ internal CngBufferDescriptors BufferType; // Buffer type
internal IntPtr pvBuffer; // Pointer to buffer
}
descPtr = Marshal.AllocHGlobal(Marshal.SizeOf(desc));
buffPtr = Marshal.AllocHGlobal(Marshal.SizeOf(buff));
buff.cbBuffer = (curveName.Length + 1) * 2; // Add 1 for null terminator
- buff.BufferType = Interop.BCrypt.NCryptBufferDescriptors.NCRYPTBUFFER_ECC_CURVE_NAME;
+ buff.BufferType = Interop.BCrypt.CngBufferDescriptors.NCRYPTBUFFER_ECC_CURVE_NAME;
buff.pvBuffer = safeCurveName.DangerousGetHandle();
Marshal.StructureToPtr(buff, buffPtr, false);
pal_ecc.c
pal_hmac.c
pal_keyagree.c
+ pal_keyderivation.c
pal_keychain.c
pal_random.c
pal_rsa.c
#include "pal_trust.h"
#include "pal_x509.h"
#include "pal_x509chain.h"
+#include "pal_keyderivation.h"
static const Entry s_cryptoAppleNative[] =
{
DllImportEntry(AppleCryptoNative_X509ChainGetStatusAtIndex)
DllImportEntry(AppleCryptoNative_GetOSStatusForChainStatus)
DllImportEntry(AppleCryptoNative_X509ChainSetTrustAnchorCertificates)
+ DllImportEntry(AppleCryptoNative_Pbkdf2)
};
EXTERN_C const void* CryptoAppleResolveDllImport(const char* name);
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#include "pal_keyderivation.h"
+
+#if !defined(TARGET_IOS) && !defined(TARGET_TVOS)
+
+static int32_t PrfAlgorithmFromHashAlgorithm(PAL_HashAlgorithm hashAlgorithm, CCPseudoRandomAlgorithm* algorithm)
+{
+ if (algorithm == NULL)
+ return 0;
+
+ switch (hashAlgorithm)
+ {
+ case PAL_SHA1:
+ *algorithm = kCCPRFHmacAlgSHA1;
+ return 1;
+ case PAL_SHA256:
+ *algorithm = kCCPRFHmacAlgSHA256;
+ return 1;
+ case PAL_SHA384:
+ *algorithm = kCCPRFHmacAlgSHA384;
+ return 1;
+ case PAL_SHA512:
+ *algorithm = kCCPRFHmacAlgSHA512;
+ return 1;
+ default:
+ *algorithm = 0;
+ return 0;
+ }
+}
+
+int32_t AppleCryptoNative_Pbkdf2(PAL_HashAlgorithm prfAlgorithm,
+ const char* password,
+ int32_t passwordLen,
+ const uint8_t* salt,
+ int32_t saltLen,
+ int32_t iterations,
+ uint8_t* derivedKey,
+ uint32_t derivedKeyLen,
+ int32_t* errorCode)
+{
+ if (errorCode != NULL)
+ *errorCode = noErr;
+
+ if (passwordLen < 0 || saltLen < 0 || iterations < 0 || derivedKey == NULL ||
+ derivedKeyLen < 0 || errorCode == NULL)
+ {
+ return -1;
+ }
+
+ if (salt == NULL && saltLen != 0)
+ {
+ return -1;
+ }
+
+ const char* empty = "";
+
+ if (password == NULL)
+ {
+ if (passwordLen != 0)
+ {
+ return -1;
+ }
+
+ // macOS will not accept a null password, but it will accept a zero-length
+ // password with a valid pointer.
+ password = empty;
+ }
+
+ CCPseudoRandomAlgorithm prf;
+
+ if (!PrfAlgorithmFromHashAlgorithm(prfAlgorithm, &prf))
+ {
+ return -2;
+ }
+
+ CCStatus result = CCKeyDerivationPBKDF(kCCPBKDF2, password, passwordLen, salt,
+ saltLen, prf, iterations, derivedKey, derivedKeyLen);
+ *errorCode = result;
+ return result == kCCSuccess ? 1 : 0;
+}
+#endif
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#pragma once
+
+#include "pal_digest.h"
+#include <Security/Security.h>
+
+#if !defined(TARGET_IOS) && !defined(TARGET_TVOS)
+/*
+Filled the derivedKey buffer with PBKDF2 derived data.
+
+Implemented by:
+1) Validating input
+2) Calling CCKeyDerivationPBKDF
+
+password and salt may be NULL if their respective length parameter
+is zero. When password is NULL, it will be replaced with a pointer to an empty
+location.
+
+Returns -1 on invalid input, or -2 if the prfAlgorithm is an unknown
+or unsupported hash algorithm. On valid input, the return value
+is 1 if successful, and 0 if unsuccessful.
+
+Returns the result of SecKeychainCreate.
+
+Output:
+errorCode: Contains the CCStatus of the operation. This will contain the
+error code when the call is unsuccessful with valid input.
+*/
+PALEXPORT int32_t AppleCryptoNative_Pbkdf2(PAL_HashAlgorithm prfAlgorithm,
+ const char* password,
+ int32_t passwordLen,
+ const uint8_t* salt,
+ int32_t saltLen,
+ int32_t iterations,
+ uint8_t* derivedKey,
+ uint32_t derivedKeyLen,
+ int32_t* errorCode);
+#endif
DllImportEntry(CryptoNative_ObjTxt2Obj)
DllImportEntry(CryptoNative_OcspRequestDestroy)
DllImportEntry(CryptoNative_OcspResponseDestroy)
+ DllImportEntry(CryptoNative_Pbkdf2)
DllImportEntry(CryptoNative_PemReadBioPkcs7)
DllImportEntry(CryptoNative_PemReadBioX509Crl)
DllImportEntry(CryptoNative_PemReadX509FromBio)
REQUIRED_FUNCTION(PEM_read_bio_X509_AUX) \
REQUIRED_FUNCTION(PEM_read_bio_X509_CRL) \
REQUIRED_FUNCTION(PEM_write_bio_X509_CRL) \
+ REQUIRED_FUNCTION(PKCS5_PBKDF2_HMAC) \
REQUIRED_FUNCTION(PKCS12_free) \
REQUIRED_FUNCTION(PKCS12_parse) \
REQUIRED_FUNCTION(PKCS7_sign) \
#define PEM_read_bio_X509_AUX PEM_read_bio_X509_AUX_ptr
#define PEM_read_bio_X509_CRL PEM_read_bio_X509_CRL_ptr
#define PEM_write_bio_X509_CRL PEM_write_bio_X509_CRL_ptr
+#define PKCS5_PBKDF2_HMAC PKCS5_PBKDF2_HMAC_ptr
#define PKCS12_free PKCS12_free_ptr
#define PKCS12_parse PKCS12_parse_ptr
#define PKCS7_sign PKCS7_sign_ptr
{
return EVP_MAX_MD_SIZE;
}
+
+int32_t CryptoNative_Pbkdf2(const char* password,
+ int32_t passwordLength,
+ const unsigned char* salt,
+ int32_t saltLength,
+ int32_t iterations,
+ const EVP_MD* digest,
+ unsigned char* destination,
+ int32_t destinationLength)
+{
+ if (passwordLength < 0 || saltLength < 0 || iterations <= 0 || digest == NULL ||
+ destination == NULL || destinationLength < 0)
+ {
+ return -1;
+ }
+
+ const char* empty = "";
+
+ if (salt == NULL)
+ {
+ if (saltLength != 0)
+ {
+ return -1;
+ }
+
+ salt = (const unsigned char*)empty;
+ }
+
+ if (password == NULL)
+ {
+ if (passwordLength != 0)
+ {
+ return -1;
+ }
+
+ password = empty;
+ }
+
+ return PKCS5_PBKDF2_HMAC(
+ password, passwordLength, salt, saltLength, iterations, digest, destinationLength, destination);
+}
Function:
GetMaxMdSize
-Returns the maxium bytes for a message digest.
+Returns the maximum bytes for a message digest.
*/
PALEXPORT int32_t CryptoNative_GetMaxMdSize(void);
+
+/*
+Filled the destination buffer with PBKDF2 derived data.
+
+Implemented by:
+1) Validating input
+2) Calling PKCS5_PBKDF2_HMAC
+
+password and salt may be NULL if their respective length parameters
+are zero. When null, it will be replaced with a pointer to an empty
+location.
+
+Returns -1 on invalid input. On valid input, the return value
+is the return value of PKCS5_PBKDF2_HMAC.
+*/
+PALEXPORT int32_t CryptoNative_Pbkdf2(const char* password,
+ int32_t passwordLength,
+ const unsigned char* salt,
+ int32_t saltLength,
+ int32_t iterations,
+ const EVP_MD* digest,
+ unsigned char* destination,
+ int32_t destinationLength);
public byte[] CryptDeriveKey(string algname, string alghashname, int keySize, byte[] rgbIV) { throw null; }
protected override void Dispose(bool disposing) { }
public override byte[] GetBytes(int cb) { throw null; }
+ public static byte[] Pbkdf2(byte[] password, byte[] salt, int iterations, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, int outputLength) { throw null; }
+ public static byte[] Pbkdf2(System.ReadOnlySpan<byte> password, System.ReadOnlySpan<byte> salt, int iterations, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, int outputLength) { throw null; }
+ public static void Pbkdf2(System.ReadOnlySpan<byte> password, System.ReadOnlySpan<byte> salt, System.Span<byte> destination, int iterations, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { }
+ public static byte[] Pbkdf2(System.ReadOnlySpan<char> password, System.ReadOnlySpan<byte> salt, int iterations, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, int outputLength) { throw null; }
+ public static void Pbkdf2(System.ReadOnlySpan<char> password, System.ReadOnlySpan<byte> salt, System.Span<byte> destination, int iterations, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { }
+ public static byte[] Pbkdf2(string password, byte[] salt, int iterations, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, int outputLength) { throw null; }
public override void Reset() { }
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
return new HmacHashProvider(evpType, key);
}
- private static IntPtr HashAlgorithmToEvp(string hashAlgorithmId) => hashAlgorithmId switch {
+ public static IntPtr HashAlgorithmToEvp(string hashAlgorithmId) => hashAlgorithmId switch {
HashAlgorithmNames.SHA1 => s_evpSha1 == IntPtr.Zero ? (s_evpSha1 = Interop.Crypto.EvpSha1()) : s_evpSha1,
HashAlgorithmNames.SHA256 => s_evpSha256 == IntPtr.Zero ? (s_evpSha256 = Interop.Crypto.EvpSha256()) : s_evpSha256,
HashAlgorithmNames.SHA384 => s_evpSha384 == IntPtr.Zero ? (s_evpSha384 = Interop.Crypto.EvpSha384()) : s_evpSha384,
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Diagnostics;
+using System.Security.Cryptography;
+using PAL_HashAlgorithm = Interop.AppleCrypto.PAL_HashAlgorithm;
+
+namespace Internal.Cryptography
+{
+ internal partial class Pbkdf2Implementation
+ {
+ public static unsafe void Fill(
+ ReadOnlySpan<byte> password,
+ ReadOnlySpan<byte> salt,
+ int iterations,
+ HashAlgorithmName hashAlgorithmName,
+ Span<byte> destination)
+ {
+ Debug.Assert(!destination.IsEmpty);
+
+ PAL_HashAlgorithm prfAlgorithm;
+
+ switch (hashAlgorithmName.Name)
+ {
+ case HashAlgorithmNames.SHA1:
+ prfAlgorithm = PAL_HashAlgorithm.Sha1;
+ break;
+ case HashAlgorithmNames.SHA256:
+ prfAlgorithm = PAL_HashAlgorithm.Sha256;
+ break;
+ case HashAlgorithmNames.SHA384:
+ prfAlgorithm = PAL_HashAlgorithm.Sha384;
+ break;
+ case HashAlgorithmNames.SHA512:
+ prfAlgorithm = PAL_HashAlgorithm.Sha512;
+ break;
+ default:
+ Debug.Fail($"Unexpected hash algorithm '{hashAlgorithmName.Name}'");
+ throw new CryptographicException();
+ };
+
+ Interop.AppleCrypto.Pbkdf2(prfAlgorithm, password, salt, iterations, destination);
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Diagnostics;
+using System.Security.Cryptography;
+
+namespace Internal.Cryptography
+{
+ internal partial class Pbkdf2Implementation
+ {
+ public static unsafe void Fill(
+ ReadOnlySpan<byte> password,
+ ReadOnlySpan<byte> salt,
+ int iterations,
+ HashAlgorithmName hashAlgorithmName,
+ Span<byte> destination)
+ {
+ Debug.Assert(!destination.IsEmpty);
+ Debug.Assert(hashAlgorithmName.Name is not null);
+ IntPtr evpHashType = HashProviderDispenser.HashAlgorithmToEvp(hashAlgorithmName.Name);
+ int result = Interop.Crypto.Pbkdf2(password, salt, iterations, evpHashType, destination);
+ const int Success = 1;
+
+ if (result != Success)
+ {
+ Debug.Assert(result == 0, $"Unexpected result {result}");
+ throw Interop.Crypto.CreateOpenSslCryptographicException();
+ }
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Diagnostics;
+using System.Threading;
+using System.Security.Cryptography;
+using Microsoft.Win32.SafeHandles;
+using BCryptAlgPseudoHandle = Interop.BCrypt.BCryptAlgPseudoHandle;
+using BCryptBuffer = Interop.BCrypt.BCryptBuffer;
+using BCryptOpenAlgorithmProviderFlags = Interop.BCrypt.BCryptOpenAlgorithmProviderFlags;
+using CngBufferDescriptors = Interop.BCrypt.CngBufferDescriptors;
+using NTSTATUS = Interop.BCrypt.NTSTATUS;
+
+namespace Internal.Cryptography
+{
+ internal partial class Pbkdf2Implementation
+ {
+ private static readonly bool s_usePseudoHandles = OperatingSystem.IsWindowsVersionAtLeast(10, 0, 0);
+
+ // For Windows 7 we will use BCryptDeriveKeyPBKDF2. For Windows 8+ we will use BCryptKeyDerivation
+ // since it has better performance.
+ private static readonly bool s_useKeyDerivation = OperatingSystem.IsWindowsVersionAtLeast(8, 0, 0);
+
+ // A cached instance of PBKDF2 for Windows 8, where pseudo handles are not supported.
+ private static SafeBCryptAlgorithmHandle? s_pbkdf2AlgorithmHandle;
+
+ public static unsafe void Fill(
+ ReadOnlySpan<byte> password,
+ ReadOnlySpan<byte> salt,
+ int iterations,
+ HashAlgorithmName hashAlgorithmName,
+ Span<byte> destination)
+ {
+ Debug.Assert(!destination.IsEmpty);
+ Debug.Assert(iterations >= 0);
+ Debug.Assert(hashAlgorithmName.Name is not null);
+
+ if (s_useKeyDerivation)
+ {
+ FillKeyDerivation(password, salt, iterations, hashAlgorithmName.Name, destination);
+ }
+ else
+ {
+ FillDeriveKeyPBKDF2(password, salt, iterations, hashAlgorithmName.Name, destination);
+ }
+ }
+
+ private static unsafe void FillKeyDerivation(
+ ReadOnlySpan<byte> password,
+ ReadOnlySpan<byte> salt,
+ int iterations,
+ string hashAlgorithmName,
+ Span<byte> destination)
+ {
+ SafeBCryptKeyHandle keyHandle;
+ int hashBlockSizeBytes = GetHashBlockSize(hashAlgorithmName);
+
+ // stackalloc 0 to let compiler know this cannot escape.
+ Span<byte> clearSpan = stackalloc byte[0];
+ ReadOnlySpan<byte> symmetricKeyMaterial = stackalloc byte[0];
+ int symmetricKeyMaterialLength;
+
+ if (password.IsEmpty)
+ {
+ // CNG won't accept a null pointer for the password.
+ symmetricKeyMaterial = stackalloc byte[1];
+ symmetricKeyMaterialLength = 0;
+ clearSpan = default;
+ }
+ else if (password.Length <= hashBlockSizeBytes)
+ {
+ // Password is small enough to use as-is.
+ symmetricKeyMaterial = password;
+ symmetricKeyMaterialLength = password.Length;
+ clearSpan = default;
+ }
+ else
+ {
+ // RFC 2104: "The key for HMAC can be of any length (keys longer than B bytes are
+ // first hashed using H).
+ // We denote by B the byte-length of such
+ // blocks (B=64 for all the above mentioned examples of hash functions)
+ //
+ // Windows' PBKDF2 will do this up to a point. To ensure we accept arbitrary inputs for
+ // PBKDF2, we do the hashing ourselves.
+ Span<byte> hashBuffer = stackalloc byte[512 / 8]; // 64 bytes is SHA512, the largest digest handled.
+ int hashBufferSize;
+
+ switch (hashAlgorithmName)
+ {
+ case HashAlgorithmNames.SHA1:
+ hashBufferSize = SHA1.HashData(password, hashBuffer);
+ break;
+ case HashAlgorithmNames.SHA256:
+ hashBufferSize = SHA256.HashData(password, hashBuffer);
+ break;
+ case HashAlgorithmNames.SHA384:
+ hashBufferSize = SHA384.HashData(password, hashBuffer);
+ break;
+ case HashAlgorithmNames.SHA512:
+ hashBufferSize = SHA512.HashData(password, hashBuffer);
+ break;
+ default:
+ Debug.Fail($"Unexpected hash algorithm '{hashAlgorithmName}'");
+ throw new CryptographicException();
+ }
+
+ clearSpan = hashBuffer.Slice(0, hashBufferSize);
+ symmetricKeyMaterial = clearSpan;
+ symmetricKeyMaterialLength = hashBufferSize;
+ }
+
+ Debug.Assert(symmetricKeyMaterial.Length > 0);
+
+ NTSTATUS generateKeyStatus;
+
+ if (s_usePseudoHandles)
+ {
+ fixed (byte* pSymmetricKeyMaterial = symmetricKeyMaterial)
+ {
+ generateKeyStatus = Interop.BCrypt.BCryptGenerateSymmetricKey(
+ (nuint)BCryptAlgPseudoHandle.BCRYPT_PBKDF2_ALG_HANDLE,
+ out keyHandle,
+ pbKeyObject: IntPtr.Zero,
+ cbKeyObject: 0,
+ pSymmetricKeyMaterial,
+ symmetricKeyMaterialLength,
+ dwFlags: 0);
+ }
+ }
+ else
+ {
+ if (s_pbkdf2AlgorithmHandle is null)
+ {
+ NTSTATUS openStatus = Interop.BCrypt.BCryptOpenAlgorithmProvider(
+ out SafeBCryptAlgorithmHandle pbkdf2AlgorithmHandle,
+ Internal.NativeCrypto.BCryptNative.AlgorithmName.Pbkdf2,
+ null,
+ BCryptOpenAlgorithmProviderFlags.None);
+
+ if (openStatus != NTSTATUS.STATUS_SUCCESS)
+ {
+ pbkdf2AlgorithmHandle.Dispose();
+ CryptographicOperations.ZeroMemory(clearSpan);
+ throw Interop.BCrypt.CreateCryptographicException(openStatus);
+ }
+
+ // This might race, and that's okay. Worst case the algorithm is opened
+ // more than once, and the ones that lost will get cleaned up during collection.
+ Interlocked.CompareExchange(ref s_pbkdf2AlgorithmHandle, pbkdf2AlgorithmHandle, null);
+ }
+
+ fixed (byte* pSymmetricKeyMaterial = symmetricKeyMaterial)
+ {
+ generateKeyStatus = Interop.BCrypt.BCryptGenerateSymmetricKey(
+ s_pbkdf2AlgorithmHandle,
+ out keyHandle,
+ pbKeyObject: IntPtr.Zero,
+ cbKeyObject: 0,
+ pSymmetricKeyMaterial,
+ symmetricKeyMaterialLength,
+ dwFlags: 0);
+ }
+ }
+
+ CryptographicOperations.ZeroMemory(clearSpan);
+
+ if (generateKeyStatus != NTSTATUS.STATUS_SUCCESS)
+ {
+ keyHandle.Dispose();
+ throw Interop.BCrypt.CreateCryptographicException(generateKeyStatus);
+ }
+
+ Debug.Assert(!keyHandle.IsInvalid);
+
+ ulong kdfIterations = (ulong)iterations; // Previously asserted to be positive.
+
+ using (keyHandle)
+ fixed (char* pHashAlgorithmName = hashAlgorithmName)
+ fixed (byte* pSalt = salt)
+ fixed (byte* pDestination = destination)
+ {
+ Span<BCryptBuffer> buffers = stackalloc BCryptBuffer[3];
+ buffers[0].BufferType = CngBufferDescriptors.KDF_ITERATION_COUNT;
+ buffers[0].pvBuffer = (IntPtr)(&kdfIterations);
+ buffers[0].cbBuffer = sizeof(ulong);
+
+ buffers[1].BufferType = CngBufferDescriptors.KDF_SALT;
+ buffers[1].pvBuffer = (IntPtr)pSalt;
+ buffers[1].cbBuffer = salt.Length;
+
+ buffers[2].BufferType = CngBufferDescriptors.KDF_HASH_ALGORITHM;
+ buffers[2].pvBuffer = (IntPtr)pHashAlgorithmName;
+
+ // C# spec: "A char* value produced by fixing a string instance always points to a null-terminated string"
+ buffers[2].cbBuffer = checked((hashAlgorithmName.Length + 1) * sizeof(char)); // Add null terminator.
+
+ fixed (BCryptBuffer* pBuffers = buffers)
+ {
+ Interop.BCrypt.BCryptBufferDesc bufferDesc;
+ bufferDesc.ulVersion = Interop.BCrypt.BCRYPTBUFFER_VERSION;
+ bufferDesc.cBuffers = buffers.Length;
+ bufferDesc.pBuffers = (IntPtr)pBuffers;
+
+ NTSTATUS deriveStatus = Interop.BCrypt.BCryptKeyDerivation(
+ keyHandle,
+ &bufferDesc,
+ pDestination,
+ destination.Length,
+ out uint resultLength,
+ dwFlags: 0);
+
+ if (deriveStatus != NTSTATUS.STATUS_SUCCESS)
+ {
+ throw Interop.BCrypt.CreateCryptographicException(deriveStatus);
+ }
+
+ if (destination.Length != resultLength)
+ {
+ Debug.Fail("PBKDF2 resultLength != destination.Length");
+ throw new CryptographicException();
+ }
+ }
+ }
+ }
+
+ private static unsafe void FillDeriveKeyPBKDF2(
+ ReadOnlySpan<byte> password,
+ ReadOnlySpan<byte> salt,
+ int iterations,
+ string hashAlgorithmName,
+ Span<byte> destination)
+ {
+ const BCryptOpenAlgorithmProviderFlags OpenAlgorithmFlags = BCryptOpenAlgorithmProviderFlags.BCRYPT_ALG_HANDLE_HMAC_FLAG;
+
+ // This code path will only be taken on Windows 7, so we can assume pseudo handles are not supported.
+ // Do not dispose handle since it is shared and cached.
+ SafeBCryptAlgorithmHandle handle =
+ Interop.BCrypt.BCryptAlgorithmCache.GetCachedBCryptAlgorithmHandle(hashAlgorithmName, OpenAlgorithmFlags, out _);
+
+ fixed (byte* pPassword = password)
+ fixed (byte* pSalt = salt)
+ fixed (byte* pDestination = destination)
+ {
+ NTSTATUS status = Interop.BCrypt.BCryptDeriveKeyPBKDF2(
+ handle,
+ pPassword,
+ password.Length,
+ pSalt,
+ salt.Length,
+ (ulong)iterations,
+ pDestination,
+ destination.Length,
+ dwFlags: 0);
+
+ if (status != NTSTATUS.STATUS_SUCCESS)
+ {
+ throw Interop.BCrypt.CreateCryptographicException(status);
+ }
+ }
+ }
+
+ private static int GetHashBlockSize(string hashAlgorithmName)
+ {
+ // Block sizes per NIST FIPS pub 180-4.
+ switch (hashAlgorithmName)
+ {
+ case HashAlgorithmNames.SHA1:
+ case HashAlgorithmNames.SHA256:
+ return 512 / 8;
+ case HashAlgorithmNames.SHA384:
+ case HashAlgorithmNames.SHA512:
+ return 1024 / 8;
+ default:
+ Debug.Fail($"Unexpected hash algorithm '{hashAlgorithmName}'");
+ throw new CryptographicException();
+ }
+ }
+ }
+}
<Compile Include="System\Security\Cryptography\Rijndael.cs" />
<Compile Include="System\Security\Cryptography\RijndaelManaged.cs" />
<Compile Include="System\Security\Cryptography\Rfc2898DeriveBytes.cs" />
+ <Compile Include="System\Security\Cryptography\Rfc2898DeriveBytes.OneShot.cs" />
<Compile Include="System\Security\Cryptography\RSA.cs" />
<Compile Include="System\Security\Cryptography\RSA.Xml.cs" />
<Compile Include="System\Security\Cryptography\RSAEncryptionPadding.cs" />
<Compile Include="Internal\Cryptography\DesImplementation.Windows.cs" />
<Compile Include="Internal\Cryptography\HashProviderDispenser.Windows.cs" />
<Compile Include="Internal\Cryptography\RandomNumberGeneratorImplementation.Windows.cs" />
+ <Compile Include="Internal\Cryptography\Pbkdf2Implementation.Windows.cs" />
<Compile Include="Internal\Cryptography\RC2Implementation.Windows.cs" />
<Compile Include="Internal\Cryptography\TripleDesImplementation.Windows.cs" />
<Compile Include="$(CommonPath)Internal\Cryptography\BasicSymmetricCipherBCrypt.cs"
Link="Common\Interop\Windows\BCrypt\Interop.BCryptDuplicateHash.cs" />
<Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptHash.cs"
Link="Common\Interop\Windows\BCrypt\Interop.BCryptHash.cs" />
+ <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptAlgPseudoHandle.cs"
+ Link="Common\Interop\Windows\BCrypt\Interop.BCryptAlgPseudoHandle.cs" />
+ <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptDeriveKeyPBKDF2.cs"
+ Link="Common\Interop\Windows\BCrypt\Interop.BCryptDeriveKeyPBKDF2.cs" />
+ <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptDestroyKey.cs"
+ Link="Common\Interop\Windows\BCrypt\Interop.BCryptDestroyKey.cs" />
<Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptHashData.cs"
Link="Common\Interop\Windows\BCrypt\Interop.BCryptHashData.cs" />
<Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptFinishHash.cs"
Link="Common\Interop\Windows\BCrypt\Interop.BCryptFinishHash.cs" />
<Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptGetProperty.cs"
Link="Common\Interop\Windows\BCrypt\Interop.BCryptGetProperty.cs" />
+ <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptGenerateSymmetricKey.cs"
+ Link="Common\Interop\Windows\BCrypt\Interop.BCryptGenerateSymmetricKey.cs" />
+ <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptKeyDerivation.cs"
+ Link="Common\Interop\Windows\BCrypt\Interop.BCryptKeyDerivation.cs" />
<Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptPropertyStrings.cs"
Link="Common\Interop\Windows\BCrypt\Interop.BCryptPropertyStrings.cs" />
<Compile Include="$(CommonPath)Interop\Windows\NCrypt\Interop.NCryptDeriveKeyMaterial.cs"
Link="Microsoft\Win32\SafeHandles\SafeBCryptAlgorithmHandle.cs" />
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeBCryptHashHandle.cs"
Link="Microsoft\Win32\SafeHandles\SafeBCryptHashHandle.cs" />
+ <Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeBCryptKeyHandle.cs"
+ Link="Microsoft\Win32\SafeHandles\SafeBCryptKeyHandle.cs" />
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeUnicodeStringHandle.cs"
Link="Microsoft\Win32\SafeHandles\SafeUnicodeStringHandle.cs" />
<Compile Include="$(CommonPath)Internal\Cryptography\CngCommon.Hash.cs"
<Compile Include="Internal\Cryptography\HashProviderDispenser.Unix.cs" />
<Compile Include="Internal\Cryptography\OpenSslCipher.cs" />
<Compile Condition="'$(TargetsBrowser)' != 'true'" Include="Internal\Cryptography\RandomNumberGeneratorImplementation.Unix.cs" />
+ <Compile Condition="'$(TargetsBrowser)' != 'true'" Include="Internal\Cryptography\Pbkdf2Implementation.Unix.cs" />
<Compile Include="Internal\Cryptography\RC2Implementation.Unix.cs" />
<Compile Include="Internal\Cryptography\TripleDesImplementation.Unix.cs" />
<Compile Include="System\Security\Cryptography\ECDsaOpenSsl.cs" />
Link="Common\Interop\OSX\System.Security.Cryptography.Native.Apple\Interop.SecKeyRef.Export.cs" />
<Compile Include="$(CommonPath)Interop\OSX\System.Security.Cryptography.Native.Apple\Interop.Symmetric.cs"
Link="Common\Interop\OSX\System.Security.Cryptography.Native.Apple\Interop.Symmetric.cs" />
+ <Compile Include="$(CommonPath)Interop\OSX\System.Security.Cryptography.Native.Apple\Interop.Pbkdf2.cs"
+ Link="Common\Interop\OSX\System.Security.Cryptography.Native.Apple\Interop.Pbkdf2.cs" />
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeCreateHandle.OSX.cs"
Link="Common\Microsoft\Win32\SafeHandles\SafeCreateHandle.OSX.cs" />
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeHandleCache.cs"
<Compile Include="Internal\Cryptography\DesImplementation.OSX.cs" />
<Compile Include="Internal\Cryptography\HashProviderDispenser.OSX.cs" />
<Compile Include="Internal\Cryptography\RandomNumberGeneratorImplementation.OSX.cs" />
+ <Compile Include="Internal\Cryptography\Pbkdf2Implementation.OSX.cs" />
<Compile Include="Internal\Cryptography\RC2Implementation.OSX.cs" />
<Compile Include="Internal\Cryptography\TripleDesImplementation.OSX.cs" />
<Compile Include="System\Security\Cryptography\ECDiffieHellman.Create.SecurityTransforms.cs" />
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+using System.Text;
+using Internal.Cryptography;
+
+namespace System.Security.Cryptography
+{
+ public partial class Rfc2898DeriveBytes
+ {
+ // Throwing UTF8 on invalid input.
+ private static readonly Encoding s_throwingUtf8Encoding = new UTF8Encoding(false, true);
+
+ /// <summary>
+ /// Creates a PBKDF2 derived key from password bytes.
+ /// </summary>
+ /// <param name="password">The password used to derive the key.</param>
+ /// <param name="salt">The key salt used to derive the key.</param>
+ /// <param name="iterations">The number of iterations for the operation.</param>
+ /// <param name="hashAlgorithm">The hash algorithm to use to derive the key.</param>
+ /// <param name="outputLength">The size of key to derive.</param>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="password" /> or <paramref name="salt" /> is <see langword="null" />.
+ /// </exception>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <para><paramref name="outputLength" /> is not zero or a positive value.</para>
+ /// <para>-or-</para>
+ /// <para><paramref name="iterations" /> is not a positive value.</para>
+ /// </exception>
+ /// <exception cref="ArgumentException">
+ /// <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" />
+ /// that is empty or <see langword="null" />.
+ /// </exception>
+ /// <exception cref="CryptographicException">
+ /// <paramref name="hashAlgorithm" /> is an unsupported hash algorithm. Supported algorithms
+ /// are <see cref="HashAlgorithmName.SHA1" />, <see cref="HashAlgorithmName.SHA256" />,
+ /// <see cref="HashAlgorithmName.SHA384" />, and <see cref="HashAlgorithmName.SHA512" />.
+ /// </exception>
+ public static byte[] Pbkdf2(
+ byte[] password,
+ byte[] salt,
+ int iterations,
+ HashAlgorithmName hashAlgorithm,
+ int outputLength)
+ {
+ if (password is null)
+ throw new ArgumentNullException(nameof(password));
+ if (salt is null)
+ throw new ArgumentNullException(nameof(salt));
+
+ return Pbkdf2(new ReadOnlySpan<byte>(password), new ReadOnlySpan<byte>(salt), iterations, hashAlgorithm, outputLength);
+ }
+
+ /// <summary>
+ /// Creates a PBKDF2 derived key from password bytes.
+ /// </summary>
+ /// <param name="password">The password used to derive the key.</param>
+ /// <param name="salt">The key salt used to derive the key.</param>
+ /// <param name="iterations">The number of iterations for the operation.</param>
+ /// <param name="hashAlgorithm">The hash algorithm to use to derive the key.</param>
+ /// <param name="outputLength">The size of key to derive.</param>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <para><paramref name="outputLength" /> is not zero or a positive value.</para>
+ /// <para>-or-</para>
+ /// <para><paramref name="iterations" /> is not a positive value.</para>
+ /// </exception>
+ /// <exception cref="ArgumentException">
+ /// <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" />
+ /// that is empty or <see langword="null" />.
+ /// </exception>
+ /// <exception cref="CryptographicException">
+ /// <paramref name="hashAlgorithm" /> is an unsupported hash algorithm. Supported algorithms
+ /// are <see cref="HashAlgorithmName.SHA1" />, <see cref="HashAlgorithmName.SHA256" />,
+ /// <see cref="HashAlgorithmName.SHA384" />, and <see cref="HashAlgorithmName.SHA512" />.
+ /// </exception>
+ public static byte[] Pbkdf2(
+ ReadOnlySpan<byte> password,
+ ReadOnlySpan<byte> salt,
+ int iterations,
+ HashAlgorithmName hashAlgorithm,
+ int outputLength)
+ {
+ if (iterations <= 0)
+ throw new ArgumentOutOfRangeException(nameof(iterations), SR.ArgumentOutOfRange_NeedPosNum);
+ if (outputLength < 0)
+ throw new ArgumentOutOfRangeException(nameof(outputLength), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+ ValidateHashAlgorithm(hashAlgorithm);
+
+ byte[] result = new byte[outputLength];
+ Pbkdf2Core(password, salt, result, iterations, hashAlgorithm);
+ return result;
+ }
+
+ /// <summary>
+ /// Fills a buffer with a PBKDF2 derived key.
+ /// </summary>
+ /// <param name="password">The password used to derive the key.</param>
+ /// <param name="salt">The key salt used to derive the key.</param>
+ /// <param name="iterations">The number of iterations for the operation.</param>
+ /// <param name="hashAlgorithm">The hash algorithm to use to derive the key.</param>
+ /// <param name="destination">The buffer to fill with a derived key.</param>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="iterations" /> is not a positive value.
+ /// </exception>
+ /// <exception cref="ArgumentException">
+ /// <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" />
+ /// that is empty or <see langword="null" />.
+ /// </exception>
+ /// <exception cref="CryptographicException">
+ /// <paramref name="hashAlgorithm" /> is an unsupported hash algorithm. Supported algorithms
+ /// are <see cref="HashAlgorithmName.SHA1" />, <see cref="HashAlgorithmName.SHA256" />,
+ /// <see cref="HashAlgorithmName.SHA384" />, and <see cref="HashAlgorithmName.SHA512" />.
+ /// </exception>
+ public static void Pbkdf2(
+ ReadOnlySpan<byte> password,
+ ReadOnlySpan<byte> salt,
+ Span<byte> destination,
+ int iterations,
+ HashAlgorithmName hashAlgorithm)
+ {
+ if (iterations <= 0)
+ throw new ArgumentOutOfRangeException(nameof(iterations), SR.ArgumentOutOfRange_NeedPosNum);
+
+ ValidateHashAlgorithm(hashAlgorithm);
+
+ Pbkdf2Core(password, salt, destination, iterations, hashAlgorithm);
+ }
+
+ /// <summary>
+ /// Creates a PBKDF2 derived key from a password.
+ /// </summary>
+ /// <param name="password">The password used to derive the key.</param>
+ /// <param name="salt">The key salt used to derive the key.</param>
+ /// <param name="iterations">The number of iterations for the operation.</param>
+ /// <param name="hashAlgorithm">The hash algorithm to use to derive the key.</param>
+ /// <param name="outputLength">The size of key to derive.</param>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="password" /> or <paramref name="salt" /> is <see langword="null" />.
+ /// </exception>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <para><paramref name="outputLength" /> is not zero or a positive value.</para>
+ /// <para>-or-</para>
+ /// <para><paramref name="iterations" /> is not a positive value.</para>
+ /// </exception>
+ /// <exception cref="ArgumentException">
+ /// <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" />
+ /// that is empty or <see langword="null" />.
+ /// </exception>
+ /// <exception cref="CryptographicException">
+ /// <paramref name="hashAlgorithm" /> is an unsupported hash algorithm. Supported algorithms
+ /// are <see cref="HashAlgorithmName.SHA1" />, <see cref="HashAlgorithmName.SHA256" />,
+ /// <see cref="HashAlgorithmName.SHA384" />, and <see cref="HashAlgorithmName.SHA512" />.
+ /// </exception>
+ /// <exception cref="EncoderFallbackException">
+ /// <paramref name="password" /> contains text that cannot be converted to UTF8.
+ /// </exception>
+ /// <remarks>
+ /// The <paramref name="password" /> will be converted to bytes using the UTF8 encoding. For
+ /// other encodings, convert the password string to bytes using the appropriate <see cref="System.Text.Encoding" />
+ /// and use <see cref="Pbkdf2(byte[], byte[], int, HashAlgorithmName, int)" />.
+ /// </remarks>
+ public static byte[] Pbkdf2(
+ string password,
+ byte[] salt,
+ int iterations,
+ HashAlgorithmName hashAlgorithm,
+ int outputLength)
+ {
+ if (password is null)
+ throw new ArgumentNullException(nameof(password));
+ if (salt is null)
+ throw new ArgumentNullException(nameof(salt));
+
+ return Pbkdf2(password.AsSpan(), new ReadOnlySpan<byte>(salt), iterations, hashAlgorithm, outputLength);
+ }
+
+ /// <summary>
+ /// Creates a PBKDF2 derived key from a password.
+ /// </summary>
+ /// <param name="password">The password used to derive the key.</param>
+ /// <param name="salt">The key salt used to derive the key.</param>
+ /// <param name="iterations">The number of iterations for the operation.</param>
+ /// <param name="hashAlgorithm">The hash algorithm to use to derive the key.</param>
+ /// <param name="outputLength">The size of key to derive.</param>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <para><paramref name="outputLength" /> is not zero or a positive value.</para>
+ /// <para>-or-</para>
+ /// <para><paramref name="iterations" /> is not a positive value.</para>
+ /// </exception>
+ /// <exception cref="ArgumentException">
+ /// <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" />
+ /// that is empty or <see langword="null" />.
+ /// </exception>
+ /// <exception cref="CryptographicException">
+ /// <paramref name="hashAlgorithm" /> is an unsupported hash algorithm. Supported algorithms
+ /// are <see cref="HashAlgorithmName.SHA1" />, <see cref="HashAlgorithmName.SHA256" />,
+ /// <see cref="HashAlgorithmName.SHA384" />, and <see cref="HashAlgorithmName.SHA512" />.
+ /// </exception>
+ /// <exception cref="EncoderFallbackException">
+ /// <paramref name="password" /> contains text that cannot be converted to UTF8.
+ /// </exception>
+ /// <remarks>
+ /// The <paramref name="password" /> will be converted to bytes using the UTF8 encoding. For
+ /// other encodings, convert the password string to bytes using the appropriate <see cref="System.Text.Encoding" />
+ /// and use <see cref="Pbkdf2(ReadOnlySpan{byte}, ReadOnlySpan{byte}, int, HashAlgorithmName, int)" />.
+ /// </remarks>
+ public static byte[] Pbkdf2(
+ ReadOnlySpan<char> password,
+ ReadOnlySpan<byte> salt,
+ int iterations,
+ HashAlgorithmName hashAlgorithm,
+ int outputLength)
+ {
+ if (outputLength < 0)
+ throw new ArgumentOutOfRangeException(nameof(outputLength), SR.ArgumentOutOfRange_NeedNonNegNum);
+ if (iterations <= 0)
+ throw new ArgumentOutOfRangeException(nameof(iterations), SR.ArgumentOutOfRange_NeedPosNum);
+
+ ValidateHashAlgorithm(hashAlgorithm);
+
+ byte[] result = new byte[outputLength];
+ Pbkdf2Core(password, salt, result, iterations, hashAlgorithm);
+ return result;
+ }
+
+ /// <summary>
+ /// Fills a buffer with a PBKDF2 derived key.
+ /// </summary>
+ /// <param name="password">The password used to derive the key.</param>
+ /// <param name="salt">The key salt used to derive the key.</param>
+ /// <param name="iterations">The number of iterations for the operation.</param>
+ /// <param name="hashAlgorithm">The hash algorithm to use to derive the key.</param>
+ /// <param name="destination">The buffer to fill with a derived key.</param>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="iterations" /> is not a positive value.
+ /// </exception>
+ /// <exception cref="ArgumentException">
+ /// <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" />
+ /// that is empty or <see langword="null" />.
+ /// </exception>
+ /// <exception cref="CryptographicException">
+ /// <paramref name="hashAlgorithm" /> is an unsupported hash algorithm. Supported algorithms
+ /// are <see cref="HashAlgorithmName.SHA1" />, <see cref="HashAlgorithmName.SHA256" />,
+ /// <see cref="HashAlgorithmName.SHA384" />, and <see cref="HashAlgorithmName.SHA512" />.
+ /// </exception>
+ /// <exception cref="EncoderFallbackException">
+ /// <paramref name="password" /> contains text that cannot be converted to UTF8.
+ /// </exception>
+ /// <remarks>
+ /// The <paramref name="password" /> will be converted to bytes using the UTF8 encoding. For
+ /// other encodings, convert the password string to bytes using the appropriate <see cref="System.Text.Encoding" />
+ /// and use <see cref="Pbkdf2(ReadOnlySpan{byte}, ReadOnlySpan{byte}, Span{byte}, int, HashAlgorithmName)" />.
+ /// </remarks>
+ public static void Pbkdf2(
+ ReadOnlySpan<char> password,
+ ReadOnlySpan<byte> salt,
+ Span<byte> destination,
+ int iterations,
+ HashAlgorithmName hashAlgorithm)
+ {
+ if (iterations <= 0)
+ throw new ArgumentOutOfRangeException(nameof(iterations), SR.ArgumentOutOfRange_NeedPosNum);
+
+ ValidateHashAlgorithm(hashAlgorithm);
+
+ Pbkdf2Core(password, salt, destination, iterations, hashAlgorithm);
+ }
+
+ private static void Pbkdf2Core(
+ ReadOnlySpan<char> password,
+ ReadOnlySpan<byte> salt,
+ Span<byte> destination,
+ int iterations,
+ HashAlgorithmName hashAlgorithm)
+ {
+ Debug.Assert(hashAlgorithm.Name is not null);
+ Debug.Assert(iterations > 0);
+
+ if (destination.IsEmpty)
+ {
+ return;
+ }
+
+ const int MaxPasswordStackSize = 256;
+
+ byte[]? rentedPasswordBuffer = null;
+ int maxEncodedSize = s_throwingUtf8Encoding.GetMaxByteCount(password.Length);
+
+ Span<byte> passwordBuffer = maxEncodedSize > MaxPasswordStackSize ?
+ (rentedPasswordBuffer = CryptoPool.Rent(maxEncodedSize)) :
+ stackalloc byte[MaxPasswordStackSize];
+ int passwordBytesWritten = s_throwingUtf8Encoding.GetBytes(password, passwordBuffer);
+ Span<byte> passwordBytes = passwordBuffer.Slice(0, passwordBytesWritten);
+
+ try
+ {
+ Pbkdf2Implementation.Fill(passwordBytes, salt, iterations, hashAlgorithm, destination);
+ }
+ finally
+ {
+ CryptographicOperations.ZeroMemory(passwordBytes);
+ }
+
+ if (rentedPasswordBuffer is not null)
+ {
+ CryptoPool.Return(rentedPasswordBuffer, clearSize: 0); // manually cleared above.
+ }
+ }
+
+ private static void Pbkdf2Core(
+ ReadOnlySpan<byte> password,
+ ReadOnlySpan<byte> salt,
+ Span<byte> destination,
+ int iterations,
+ HashAlgorithmName hashAlgorithm)
+ {
+ Debug.Assert(hashAlgorithm.Name is not null);
+ Debug.Assert(iterations > 0);
+
+ if (destination.IsEmpty)
+ {
+ return;
+ }
+
+ Pbkdf2Implementation.Fill(password, salt, iterations, hashAlgorithm, destination);
+ }
+
+ private static void ValidateHashAlgorithm(HashAlgorithmName hashAlgorithm)
+ {
+ if (string.IsNullOrEmpty(hashAlgorithm.Name))
+ throw new ArgumentException(SR.Cryptography_HashAlgorithmNameNullOrEmpty, nameof(hashAlgorithm));
+
+ string hashAlgorithmName = hashAlgorithm.Name;
+
+ // MD5 intentionally left out.
+ if (hashAlgorithmName != HashAlgorithmName.SHA1.Name &&
+ hashAlgorithmName != HashAlgorithmName.SHA256.Name &&
+ hashAlgorithmName != HashAlgorithmName.SHA384.Name &&
+ hashAlgorithmName != HashAlgorithmName.SHA512.Name)
+ {
+ throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmName));
+ }
+ }
+ }
+}
namespace System.Security.Cryptography
{
[UnsupportedOSPlatform("browser")]
- public class Rfc2898DeriveBytes : DeriveBytes
+ public partial class Rfc2898DeriveBytes : DeriveBytes
{
private const int MinimumSaltSize = 8;
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Text;
+using Xunit;
+using Test.Cryptography;
+
+namespace System.Security.Cryptography.DeriveBytesTests
+{
+ [SkipOnMono("Not supported on Browser", TestPlatforms.Browser)]
+ public static class Rfc2898OneShotTests
+ {
+ private const string Password = "tired";
+
+ private static readonly byte[] s_passwordBytes = Encoding.UTF8.GetBytes(Password);
+ private static readonly byte[] s_salt = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
+ private static readonly int s_extractLength = 14;
+
+ [Fact]
+ public static void Pbkdf2_PasswordBytes_NullPassword()
+ {
+ AssertExtensions.Throws<ArgumentNullException>("password", () =>
+ Rfc2898DeriveBytes.Pbkdf2(
+ password: (byte[])null, s_salt, iterations: 1, HashAlgorithmName.SHA256, s_extractLength)
+ );
+ }
+
+ [Fact]
+ public static void Pbkdf2_PasswordBytes_NullSalt()
+ {
+ AssertExtensions.Throws<ArgumentNullException>("salt", () =>
+ Rfc2898DeriveBytes.Pbkdf2(
+ s_passwordBytes, salt: (byte[])null, iterations: 1, HashAlgorithmName.SHA256, s_extractLength)
+ );
+ }
+
+ [Fact]
+ public static void Pbkdf2_PasswordBytes_SaltBytes_SaltEmpty()
+ {
+ byte[] expectedKey = "1E437A1C79D75BE61E91141DAE20".HexToByteArray();
+ byte[] key = Rfc2898DeriveBytes.Pbkdf2(
+ new byte[0], salt: new byte[0], iterations: 1, HashAlgorithmName.SHA1, s_extractLength);
+ Assert.Equal(expectedKey, key);
+ }
+
+ [Fact]
+ public static void Pbkdf2_PasswordBytes_SaltBytes_IterationsNegative()
+ {
+ AssertExtensions.Throws<ArgumentOutOfRangeException>("iterations", () =>
+ Rfc2898DeriveBytes.Pbkdf2(
+ s_passwordBytes, s_salt, iterations: -1, HashAlgorithmName.SHA256, s_extractLength)
+ );
+ }
+
+ [Fact]
+ public static void Pbkdf2_PasswordBytes_SaltBytes_OutputLengthNegative()
+ {
+ AssertExtensions.Throws<ArgumentOutOfRangeException>("outputLength", () =>
+ Rfc2898DeriveBytes.Pbkdf2(
+ s_passwordBytes, s_salt, iterations: 1, HashAlgorithmName.SHA256, -1)
+ );
+ }
+
+ [Fact]
+ public static void Pbkdf2_PasswordBytes_BogusHash()
+ {
+ Assert.Throws<CryptographicException>(() =>
+ Rfc2898DeriveBytes.Pbkdf2(
+ s_passwordBytes, s_salt, iterations: 1, new HashAlgorithmName("BLAH"), s_extractLength)
+ );
+ }
+
+ [Fact]
+ public static void Pbkdf2_PasswordBytes_NullHashName()
+ {
+ AssertExtensions.Throws<ArgumentException>("hashAlgorithm", () =>
+ Rfc2898DeriveBytes.Pbkdf2(
+ s_passwordBytes, s_salt, iterations: 1, default(HashAlgorithmName), s_extractLength)
+ );
+ }
+
+ [Fact]
+ public static void Pbkdf2_PasswordBytes_EmptyHashName()
+ {
+ AssertExtensions.Throws<ArgumentException>("hashAlgorithm", () =>
+ Rfc2898DeriveBytes.Pbkdf2(
+ s_passwordBytes, s_salt, iterations: 1, new HashAlgorithmName(""), s_extractLength)
+ );
+ }
+
+ [Fact]
+ public static void Pbkdf2_PasswordString_NullPassword()
+ {
+ AssertExtensions.Throws<ArgumentNullException>("password", () =>
+ Rfc2898DeriveBytes.Pbkdf2(
+ password: (string)null, s_salt, iterations: 1, HashAlgorithmName.SHA256, s_extractLength)
+ );
+ }
+
+ [Fact]
+ public static void Pbkdf2_PasswordString_NullSalt()
+ {
+ AssertExtensions.Throws<ArgumentNullException>("salt", () =>
+ Rfc2898DeriveBytes.Pbkdf2(
+ Password, salt: null, iterations: 1, HashAlgorithmName.SHA256, s_extractLength)
+ );
+ }
+
+ [Fact]
+ public static void Pbkdf2_PasswordString_SaltBytes_SaltEmpty()
+ {
+ byte[] expectedKey = "1E437A1C79D75BE61E91141DAE20".HexToByteArray();
+ byte[] key = Rfc2898DeriveBytes.Pbkdf2(
+ password: "", salt: new byte[0], iterations: 1, HashAlgorithmName.SHA1, s_extractLength);
+ Assert.Equal(expectedKey, key);
+ }
+
+ [Fact]
+ public static void Pbkdf2_PasswordString_SaltBytes_IterationsNegative()
+ {
+ AssertExtensions.Throws<ArgumentOutOfRangeException>("iterations", () =>
+ Rfc2898DeriveBytes.Pbkdf2(
+ Password, s_salt, iterations: -1, HashAlgorithmName.SHA256, s_extractLength)
+ );
+ }
+
+ [Fact]
+ public static void Pbkdf2_PasswordString_SaltBytes_OutputLengthNegative()
+ {
+ AssertExtensions.Throws<ArgumentOutOfRangeException>("outputLength", () =>
+ Rfc2898DeriveBytes.Pbkdf2(
+ Password, s_salt, iterations: 1, HashAlgorithmName.SHA256, -1)
+ );
+ }
+
+ [Fact]
+ public static void Pbkdf2_PasswordString_BogusHash()
+ {
+ Assert.Throws<CryptographicException>(() =>
+ Rfc2898DeriveBytes.Pbkdf2(
+ Password, s_salt, iterations: 1, new HashAlgorithmName("BLAH"), s_extractLength)
+ );
+ }
+
+ [Fact]
+ public static void Pbkdf2_PasswordString_NullHashName()
+ {
+ AssertExtensions.Throws<ArgumentException>("hashAlgorithm", () =>
+ Rfc2898DeriveBytes.Pbkdf2(
+ Password, s_salt, iterations: 1, default(HashAlgorithmName), s_extractLength)
+ );
+ }
+
+ [Fact]
+ public static void Pbkdf2_PasswordString_EmptyHashName()
+ {
+ AssertExtensions.Throws<ArgumentException>("hashAlgorithm", () =>
+ Rfc2898DeriveBytes.Pbkdf2(
+ Password, s_salt, iterations: 1, new HashAlgorithmName(""), s_extractLength)
+ );
+ }
+
+ [Fact]
+ public static void Pbkdf2_PasswordString_InvalidUtf8()
+ {
+ Assert.Throws<EncoderFallbackException>(() =>
+ Rfc2898DeriveBytes.Pbkdf2(
+ "\uD800", s_salt, iterations: 1, HashAlgorithmName.SHA256, s_extractLength));
+ }
+
+ [Fact]
+ public static void Pbkdf2_Password_Salt_Overlapping_Completely()
+ {
+ Span<byte> buffer = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
+ byte[] expected = { 0xBE, 0xA4, 0xEE, 0x0E, 0xC3, 0x98, 0xBF, 0x32 };
+ Rfc2898DeriveBytes.Pbkdf2(buffer, buffer, buffer, iterations: 1, HashAlgorithmName.SHA256);
+ Assert.Equal(expected, buffer.ToArray());
+ }
+
+ [Fact]
+ public static void Pbkdf2_Password_Salt_Overlapping_Forward()
+ {
+ Span<byte> buffer = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 0xFF };
+ Span<byte> output = buffer[1..];
+ Span<byte> inputs = buffer[..^1];
+ byte[] expected = { 0xBE, 0xA4, 0xEE, 0x0E, 0xC3, 0x98, 0xBF, 0x32 };
+ Rfc2898DeriveBytes.Pbkdf2(inputs, inputs, output, iterations: 1, HashAlgorithmName.SHA256);
+ Assert.Equal(expected, output.ToArray());
+ }
+
+ [Fact]
+ public static void Pbkdf2_Password_Salt_Overlapping_Backward()
+ {
+ Span<byte> buffer = new byte[] { 0xFF, 1, 2, 3, 4, 5, 6, 7, 8 };
+ Span<byte> output = buffer[..^1];
+ Span<byte> inputs = buffer[1..];
+ byte[] expected = { 0xBE, 0xA4, 0xEE, 0x0E, 0xC3, 0x98, 0xBF, 0x32 };
+ Rfc2898DeriveBytes.Pbkdf2(inputs, inputs, output, iterations: 1, HashAlgorithmName.SHA256);
+ Assert.Equal(expected, output.ToArray());
+ }
+
+ [Theory]
+ [MemberData(nameof(Pbkdf2_PasswordBytes_Compare_Data))]
+ public static void Pbkdf2_PasswordBytes_Compare(
+ string hashAlgorithm,
+ int length,
+ int iterations,
+ string passwordHex,
+ string saltHex)
+ {
+ byte[] password = Convert.FromHexString(passwordHex);
+ byte[] salt = Convert.FromHexString(saltHex);
+ HashAlgorithmName hashAlgorithmName = new HashAlgorithmName(hashAlgorithm);
+ byte[] key1;
+
+ using (Rfc2898DeriveBytes instanceKdf = new Rfc2898DeriveBytes(password, salt, iterations, hashAlgorithmName))
+ {
+ key1 = instanceKdf.GetBytes(length);
+ }
+
+ // byte array allocating
+ byte[] key2 = Rfc2898DeriveBytes.Pbkdf2(password, salt, iterations, hashAlgorithmName, length);
+ Assert.Equal(key1, key2);
+
+ Span<byte> destinationBuffer = new byte[length + 2];
+ Span<byte> destination = destinationBuffer.Slice(1, length);
+ Rfc2898DeriveBytes.Pbkdf2(password, salt, destination, iterations, hashAlgorithmName);
+
+ Assert.True(key1.AsSpan().SequenceEqual(destination), "key1 == destination");
+ Assert.Equal(0, destinationBuffer[^1]); // Make sure we didn't write past the destination
+ Assert.Equal(0, destinationBuffer[0]); // Make sure we didn't write before the destination
+ }
+
+ [Theory]
+ [MemberData(nameof(Pbkdf2_PasswordString_Compare_Data))]
+ public static void Pbkdf2_PasswordString_Compare(
+ string hashAlgorithm,
+ int length,
+ int iterations,
+ string password,
+ string saltHex)
+ {
+ byte[] salt = Convert.FromHexString(saltHex);
+ HashAlgorithmName hashAlgorithmName = new HashAlgorithmName(hashAlgorithm);
+ byte[] key1;
+
+ using (Rfc2898DeriveBytes instanceKdf = new Rfc2898DeriveBytes(password, salt, iterations, hashAlgorithmName))
+ {
+ key1 = instanceKdf.GetBytes(length);
+ }
+
+ // byte array allocating
+ byte[] key2 = Rfc2898DeriveBytes.Pbkdf2(password, salt, iterations, hashAlgorithmName, length);
+ Assert.Equal(key1, key2);
+
+ Span<byte> destinationBuffer = new byte[length + 2];
+ Span<byte> destination = destinationBuffer.Slice(1, length);
+ Rfc2898DeriveBytes.Pbkdf2(password, salt, destination, iterations, hashAlgorithmName);
+
+ Assert.True(key1.AsSpan().SequenceEqual(destination), "key1 == destination");
+ Assert.Equal(0, destinationBuffer[^1]); // Make sure we didn't write past the destination
+ Assert.Equal(0, destinationBuffer[0]); // Make sure we didn't write before the destination
+ }
+
+ [Theory]
+ [MemberData(nameof(Pbkdf2_Rfc6070_Vectors))]
+ public static void Pbkdf2_Rfc6070(string password, string salt, int iterations, string expectedHex)
+ {
+ byte[] expected = expectedHex.HexToByteArray();
+ byte[] saltBytes = Encoding.UTF8.GetBytes(salt);
+ byte[] actual = Rfc2898DeriveBytes.Pbkdf2(password, saltBytes, iterations, HashAlgorithmName.SHA1, expected.Length);
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ [OuterLoop("Uses a high number of iterations that can take over 20 seconds on some machines")]
+ public static void Pbkdf2_Rfc6070_HighIterations()
+ {
+ string password = "password";
+ int iterations = 16777216;
+ byte[] expected = "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984".HexToByteArray();
+ byte[] salt = Encoding.UTF8.GetBytes("salt");
+ byte[] actual = Rfc2898DeriveBytes.Pbkdf2(password, salt, iterations, HashAlgorithmName.SHA1, expected.Length);
+ Assert.Equal(expected, actual);
+ }
+
+ public static IEnumerable<object[]> Pbkdf2_PasswordBytes_Compare_Data()
+ {
+ string largeInputHex = new string('A', 8192); // 8192 hex characters = 4096 bytes.
+
+ foreach (HashAlgorithmName hashAlgorithm in SupportedHashAlgorithms)
+ {
+ // hashAlgorithm, length, iterations, passwordHex, saltHex
+ yield return new object[] { hashAlgorithm.Name, 1, 1, s_passwordBytes.ByteArrayToHex(), s_salt.ByteArrayToHex() };
+ yield return new object[] { hashAlgorithm.Name, 1, 1, "", s_salt.ByteArrayToHex() };
+ yield return new object[] { hashAlgorithm.Name, 257, 257, "", s_salt.ByteArrayToHex() };
+ yield return new object[] { hashAlgorithm.Name, 257, 257, "D8D8D8D8D8D8D8D8", "D8D8D8D8D8D8D8D8" };
+ yield return new object[] { hashAlgorithm.Name, 257, 257, "0000000000000000", "0000000000000000" };
+ yield return new object[] { hashAlgorithm.Name, 257, 257, largeInputHex, largeInputHex };
+ }
+
+ // Test around HMAC SHA1 and SHA256 block boundaries
+ for (int blockBoundary = 63; blockBoundary <= 65; blockBoundary++)
+ {
+ byte[] password = new byte[blockBoundary];
+ yield return new object[] { HashAlgorithmName.SHA1.Name, 257, 257, password.ByteArrayToHex(), "0000000000000000" };
+ yield return new object[] { HashAlgorithmName.SHA256.Name, 257, 257, password.ByteArrayToHex(), "0000000000000000" };
+ }
+
+ // Test around HMAC SHA384 and SHA512 block boundaries
+ for (int blockBoundary = 127; blockBoundary <= 129; blockBoundary++)
+ {
+ byte[] password = new byte[blockBoundary];
+ yield return new object[] { HashAlgorithmName.SHA384.Name, 257, 257, password.ByteArrayToHex(), "0000000000000000" };
+ yield return new object[] { HashAlgorithmName.SHA512.Name, 257, 257, password.ByteArrayToHex(), "0000000000000000" };
+ }
+ }
+
+ public static IEnumerable<object[]> Pbkdf2_PasswordString_Compare_Data()
+ {
+ string largePassword = new string('y', 1024);
+
+ foreach (HashAlgorithmName hashAlgorithm in SupportedHashAlgorithms)
+ {
+ // hashAlgorithm, length, iterations, password, saltHex
+ yield return new object[] { hashAlgorithm.Name, 1, 1, Password, s_salt.ByteArrayToHex() };
+ yield return new object[] { hashAlgorithm.Name, 1, 1, "", s_salt.ByteArrayToHex() };
+ yield return new object[] { hashAlgorithm.Name, 257, 257, "", s_salt.ByteArrayToHex() };
+ yield return new object[] { hashAlgorithm.Name, 257, 257, Password, "D8D8D8D8D8D8D8D8" };
+ yield return new object[] { hashAlgorithm.Name, 257, 257, Password, "0000000000000000" };
+
+ // case for password exceeding the stack buffer limit.
+ yield return new object[] { hashAlgorithm.Name, 257, 257, largePassword, "0000000000000000" };
+ }
+ }
+
+ public static IEnumerable<object[]> Pbkdf2_Rfc6070_Vectors()
+ {
+ // password (P), salt (S), iterations (c), expected (DK)
+ yield return new object[] { "password", "salt", 1, "0c60c80f961f0e71f3a9b524af6012062fe037a6" };
+ yield return new object[] { "password", "salt", 2, "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957" };
+ yield return new object[] { "passwordPASSWORDpassword", "saltSALTsaltSALTsaltSALTsaltSALTsalt", 4096, "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038" };
+ yield return new object[] { "pass\0word", "sa\0lt", 4096, "56fa6aa75548099dcc37d7f03425e0c3" };
+ }
+
+ private static HashAlgorithmName[] SupportedHashAlgorithms => new []
+ {
+ HashAlgorithmName.SHA1,
+ HashAlgorithmName.SHA256,
+ HashAlgorithmName.SHA384,
+ HashAlgorithmName.SHA512
+ };
+ }
+}
<Compile Include="ReusabilityTests.cs" />
<Compile Include="Rfc2202HmacTests.cs" />
<Compile Include="Rfc2898Tests.cs" />
+ <Compile Include="Rfc2898OneShotTests.cs" />
<Compile Include="Rfc4231HmacTests.cs" />
<Compile Include="Sha1Tests.cs" />
<Compile Include="Sha256Tests.cs" />