File: src\libraries\Common\src\System\Security\Cryptography\PasswordBasedEncryption.cs
Web Access
Project: src\src\libraries\System.Security.Cryptography.Pkcs\src\System.Security.Cryptography.Pkcs.csproj (System.Security.Cryptography.Pkcs)
// 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.Diagnostics.CodeAnalysis;
using System.Formats.Asn1;
using System.Runtime.InteropServices;
using System.Security.Cryptography.Asn1;
using System.Security.Cryptography.Pkcs;
using Internal.Cryptography;
 
namespace System.Security.Cryptography
{
    internal static class PasswordBasedEncryption
    {
        internal const int IterationLimit = 600000;
 
        private static CryptographicException AlgorithmKdfRequiresChars(string algId)
        {
            return new CryptographicException(SR.Cryptography_AlgKdfRequiresChars, algId);
        }
 
        internal static void ValidatePbeParameters(
            PbeParameters pbeParameters,
            ReadOnlySpan<char> password,
            ReadOnlySpan<byte> passwordBytes)
        {
            // Leave the ArgumentNullException in the public entrypoints.
            Debug.Assert(pbeParameters != null);
 
            // Constructor promise.
            Debug.Assert(pbeParameters.IterationCount > 0);
 
            PbeEncryptionAlgorithm encryptionAlgorithm = pbeParameters.EncryptionAlgorithm;
 
            switch (encryptionAlgorithm)
            {
                case PbeEncryptionAlgorithm.Aes128Cbc:
                case PbeEncryptionAlgorithm.Aes192Cbc:
                case PbeEncryptionAlgorithm.Aes256Cbc:
                    return;
                case PbeEncryptionAlgorithm.TripleDes3KeyPkcs12:
                {
                    if (pbeParameters.HashAlgorithm != HashAlgorithmName.SHA1)
                    {
                        throw new CryptographicException(
                            SR.Cryptography_UnknownHashAlgorithm,
                            pbeParameters.HashAlgorithm.Name);
                    }
 
                    if (passwordBytes.Length > 0 && password.Length == 0)
                    {
                        throw AlgorithmKdfRequiresChars(
                            encryptionAlgorithm.ToString());
                    }
 
                    return;
                }
            }
 
            throw new CryptographicException(
                SR.Cryptography_UnknownAlgorithmIdentifier,
                encryptionAlgorithm.ToString());
        }
 
