未验证 提交 0c474714 编写于 作者: K Kevin Jones 提交者: GitHub

Add static PBKDF2 one-shot methods

This avoids the disposable object creation for Rfc2989DeriveBytes and the
state tracking.

Since the one-shots just call into platform routines (when available) it
should allow for higher iteration counts for the same clock time.
上级 d39232c0
// 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);
}
}
......@@ -51,10 +51,43 @@ internal static partial class Crypto
[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();
}
}
......@@ -35,6 +35,7 @@ internal static class AlgorithmName
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
......
// 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,
}
}
}
// 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);
}
}
// 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);
}
}
......@@ -10,16 +10,5 @@ internal partial class BCrypt
{
[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,
}
}
}
// 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);
}
}
......@@ -227,10 +227,27 @@ internal struct BCRYPT_ECCFULLKEY_BLOB
}
/// <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,
}
......@@ -241,7 +258,7 @@ internal enum NCryptBufferDescriptors : int
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
}
......
......@@ -474,7 +474,7 @@ private static ECCurve.ECCurveType ConvertToCurveTypeEnum(Interop.BCrypt.ECC_CUR
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);
......
......@@ -10,6 +10,7 @@ set(NATIVECRYPTO_SOURCES
pal_ecc.c
pal_hmac.c
pal_keyagree.c
pal_keyderivation.c
pal_keychain.c
pal_random.c
pal_rsa.c
......
......@@ -20,6 +20,7 @@
#include "pal_trust.h"
#include "pal_x509.h"
#include "pal_x509chain.h"
#include "pal_keyderivation.h"
static const Entry s_cryptoAppleNative[] =
{
......@@ -103,6 +104,7 @@ 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);
......
// 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
// 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
......@@ -209,6 +209,7 @@ static const Entry s_cryptoNative[] =
DllImportEntry(CryptoNative_ObjTxt2Obj)
DllImportEntry(CryptoNative_OcspRequestDestroy)
DllImportEntry(CryptoNative_OcspResponseDestroy)
DllImportEntry(CryptoNative_Pbkdf2)
DllImportEntry(CryptoNative_PemReadBioPkcs7)
DllImportEntry(CryptoNative_PemReadBioX509Crl)
DllImportEntry(CryptoNative_PemReadX509FromBio)
......
......@@ -437,6 +437,7 @@ void SSL_get0_alpn_selected(const SSL* ssl, const unsigned char** protocol, unsi
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) \
......@@ -847,6 +848,7 @@ FOR_ALL_OPENSSL_FUNCTIONS
#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
......
......@@ -151,3 +151,44 @@ int32_t CryptoNative_GetMaxMdSize()
{
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);
}
......@@ -117,6 +117,29 @@ PALEXPORT const EVP_MD* CryptoNative_EvpSha512(void);
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);
......@@ -587,6 +587,12 @@ public partial class Rfc2898DeriveBytes : System.Security.Cryptography.DeriveByt
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)]
......
......@@ -29,7 +29,7 @@ public static HashProvider CreateMacProvider(string hashAlgorithmId, ReadOnlySpa
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,
......
// 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);
}
}
}
// 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();
}
}
}
}
// 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();
}
}
}
}
......@@ -77,6 +77,7 @@
<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" />
......@@ -292,6 +293,7 @@
<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"
......@@ -328,12 +330,22 @@
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"
......@@ -376,6 +388,8 @@
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"
......@@ -493,6 +507,7 @@
<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" />
......@@ -539,6 +554,8 @@
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"
......@@ -560,6 +577,7 @@
<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" />
......
// 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));
}
}
}
}
......@@ -12,7 +12,7 @@
namespace System.Security.Cryptography
{
[UnsupportedOSPlatform("browser")]
public class Rfc2898DeriveBytes : DeriveBytes
public partial class Rfc2898DeriveBytes : DeriveBytes
{
private const int MinimumSaltSize = 8;
......
// 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
};
}
}
......@@ -85,6 +85,7 @@
<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" />
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册