Add static PBKDF2 one-shot methods
authorKevin Jones <kevin@vcsjones.com>
Mon, 22 Feb 2021 20:53:59 +0000 (15:53 -0500)
committerGitHub <noreply@github.com>
Mon, 22 Feb 2021 20:53:59 +0000 (12:53 -0800)
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.

28 files changed:
src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Pbkdf2.cs [new file with mode: 0644]
src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.cs
src/libraries/Common/src/Interop/Windows/BCrypt/Cng.cs
src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptAlgPseudoHandle.cs [new file with mode: 0644]
src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptDeriveKeyPBKDF2.cs [new file with mode: 0644]
src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptGenerateSymmetricKey.cs [new file with mode: 0644]
src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptHash.cs
src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptKeyDerivation.cs [new file with mode: 0644]
src/libraries/Common/src/Interop/Windows/BCrypt/Interop.Blobs.cs
src/libraries/Common/src/System/Security/Cryptography/ECCng.ImportExport.cs
src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/CMakeLists.txt
src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/entrypoints.c
src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_keyderivation.c [new file with mode: 0644]
src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_keyderivation.h [new file with mode: 0644]
src/libraries/Native/Unix/System.Security.Cryptography.Native/entrypoints.c
src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h
src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp.c
src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp.h
src/libraries/System.Security.Cryptography.Algorithms/ref/System.Security.Cryptography.Algorithms.cs
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/HashProviderDispenser.Unix.cs
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/Pbkdf2Implementation.OSX.cs [new file with mode: 0644]
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/Pbkdf2Implementation.Unix.cs [new file with mode: 0644]
src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/Pbkdf2Implementation.Windows.cs [new file with mode: 0644]
src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj
src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/Rfc2898DeriveBytes.OneShot.cs [new file with mode: 0644]
src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/Rfc2898DeriveBytes.cs
src/libraries/System.Security.Cryptography.Algorithms/tests/Rfc2898OneShotTests.cs [new file with mode: 0644]
src/libraries/System.Security.Cryptography.Algorithms/tests/System.Security.Cryptography.Algorithms.Tests.csproj

diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Pbkdf2.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Pbkdf2.cs
new file mode 100644 (file)
index 0000000..4eb5bfd
--- /dev/null
@@ -0,0 +1,62 @@
+// 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);
+    }
+}
index ab18458..45e3cce 100644 (file)
@@ -51,10 +51,43 @@ internal static partial class Interop
         [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();
     }
 }
index 3163dce..7642b9f 100644 (file)
@@ -35,6 +35,7 @@ namespace Internal.NativeCrypto
             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
diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptAlgPseudoHandle.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptAlgPseudoHandle.cs
new file mode 100644 (file)
index 0000000..8ebae44
--- /dev/null
@@ -0,0 +1,23 @@
+// 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,
+        }
+    }
+}
diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptDeriveKeyPBKDF2.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptDeriveKeyPBKDF2.cs
new file mode 100644 (file)
index 0000000..d514f2b
--- /dev/null
@@ -0,0 +1,26 @@
+// 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);
+    }
+}
diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptGenerateSymmetricKey.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptGenerateSymmetricKey.cs
new file mode 100644 (file)
index 0000000..0f829c5
--- /dev/null
@@ -0,0 +1,32 @@
+// 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);
+    }
+}
index e018062..530e7b2 100644 (file)
@@ -10,16 +10,5 @@ internal partial class Interop
     {
         [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,
-        }
     }
 }
diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptKeyDerivation.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptKeyDerivation.cs
new file mode 100644 (file)
index 0000000..dfe2c7d
--- /dev/null
@@ -0,0 +1,23 @@
+// 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);
+    }
+}
index 8caf36e..374a80e 100644 (file)
@@ -227,10 +227,27 @@ internal partial class Interop
         }
 
         /// <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 partial class Interop
         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
         }
 
index 4064f60..5e39454 100644 (file)
@@ -474,7 +474,7 @@ namespace System.Security.Cryptography
                     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);
 
index e1e4c47..a222753 100644 (file)
@@ -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
index 3c00bb5..43f76ca 100644 (file)
@@ -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);
diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_keyderivation.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_keyderivation.c
new file mode 100644 (file)
index 0000000..6358fe8
--- /dev/null
@@ -0,0 +1,83 @@
+// 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
diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_keyderivation.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_keyderivation.h
new file mode 100644 (file)
index 0000000..ee9cfae
--- /dev/null
@@ -0,0 +1,40 @@
+// 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
index 9856a0b..bd379df 100644 (file)
@@ -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)
index 9e6fa85..5490d1a 100644 (file)
@@ -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
index 4e67c45..a119f81 100644 (file)
@@ -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);
+}
index 3a546c9..11c8a16 100644 (file)
@@ -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);
index 8e9d12d..e5a78f3 100644 (file)
@@ -587,6 +587,12 @@ namespace System.Security.Cryptography
         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)]
index a672233..21647ec 100644 (file)
@@ -29,7 +29,7 @@ namespace Internal.Cryptography
             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,
diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/Pbkdf2Implementation.OSX.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/Pbkdf2Implementation.OSX.cs
new file mode 100644 (file)
index 0000000..f588543
--- /dev/null
@@ -0,0 +1,46 @@
+// 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);
+        }
+    }
+}
diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/Pbkdf2Implementation.Unix.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/Pbkdf2Implementation.Unix.cs
new file mode 100644 (file)
index 0000000..a4a04b1
--- /dev/null
@@ -0,0 +1,32 @@
+// 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();
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/Pbkdf2Implementation.Windows.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/Pbkdf2Implementation.Windows.cs
new file mode 100644 (file)
index 0000000..93c0bd5
--- /dev/null
@@ -0,0 +1,281 @@
+// 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();
+            }
+        }
+    }
+}
index 4bc4493..4e6ab6f 100644 (file)
@@ -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" />
     <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" />
diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/Rfc2898DeriveBytes.OneShot.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/Rfc2898DeriveBytes.OneShot.cs
new file mode 100644 (file)
index 0000000..205bb91
--- /dev/null
@@ -0,0 +1,347 @@
+// 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));
+            }
+        }
+    }
+}
index bf83330..7c15015 100644 (file)
@@ -12,7 +12,7 @@ using Internal.Cryptography;
 namespace System.Security.Cryptography
 {
     [UnsupportedOSPlatform("browser")]
-    public class Rfc2898DeriveBytes : DeriveBytes
+    public partial class Rfc2898DeriveBytes : DeriveBytes
     {
         private const int MinimumSaltSize = 8;
 
diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/Rfc2898OneShotTests.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/Rfc2898OneShotTests.cs
new file mode 100644 (file)
index 0000000..12f2fd0
--- /dev/null
@@ -0,0 +1,355 @@
+// 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
+            };
+    }
+}
index 3c31832..f48d669 100644 (file)
@@ -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" />