        [SuppressMessage("Microsoft.Security", "CA5350", Justification = "3DES used when specified by the input data")]
        [SuppressMessage("Microsoft.Security", "CA5351", Justification = "DES used when specified by the input data")]
        internal static unsafe int Decrypt(
            in AlgorithmIdentifierAsn algorithmIdentifier,
            ReadOnlySpan<char> password,
            ReadOnlySpan<byte> passwordBytes,
            ReadOnlySpan<byte> encryptedData,
            Span<byte> destination)
        {
            Debug.Assert(destination.Length >= encryptedData.Length);
 
            if (!Helpers.HasSymmetricEncryption)
            {
                throw new CryptographicException(
                    SR.Format(
                        SR.Cryptography_UnknownAlgorithmIdentifier,
                        algorithmIdentifier.Algorithm));
            }
 
            // Don't check that algorithmIdentifier.Parameters is set here.
            // Maybe some future PBES3 will have one with a default.
 
            HashAlgorithmName digestAlgorithmName;
            SymmetricAlgorithm? cipher = null;
 
            bool pkcs12 = false;
 
            switch (algorithmIdentifier.Algorithm)
            {
                case Oids.PbeWithMD5AndDESCBC:
                    digestAlgorithmName = HashAlgorithmName.MD5;
                    cipher = DES.Create();
                    break;
                case Oids.PbeWithMD5AndRC2CBC:
                    digestAlgorithmName = HashAlgorithmName.MD5;
                    cipher = CreateRC2();
                    break;
                case Oids.PbeWithSha1AndDESCBC:
                    digestAlgorithmName = HashAlgorithmName.SHA1;
                    cipher = DES.Create();
                    break;
                case Oids.PbeWithSha1AndRC2CBC:
                    digestAlgorithmName = HashAlgorithmName.SHA1;
                    cipher = CreateRC2();
                    break;
                case Oids.Pkcs12PbeWithShaAnd3Key3Des:
                    digestAlgorithmName = HashAlgorithmName.SHA1;
                    cipher = TripleDES.Create();
                    pkcs12 = true;
                    break;
                case Oids.Pkcs12PbeWithShaAnd2Key3Des:
                    digestAlgorithmName = HashAlgorithmName.SHA1;
                    cipher = TripleDES.Create();
                    cipher.KeySize = 128;
                    pkcs12 = true;
                    break;
                case Oids.Pkcs12PbeWithShaAnd128BitRC2:
                    digestAlgorithmName = HashAlgorithmName.SHA1;
                    cipher = CreateRC2();
                    cipher.KeySize = 128;
                    pkcs12 = true;
                    break;
                case Oids.Pkcs12PbeWithShaAnd40BitRC2:
                    digestAlgorithmName = HashAlgorithmName.SHA1;
                    cipher = CreateRC2();
                    cipher.KeySize = 40;
                    pkcs12 = true;
                    break;
                case Oids.PasswordBasedEncryptionScheme2:
                    return Pbes2Decrypt(
                        algorithmIdentifier.Parameters,
                        password,
                        passwordBytes,
                        encryptedData,
                        destination);
                default:
                    throw new CryptographicException(
                        SR.Format(
                            SR.Cryptography_UnknownAlgorithmIdentifier,
                            algorithmIdentifier.Algorithm));
            }
 
            Debug.Assert(digestAlgorithmName.Name != null);
            Debug.Assert(cipher != null);
 
            using (cipher)
            {
                if (pkcs12)
                {
                    if (password.IsEmpty && passwordBytes.Length > 0)
                    {
                        throw AlgorithmKdfRequiresChars(algorithmIdentifier.Algorithm);
                    }
 
                    return Pkcs12PbeDecrypt(
                        algorithmIdentifier,
                        password,
                        digestAlgorithmName,
                        cipher,
                        encryptedData,
                        destination);
                }
 
                using (IncrementalHash hasher = IncrementalHash.CreateHash(digestAlgorithmName))
                {
                    Span<byte> buf = stackalloc byte[128];
                    scoped ReadOnlySpan<byte> effectivePasswordBytes = default;
                    byte[]? rented = null;
                    System.Text.Encoding? encoding = null;
 
                    if (passwordBytes.Length > 0 || password.Length == 0)
                    {
                        effectivePasswordBytes = passwordBytes;
                    }
                    else
                    {
                        encoding = System.Text.Encoding.UTF8;
                        int byteCount = encoding.GetByteCount(password);
 
                        if (byteCount > buf.Length)
                        {
                            rented = CryptoPool.Rent(byteCount);
                            buf = rented.AsSpan(0, byteCount);
                        }
                        else
                        {
                            buf = buf.Slice(0, byteCount);
                        }
                    }
 
                    fixed (byte* maybeRentedPtr = &MemoryMarshal.GetReference(buf))
                    {
                        if (encoding != null)
                        {
                            int written = encoding.GetBytes(password, buf);
                            Debug.Assert(written == buf.Length);
                            buf = buf.Slice(0, written);
                            effectivePasswordBytes = buf;
                        }
 
                        try
                        {
                            return Pbes1Decrypt(
                                algorithmIdentifier.Parameters,
                                effectivePasswordBytes,
                                hasher,
                                cipher,
                                encryptedData,
                                destination);
                        }
                        finally
                        {
                            CryptographicOperations.ZeroMemory(buf);
 
                            if (rented != null)
                            {
                                CryptoPool.Return(rented, clearSize: 0);
                            }
                        }
                    }
                }
            }
        }
 
        [SuppressMessage("Microsoft.Security", "CA5350", Justification = "3DES used when specified by the input data")]
        internal static void InitiateEncryption(
            PbeParameters pbeParameters,
            out SymmetricAlgorithm cipher,
            out string hmacOid,
            out string encryptionAlgorithmOid,
            out bool isPkcs12)
        {
            Debug.Assert(pbeParameters != null);
 
            if (!Helpers.HasSymmetricEncryption)
            {
                throw new CryptographicException(
                    SR.Format(
                        SR.Cryptography_UnknownAlgorithmIdentifier,
                        pbeParameters.EncryptionAlgorithm));
            }
 
            isPkcs12 = false;
 
            switch (pbeParameters.EncryptionAlgorithm)
            {
                case PbeEncryptionAlgorithm.Aes128Cbc:
                    cipher = Aes.Create();
                    cipher.KeySize = 128;
                    encryptionAlgorithmOid = Oids.Aes128Cbc;
                    break;
                case PbeEncryptionAlgorithm.Aes192Cbc:
                    cipher = Aes.Create();
                    cipher.KeySize = 192;
                    encryptionAlgorithmOid = Oids.Aes192Cbc;
                    break;
                case PbeEncryptionAlgorithm.Aes256Cbc:
                    cipher = Aes.Create();
                    cipher.KeySize = 256;
                    encryptionAlgorithmOid = Oids.Aes256Cbc;
                    break;
                case PbeEncryptionAlgorithm.TripleDes3KeyPkcs12:
                    cipher = TripleDES.Create();
                    cipher.KeySize = 192;
                    encryptionAlgorithmOid = Oids.Pkcs12PbeWithShaAnd3Key3Des;
                    isPkcs12 = true;
                    break;
                default:
                    throw new CryptographicException(
                        SR.Format(
                            SR.Cryptography_UnknownAlgorithmIdentifier,
                            pbeParameters.EncryptionAlgorithm));
            }
 
            HashAlgorithmName prf = pbeParameters.HashAlgorithm;
 
            if (prf == HashAlgorithmName.SHA256)
            {
                hmacOid = Oids.HmacWithSha256;
            }
            else if (prf == HashAlgorithmName.SHA384)
            {
                hmacOid = Oids.HmacWithSha384;
            }
            else if (prf == HashAlgorithmName.SHA512)
            {
                hmacOid = Oids.HmacWithSha512;
            }
            else if (prf == HashAlgorithmName.SHA1)
            {
                hmacOid = Oids.HmacWithSha1;
            }
            else
            {
                cipher.Dispose();
                throw new CryptographicException(SR.Cryptography_UnknownHashAlgorithm, prf.Name);
            }
 
            // PKCS12-PBE should have been verified to be using SHA-1 already.
            Debug.Assert(hmacOid == Oids.HmacWithSha1 || !isPkcs12);
        }
 
        [SuppressMessage("Microsoft.Security", "CA5379", Justification = "SHA1 used if specified by argument")]
        internal static unsafe int Encrypt(
            ReadOnlySpan<char> password,
            ReadOnlySpan<byte> passwordBytes,
            SymmetricAlgorithm cipher,
            bool isPkcs12,
            AsnWriter source,
            PbeParameters pbeParameters,
            ReadOnlySpan<byte> salt,
            byte[] destination,
            Span<byte> ivDest)
        {
            byte[]? pwdTmpBytes = null;
            byte[] derivedKey;
            byte[] iv = cipher.IV;
 
            int sourceLength = source.GetEncodedLength();
            byte[] sourceRent = CryptoPool.Rent(sourceLength);
            int keySizeBytes = cipher.KeySize / 8;
            int iterationCount = pbeParameters.IterationCount;
            HashAlgorithmName prf = pbeParameters.HashAlgorithm;
            System.Text.Encoding encoding = System.Text.Encoding.UTF8;
 
            if (!isPkcs12)
            {
                if (passwordBytes.Length == 0 && password.Length > 0)
                {
                    pwdTmpBytes = new byte[encoding.GetByteCount(password)];
                }
                else if (passwordBytes.Length == 0)
                {
                    pwdTmpBytes = Array.Empty<byte>();
                }
                else
                {
                    pwdTmpBytes = new byte[passwordBytes.Length];
                }
            }
 
            fixed (byte* pkcs8RentPin = sourceRent)
            fixed (byte* pwdTmpBytesPtr = pwdTmpBytes)
            {
                if (isPkcs12)
                {
                    // Verified by ValidatePbeParameters, which should be called at entrypoints.
                    Debug.Assert(password.Length > 0 || passwordBytes.IsEmpty);
                    Debug.Assert(pbeParameters.HashAlgorithm == HashAlgorithmName.SHA1);
 
                    derivedKey = new byte[keySizeBytes];
 
                    Pkcs12Kdf.DeriveCipherKey(
                        password,
                        prf,
                        iterationCount,
                        salt,
                        derivedKey);
 
                    Pkcs12Kdf.DeriveIV(
                        password,
                        prf,
                        iterationCount,
                        salt,
                        iv);
 
                    ivDest.Clear();
                }
                else
                {
                    if (passwordBytes.Length > 0)
                    {
                        Debug.Assert(pwdTmpBytes!.Length == passwordBytes.Length);
                        passwordBytes.CopyTo(pwdTmpBytes);
                    }
                    else if (password.Length > 0)
                    {
                        int length = encoding.GetBytes(password, pwdTmpBytes);
 
                        if (length != pwdTmpBytes!.Length)
                        {
                            Debug.Fail($"UTF-8 encoding size changed between GetByteCount and GetBytes");
                            throw new CryptographicException();
                        }
                    }
                    else
                    {
                        Debug.Assert(pwdTmpBytes!.Length == 0);
                    }
 
                    derivedKey = DeriveKey(pwdTmpBytes, salt, iterationCount, prf, keySizeBytes);
                    iv.CopyTo(ivDest);
                }
 
                fixed (byte* keyPtr = derivedKey)
                {
                    CryptographicOperations.ZeroMemory(pwdTmpBytes);
 
                    using (ICryptoTransform encryptor = cipher.CreateEncryptor(derivedKey, iv))
                    {
                        Debug.Assert(encryptor.CanTransformMultipleBlocks);
 
                        int blockSizeBytes = (cipher.BlockSize / 8);
                        int remaining = sourceLength % blockSizeBytes;
                        int fullBlocksLength = sourceLength - remaining;
 
                        try
                        {
                            if (!source.TryEncode(sourceRent, out _))
                            {
                                Debug.Fail("TryEncode failed with a pre-allocated buffer");
                                throw new CryptographicException();
                            }
 
                            int written = 0;
 
                            if (fullBlocksLength != 0)
                            {
                                written = encryptor.TransformBlock(
                                    sourceRent,
                                    0,
                                    fullBlocksLength,
                                    destination,
                                    0);
                            }
 
                            byte[] lastBlock = encryptor.TransformFinalBlock(
                                sourceRent,
                                written,
                                remaining);
 
                            lastBlock.AsSpan().CopyTo(destination.AsSpan(written));
                            return written + lastBlock.Length;
                        }
                        finally
                        {
                            CryptoPool.Return(sourceRent, sourceLength);
                        }
                    }
                }
            }
        }
 
        private static unsafe int Pbes2Decrypt(
            ReadOnlyMemory<byte>? algorithmParameters,
            ReadOnlySpan<char> password,
            ReadOnlySpan<byte> passwordBytes,
            ReadOnlySpan<byte> encryptedData,
            Span<byte> destination)
        {
            Span<byte> buf = stackalloc byte[128];
            scoped ReadOnlySpan<byte> effectivePasswordBytes = default;
            byte[]? rented = null;
            System.Text.Encoding? encoding = null;
 
            if (passwordBytes.Length > 0 || password.Length == 0)
            {
                effectivePasswordBytes = passwordBytes;
            }
            else
            {
                encoding = System.Text.Encoding.UTF8;
                int byteCount = encoding.GetByteCount(password);
 
                if (byteCount > buf.Length)
                {
                    rented = CryptoPool.Rent(byteCount);
                    buf = rented.AsSpan(0, byteCount);
                }
                else
                {
                    buf = buf.Slice(0, byteCount);
                }
            }
 
            fixed (byte* maybeRentedPtr = &MemoryMarshal.GetReference(buf))
            {
                if (encoding != null)
                {
                    int written = encoding.GetBytes(password, buf);
                    Debug.Assert(written == buf.Length);
                    buf = buf.Slice(0, written);
                    effectivePasswordBytes = buf;
                }
 
                try
                {
                    return Pbes2Decrypt(
                        algorithmParameters,
                        effectivePasswordBytes,
                        encryptedData,
                        destination);
                }
                finally
                {
                    if (rented != null)
                    {
                        CryptoPool.Return(rented, buf.Length);
                    }
                }
            }
        }
 
        private static unsafe int Pbes2Decrypt(
            ReadOnlyMemory<byte>? algorithmParameters,
            ReadOnlySpan<byte> password,
            ReadOnlySpan<byte> encryptedData,
            Span<byte> destination)
        {
            if (!algorithmParameters.HasValue)
            {
                throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
            }
 
            PBES2Params pbes2Params = PBES2Params.Decode(algorithmParameters.Value, AsnEncodingRules.BER);
 
            if (pbes2Params.KeyDerivationFunc.Algorithm != Oids.Pbkdf2)
            {
                throw new CryptographicException(
                    SR.Format(
                        SR.Cryptography_UnknownAlgorithmIdentifier,
                        pbes2Params.EncryptionScheme.Algorithm));
            }
 
            Rfc2898DeriveBytes pbkdf2 =
                OpenPbkdf2(password, pbes2Params.KeyDerivationFunc.Parameters, out int? requestedKeyLength);
 
            using (pbkdf2)
            {
                // The biggest block size (for IV) we support is AES (128-bit / 16 byte)
                Span<byte> iv = stackalloc byte[16];
 
                SymmetricAlgorithm cipher = OpenCipher(
                    pbes2Params.EncryptionScheme,
                    requestedKeyLength,
                    ref iv);
 
                using (cipher)
                {
                    byte[] key = pbkdf2.GetBytes(cipher.KeySize / 8);
 
                    fixed (byte* keyPtr = key)
                    {
                        try
                        {
                            return Decrypt(cipher, key, iv, encryptedData, destination);
                        }
                        finally
                        {
                            CryptographicOperations.ZeroMemory(key);
                        }
                    }
                }
            }
        }
 
        [SuppressMessage("Microsoft.Security", "CA5350", Justification = "3DES used when specified by the input data")]
        [SuppressMessage("Microsoft.Security", "CA5351", Justification = "DES used when specified by the input data")]
        private static SymmetricAlgorithm OpenCipher(
            AlgorithmIdentifierAsn encryptionScheme,
            int? requestedKeyLength,
            ref Span<byte> iv)
        {
            string? algId = encryptionScheme.Algorithm;
 
            if (!Helpers.HasSymmetricEncryption)
            {
                throw new CryptographicException(
                    SR.Format(SR.Cryptography_AlgorithmNotSupported, algId));
            }
 
            if (algId == Oids.Aes128Cbc ||
                algId == Oids.Aes192Cbc ||
                algId == Oids.Aes256Cbc)
            {
                // https://tools.ietf.org/html/rfc8018#appendix-B.2.5
                int correctKeySize;
 
                switch (algId)
                {
                    case Oids.Aes128Cbc:
                        correctKeySize = 16;
                        break;
                    case Oids.Aes192Cbc:
                        correctKeySize = 24;
                        break;
                    case Oids.Aes256Cbc:
                        correctKeySize = 32;
                        break;
                    default:
                        Debug.Fail("Key-sized OID included in the if, but not the switch");
                        throw new CryptographicException();
                }
 
                if (requestedKeyLength != null && requestedKeyLength != correctKeySize)
                {
                    throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
                }
 
                // The parameters field ... shall have type OCTET STRING (SIZE(16))
                // specifying the initialization vector ...
 
                ReadIvParameter(encryptionScheme.Parameters, 16, ref iv);
 
                Aes aes = Aes.Create();
                aes.KeySize = correctKeySize * 8;
                return aes;
            }
 
            if (algId == Oids.TripleDesCbc)
            {
                // https://tools.ietf.org/html/rfc8018#appendix-B.2.2
 
                // ... has a 24-octet encryption key ...
                if (requestedKeyLength != null && requestedKeyLength != 24)
                {
                    throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
                }
 
                // The parameters field associated with this OID ... shall have type
                // OCTET STRING (SIZE(8)) specifying the initialization vector ...
                ReadIvParameter(encryptionScheme.Parameters, 8, ref iv);
                return TripleDES.Create();
            }
 
            if (algId == Oids.Rc2Cbc)
            {
                // https://tools.ietf.org/html/rfc8018#appendix-B.2.3
 
                if (encryptionScheme.Parameters == null)
                {
                    throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
                }
 
                // RC2 has a variable key size. RFC 8018 does not define a default,
                // so of PBKDF2 didn't provide it, fail.
                if (requestedKeyLength == null)
                {
                    throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
                }
 
                Rc2CbcParameters rc2Parameters = Rc2CbcParameters.Decode(
                    encryptionScheme.Parameters.Value,
                    AsnEncodingRules.BER);
 
                // iv is the eight-octet initialization vector
                if (rc2Parameters.Iv.Length != 8)
                {
                    throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
                }
 
                RC2 rc2 = CreateRC2();
                rc2.KeySize = requestedKeyLength.Value * 8;
                rc2.EffectiveKeySize = rc2Parameters.GetEffectiveKeyBits();
 
                rc2Parameters.Iv.Span.CopyTo(iv);
                iv = iv.Slice(0, rc2Parameters.Iv.Length);
                return rc2;
            }
 
            if (algId == Oids.DesCbc)
            {
                // https://tools.ietf.org/html/rfc8018#appendix-B.2.1
 
                // ... has an eight-octet encryption key ...
                if (requestedKeyLength != null && requestedKeyLength != 8)
                {
                    throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
                }
 
                // The parameters field associated with this OID ... shall have type
                // OCTET STRING (SIZE(8)) specifying the initialization vector ...
                ReadIvParameter(encryptionScheme.Parameters, 8, ref iv);
                return DES.Create();
            }
 
            throw new CryptographicException(SR.Cryptography_UnknownAlgorithmIdentifier, algId);
        }
 
        private static void ReadIvParameter(
            ReadOnlyMemory<byte>? encryptionSchemeParameters,
            int length,
            ref Span<byte> iv)
        {
            if (encryptionSchemeParameters == null)
            {
                throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
            }
 
            try
            {
                ReadOnlySpan<byte> source = encryptionSchemeParameters.Value.Span;
 
                bool gotIv = AsnDecoder.TryReadOctetString(
                    source,
                    iv,
                    AsnEncodingRules.BER,
                    out int consumed,
                    out int bytesWritten);
 
                if (!gotIv || bytesWritten != length || consumed != source.Length)
                {
                    throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
                }
 
                iv = iv.Slice(0, bytesWritten);
            }
            catch (AsnContentException e)
            {
                throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e);
            }
        }
 
        [SuppressMessage("Microsoft.Security", "CA5379", Justification = "SHA1 used if specified by argument")]
        private static unsafe Rfc2898DeriveBytes OpenPbkdf2(
            ReadOnlySpan<byte> password,
            ReadOnlyMemory<byte>? parameters,
            out int? requestedKeyLength)
        {
            if (!parameters.HasValue)
            {
                throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
            }
 
            Pbkdf2Params pbkdf2Params = Pbkdf2Params.Decode(parameters.Value, AsnEncodingRules.BER);
 
            // No OtherSource is defined in RFC 2898 or RFC 8018, so whatever
            // algorithm was requested isn't one we know.
            if (pbkdf2Params.Salt.OtherSource != null)
            {
                throw new CryptographicException(
                    SR.Format(
                        SR.Cryptography_UnknownAlgorithmIdentifier,
                        pbkdf2Params.Salt.OtherSource.Value.Algorithm));
            }
 
            if (pbkdf2Params.Salt.Specified == null)
            {
                Debug.Fail($"No Specified Salt value is present, indicating a new choice was unhandled");
                throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
            }
 
            HashAlgorithmName prf = pbkdf2Params.Prf.Algorithm switch
            {
                Oids.HmacWithSha1 => HashAlgorithmName.SHA1,
                Oids.HmacWithSha256 => HashAlgorithmName.SHA256,
                Oids.HmacWithSha384 => HashAlgorithmName.SHA384,
                Oids.HmacWithSha512 => HashAlgorithmName.SHA512,
                _ => throw new CryptographicException(
                        SR.Format(
                            SR.Cryptography_UnknownAlgorithmIdentifier,
                            pbkdf2Params.Prf.Algorithm)),
            };
 
            // All of the PRFs that we know about have NULL parameters, so check that now that we know
            // it's not just that we don't know the algorithm.
 
            if (!pbkdf2Params.Prf.HasNullEquivalentParameters())
            {
                throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
            }
 
            int iterationCount = NormalizeIterationCount(pbkdf2Params.IterationCount);
            ReadOnlyMemory<byte> saltMemory = pbkdf2Params.Salt.Specified.Value;
 
            byte[] tmpPassword = new byte[password.Length];
            byte[] tmpSalt = new byte[saltMemory.Length];
 
            fixed (byte* tmpPasswordPtr = tmpPassword)
            fixed (byte* tmpSaltPtr = tmpSalt)
            {
                password.CopyTo(tmpPassword);
                saltMemory.CopyTo(tmpSalt);
 
                try
                {
                    requestedKeyLength = pbkdf2Params.KeyLength;
 
                    return OpenPbkdf2(
                        tmpPassword,
                        tmpSalt,
                        iterationCount,
                        prf);
                }
                catch (ArgumentException e)
                {
                    // Salt too small is the most likely candidate.
                    throw new CryptographicException(SR.Argument_InvalidValue, e);
                }
                finally
                {
                    CryptographicOperations.ZeroMemory(tmpPassword);
                    CryptographicOperations.ZeroMemory(tmpSalt);
                }
            }
        }
 
        private static int Pbes1Decrypt(
            ReadOnlyMemory<byte>? algorithmParameters,
            ReadOnlySpan<byte> password,
            IncrementalHash hasher,
            SymmetricAlgorithm cipher,
            ReadOnlySpan<byte> encryptedData,
            Span<byte> destination)
        {
            // https://tools.ietf.org/html/rfc2898#section-6.1.2
 
            // 1. Obtain the eight-octet salt S and iteration count c.
            if (!algorithmParameters.HasValue)
            {
                throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
            }
 
            PBEParameter pbeParameters = PBEParameter.Decode(algorithmParameters.Value, AsnEncodingRules.BER);
 
            if (pbeParameters.Salt.Length != 8)
            {
                throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
            }
 
            if (pbeParameters.IterationCount < 1)
            {
                throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
            }
 
            int iterationCount = NormalizeIterationCount(pbeParameters.IterationCount);
 
            // 2. Apply PBKDF1<hash>(P, S, c, 16) to produce a derived key DK of length 16 octets
            Span<byte> dk = stackalloc byte[16];
 
            try
            {
                Pbkdf1(hasher, password, pbeParameters.Salt.Span, iterationCount, dk);
 
                // 3. Separate the derived key DK into an encryption key K consisting of the
                // first eight octets of DK and an initialization vector IV consisting of the
                // next 8.
                Span<byte> k = dk.Slice(0, 8);
                Span<byte> iv = dk.Slice(8, 8);
 
                // 4 & 5 together are "use CBC with what eventually became called PKCS7 padding"
                return Decrypt(cipher, k, iv, encryptedData, destination);
            }
            finally
            {
                CryptographicOperations.ZeroMemory(dk);
            }
        }
 
        private static unsafe int Pkcs12PbeDecrypt(
            AlgorithmIdentifierAsn algorithmIdentifier,
            ReadOnlySpan<char> password,
            HashAlgorithmName hashAlgorithm,
            SymmetricAlgorithm cipher,
            ReadOnlySpan<byte> encryptedData,
            Span<byte> destination)
        {
            // https://tools.ietf.org/html/rfc7292#appendix-C
 
            if (!algorithmIdentifier.Parameters.HasValue)
            {
                throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
            }
 
            // 3DES, "two-key" 3DES, RC2-128 and RC2-40 are the only ciphers that should be here.
            // That means 64-bit block sizes and 192-bit keys (3DES-3).  So stack allocated key/IV are safe.
            if (cipher.KeySize > 256 || cipher.BlockSize > 256)
            {
                Debug.Fail(
                    $"Unexpected cipher characteristics by {cipher.GetType().FullName}, KeySize={cipher.KeySize}, BlockSize={cipher.BlockSize}");
 
                throw new CryptographicException();
            }
 
            PBEParameter pbeParameters = PBEParameter.Decode(
                algorithmIdentifier.Parameters.Value,
                AsnEncodingRules.BER);
 
            int iterationCount = NormalizeIterationCount(pbeParameters.IterationCount, IterationLimit);
            Span<byte> iv = stackalloc byte[cipher.BlockSize / 8];
            Span<byte> key = stackalloc byte[cipher.KeySize / 8];
            ReadOnlySpan<byte> saltSpan = pbeParameters.Salt.Span;
 
            try
            {
                Pkcs12Kdf.DeriveIV(
                    password,
                    hashAlgorithm,
                    iterationCount,
                    saltSpan,
                    iv);
 
                Pkcs12Kdf.DeriveCipherKey(
                    password,
                    hashAlgorithm,
                    iterationCount,
                    saltSpan,
                    key);
 
                return Decrypt(cipher, key, iv, encryptedData, destination);
            }
            finally
            {
                CryptographicOperations.ZeroMemory(key);
                CryptographicOperations.ZeroMemory(iv);
            }
        }
 
        private static unsafe int Decrypt(
            SymmetricAlgorithm cipher,
            ReadOnlySpan<byte> key,
            ReadOnlySpan<byte> iv,
            ReadOnlySpan<byte> encryptedData,
            Span<byte> destination)
        {
            // When we define a Span-based decryption API this should be changed to use it.
            byte[] tmpKey = new byte[key.Length];
            byte[] tmpIv = new byte[iv.Length];
            byte[] rentedEncryptedData = CryptoPool.Rent(encryptedData.Length);
            byte[] rentedDestination = CryptoPool.Rent(destination.Length);
 
            // Keep all the arrays pinned so they can be correctly cleared
            fixed (byte* tmpKeyPtr = tmpKey)
            fixed (byte* tmpIvPtr = tmpIv)
            fixed (byte* rentedEncryptedDataPtr = rentedEncryptedData)
            fixed (byte* rentedDestinationPtr = rentedDestination)
            {
                try
                {
                    key.CopyTo(tmpKey);
                    iv.CopyTo(tmpIv);
 
                    using (ICryptoTransform decryptor = cipher.CreateDecryptor(tmpKey, tmpIv))
                    {
                        Debug.Assert(decryptor.CanTransformMultipleBlocks);
 
                        encryptedData.CopyTo(rentedEncryptedData);
 
                        int writeOffset = decryptor.TransformBlock(
                            rentedEncryptedData,
                            0,
                            encryptedData.Length,
                            rentedDestination,
                            0);
 
                        rentedDestination.AsSpan(0, writeOffset).CopyTo(destination);
 
                        byte[] tmpEnd = decryptor.TransformFinalBlock(Array.Empty<byte>(), 0, 0);
 
                        fixed (byte* tmpEndPtr = tmpEnd)
                        {
                            Span<byte> tmpEndSpan = tmpEnd.AsSpan();
                            tmpEndSpan.CopyTo(destination.Slice(writeOffset));
                            CryptographicOperations.ZeroMemory(tmpEndSpan);
                        }
 
                        return writeOffset + tmpEnd.Length;
                    }
                }
                finally
                {
                    CryptographicOperations.ZeroMemory(tmpKey);
                    CryptographicOperations.ZeroMemory(tmpIv);
 
                    CryptoPool.Return(rentedEncryptedData, encryptedData.Length);
                    CryptoPool.Return(rentedDestination, destination.Length);
                }
            }
        }
 
        private static void Pbkdf1(
            IncrementalHash hasher,
            ReadOnlySpan<byte> password,
            ReadOnlySpan<byte> salt,
            int iterationCount,
            Span<byte> dk)
        {
            // The only two hashes that will call into this implementation are
            // MD5 (16 bytes) and SHA-1 (20 bytes).
            Span<byte> t = stackalloc byte[20];
 
            // https://tools.ietf.org/html/rfc2898#section-5.1
 
            // T_1 = Hash(P || S)
            hasher.AppendData(password);
            hasher.AppendData(salt);
 
            if (!hasher.TryGetHashAndReset(t, out int tLength))
            {
                Debug.Fail("TryGetHashAndReset failed with pre-allocated input");
                throw new CryptographicException();
            }
 
            t = t.Slice(0, tLength);
 
            // T_i = H(T_(i-1))
            for (int i = 1; i < iterationCount; i++)
            {
                hasher.AppendData(t);
 
                if (!hasher.TryGetHashAndReset(t, out tLength) || tLength != t.Length)
                {
                    Debug.Fail("TryGetHashAndReset failed with pre-allocated input");
                    throw new CryptographicException();
                }
            }
 
            // DK = T_c<0..dkLen-1>
            t.Slice(0, dk.Length).CopyTo(dk);
            CryptographicOperations.ZeroMemory(t);
        }
 
        internal static void WritePbeAlgorithmIdentifier(
            AsnWriter writer,
            bool isPkcs12,
            string encryptionAlgorithmOid,
            Span<byte> salt,
            int iterationCount,
            string hmacOid,
            Span<byte> iv)
        {
            writer.PushSequence();
 
            if (isPkcs12)
            {
                writer.WriteObjectIdentifierForCrypto(encryptionAlgorithmOid);
 
                // pkcs-12PbeParams
                {
                    writer.PushSequence();
                    writer.WriteOctetString(salt);
                    writer.WriteInteger(iterationCount);
                    writer.PopSequence();
                }
            }
            else
            {
                writer.WriteObjectIdentifierForCrypto(Oids.PasswordBasedEncryptionScheme2);
 
                // PBES2-params
                {
                    writer.PushSequence();
 
                    // keyDerivationFunc
                    {
                        writer.PushSequence();
                        writer.WriteObjectIdentifierForCrypto(Oids.Pbkdf2);
 
                        // PBKDF2-params
                        {
                            writer.PushSequence();
 
                            writer.WriteOctetString(salt);
                            writer.WriteInteger(iterationCount);
 
                            // prf
                            if (hmacOid != Oids.HmacWithSha1)
                            {
                                writer.PushSequence();
                                writer.WriteObjectIdentifierForCrypto(hmacOid);
                                writer.WriteNull();
                                writer.PopSequence();
                            }
 
                            writer.PopSequence();
                        }
 
                        writer.PopSequence();
                    }
 
                    // encryptionScheme
                    {
                        writer.PushSequence();
                        writer.WriteObjectIdentifierForCrypto(encryptionAlgorithmOid);
                        writer.WriteOctetString(iv);
                        writer.PopSequence();
                    }
 
                    writer.PopSequence();
                }
            }
 
            writer.PopSequence();
        }
 
        internal static int NormalizeIterationCount(int iterationCount, int? iterationLimit = null)
        {
            if (iterationCount <= 0 || (iterationLimit.HasValue && iterationCount > iterationLimit.Value))
            {
                throw new CryptographicException(SR.Argument_InvalidValue);
            }
 
            return iterationCount;
        }
 
        [SuppressMessage("Microsoft.Security", "CA5351", Justification = "RC2 used when specified by the input data")]
        private static RC2 CreateRC2()
        {
            if (!Helpers.IsRC2Supported)
            {
                throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(RC2)));
            }
 
            return RC2.Create();
        }
 
        private static byte[] DeriveKey(
            byte[] password,
            ReadOnlySpan<byte> salt,
            int iterationCount,
            HashAlgorithmName prf,
            int outputLength)
        {
#if NET
            return Rfc2898DeriveBytes.Pbkdf2(password, salt, iterationCount, prf, outputLength);
#else
            using (Rfc2898DeriveBytes pbkdf2 = OpenPbkdf2(password, salt.ToArray(), iterationCount, prf))
            {
                return pbkdf2.GetBytes(outputLength);
            }
#endif
        }
 
        private static Rfc2898DeriveBytes OpenPbkdf2(
            byte[] password,
            byte[] salt,
            int iterationCount,
            HashAlgorithmName prf)
        {
#if NET || NETSTANDARD2_1_OR_GREATER || NET472_OR_GREATER
#pragma warning disable CA5379
            return new Rfc2898DeriveBytes(
                password,
                salt,
                iterationCount,
                prf);
#pragma warning restore CA5379
#else
            if (prf == HashAlgorithmName.SHA1)
            {
#pragma warning disable CA5379
                return new Rfc2898DeriveBytes(password, salt, iterationCount);
#pragma warning restore CA5379
            }
 
            try
            {
                return (Rfc2898DeriveBytes)Activator.CreateInstance(
                    typeof(Rfc2898DeriveBytes),
                    password,
                    salt,
                    iterationCount,
                    prf);
            }
            catch (MissingMethodException e)
            {
                throw new CryptographicException(
                    SR.Format(SR.Cryptography_UnknownHashAlgorithm, prf.Name),
                    e);
            }
#endif
        }
    }
}