Browse Source

Use System.Security.Cryptography for RSA (#1373)

* Use BCL for RSA

* Restore benchmark (with quirks for old code)

* Fixup benchmark (remove compatibility with old code)

* cosmetic tweaks

* Add a regression test for #1388
Rob Hague 1 year ago
parent
commit
f9908a22b5

+ 35 - 0
src/Renci.SshNet/Common/Extensions.cs

@@ -64,6 +64,41 @@ namespace Renci.SshNet.Common
             return data.ToBigInteger();
             return data.ToBigInteger();
         }
         }
 
 
+        public static byte[] ToByteArray(this BigInteger bigInt, bool isUnsigned = false, bool isBigEndian = false)
+        {
+            var data = bigInt.ToByteArray();
+
+            if (isUnsigned && data[data.Length - 1] == 0)
+            {
+                data = data.Take(data.Length - 1);
+            }
+
+            if (isBigEndian)
+            {
+                _ = data.Reverse();
+            }
+
+            return data;
+        }
+
+        // See https://github.com/dotnet/runtime/blob/9b57a265c7efd3732b035bade005561a04767128/src/libraries/Common/src/System/Security/Cryptography/KeyBlobHelpers.cs#L51
+        public static byte[] ExportKeyParameter(this BigInteger value, int length)
+        {
+            var target = value.ToByteArray(isUnsigned: true, isBigEndian: true);
+
+            // The BCL crypto is expecting exactly-sized byte arrays (sized to "length").
+            // If our byte array is smaller than required, then size it up.
+            // Otherwise, just return as is: if it is too large, we'll let the BCL throw the error.
+            if (target.Length < length)
+            {
+                var correctlySized = new byte[length];
+                Buffer.BlockCopy(target, 0, correctlySized, length - target.Length, target.Length);
+                return correctlySized;
+            }
+
+            return target;
+        }
+
         /// <summary>
         /// <summary>
         /// Reverses the sequence of the elements in the entire one-dimensional <see cref="Array"/>.
         /// Reverses the sequence of the elements in the entire one-dimensional <see cref="Array"/>.
         /// </summary>
         /// </summary>

+ 0 - 19
src/Renci.SshNet/Security/Cryptography/AsymmetricCipher.cs

@@ -1,19 +0,0 @@
-namespace Renci.SshNet.Security.Cryptography
-{
-    /// <summary>
-    /// Base class for asymmetric cipher implementations.
-    /// </summary>
-    public abstract class AsymmetricCipher : Cipher
-    {
-        /// <summary>
-        /// Gets the minimum data size.
-        /// </summary>
-        /// <value>
-        /// The minimum data size.
-        /// </value>
-        public override byte MinimumSize
-        {
-            get { return 0; }
-        }
-    }
-}

+ 0 - 94
src/Renci.SshNet/Security/Cryptography/CipherDigitalSignature.cs

@@ -1,94 +0,0 @@
-using System;
-using Renci.SshNet.Common;
-
-namespace Renci.SshNet.Security.Cryptography
-{
-    /// <summary>
-    /// Implements digital signature where where asymmetric cipher is used.
-    /// </summary>
-    public abstract class CipherDigitalSignature : DigitalSignature
-    {
-        private readonly AsymmetricCipher _cipher;
-        private readonly ObjectIdentifier _oid;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="CipherDigitalSignature"/> class.
-        /// </summary>
-        /// <param name="oid">The object identifier.</param>
-        /// <param name="cipher">The cipher.</param>
-        protected CipherDigitalSignature(ObjectIdentifier oid, AsymmetricCipher cipher)
-        {
-            if (cipher is null)
-            {
-                throw new ArgumentNullException(nameof(cipher));
-            }
-
-            _cipher = cipher;
-            _oid = oid;
-        }
-
-        /// <summary>
-        /// Verifies the signature.
-        /// </summary>
-        /// <param name="input">The input.</param>
-        /// <param name="signature">The signature.</param>
-        /// <returns>
-        /// <see langword="true"/> if signature was successfully verified; otherwise <see langword="false"/>.
-        /// </returns>
-        public override bool Verify(byte[] input, byte[] signature)
-        {
-            var encryptedSignature = _cipher.Decrypt(signature);
-            var hashData = Hash(input);
-            var expected = DerEncode(hashData);
-#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER
-            return System.Security.Cryptography.CryptographicOperations.FixedTimeEquals(expected, encryptedSignature);
-#else
-            return Chaos.NaCl.CryptoBytes.ConstantTimeEquals(expected, encryptedSignature);
-#endif
-        }
-
-        /// <summary>
-        /// Creates the signature.
-        /// </summary>
-        /// <param name="input">The input.</param>
-        /// <returns>
-        /// Signed input data.
-        /// </returns>
-        public override byte[] Sign(byte[] input)
-        {
-            // Calculate hash value
-            var hashData = Hash(input);
-
-            // Calculate DER string
-            var derEncodedHash = DerEncode(hashData);
-
-            return _cipher.Encrypt(derEncodedHash).TrimLeadingZeros();
-        }
-
-        /// <summary>
-        /// Hashes the specified input.
-        /// </summary>
-        /// <param name="input">The input.</param>
-        /// <returns>Hashed data.</returns>
-        protected abstract byte[] Hash(byte[] input);
-
-        /// <summary>
-        /// Encodes hash using DER.
-        /// </summary>
-        /// <param name="hashData">The hash data.</param>
-        /// <returns>
-        /// DER Encoded byte array.
-        /// </returns>
-        protected byte[] DerEncode(byte[] hashData)
-        {
-            var alg = new DerData();
-            alg.Write(_oid);
-            alg.WriteNull();
-
-            var data = new DerData();
-            data.Write(alg);
-            data.Write(hashData);
-            return data.Encode();
-        }
-    }
-}

+ 0 - 144
src/Renci.SshNet/Security/Cryptography/Ciphers/RsaCipher.cs

@@ -1,144 +0,0 @@
-using System;
-using Renci.SshNet.Common;
-
-namespace Renci.SshNet.Security.Cryptography.Ciphers
-{
-    /// <summary>
-    /// Implements RSA cipher algorithm.
-    /// </summary>
-    public class RsaCipher : AsymmetricCipher
-    {
-        private readonly RsaKey _key;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="RsaCipher"/> class.
-        /// </summary>
-        /// <param name="key">The RSA key.</param>
-        public RsaCipher(RsaKey key)
-        {
-            if (key is null)
-            {
-                throw new ArgumentNullException(nameof(key));
-            }
-
-            _key = key;
-        }
-
-        /// <summary>
-        /// Encrypts the specified data.
-        /// </summary>
-        /// <param name="input">The data.</param>
-        /// <param name="offset">The zero-based offset in <paramref name="input"/> at which to begin encrypting.</param>
-        /// <param name="length">The number of bytes to encrypt from <paramref name="input"/>.</param>
-        /// <returns>Encrypted data.</returns>
-        public override byte[] Encrypt(byte[] input, int offset, int length)
-        {
-            // Calculate signature
-            var bitLength = _key.Modulus.BitLength;
-
-            var paddedBlock = new byte[(bitLength / 8) + (bitLength % 8 > 0 ? 1 : 0) - 1];
-
-            paddedBlock[0] = 0x01;
-            for (var i = 1; i < paddedBlock.Length - length - 1; i++)
-            {
-                paddedBlock[i] = 0xFF;
-            }
-
-            Buffer.BlockCopy(input, offset, paddedBlock, paddedBlock.Length - length, length);
-
-            return Transform(paddedBlock);
-        }
-
-        /// <summary>
-        /// Decrypts the specified input.
-        /// </summary>
-        /// <param name="input">The input.</param>
-        /// <param name="offset">The zero-based offset in <paramref name="input"/> at which to begin decrypting.</param>
-        /// <param name="length">The number of bytes to decrypt from <paramref name="input"/>.</param>
-        /// <returns>
-        /// The decrypted data.
-        /// </returns>
-        /// <exception cref="NotSupportedException">Only block type 01 or 02 are supported.</exception>
-        /// <exception cref="NotSupportedException">Thrown when decrypted block type is not supported.</exception>
-        public override byte[] Decrypt(byte[] input, int offset, int length)
-        {
-            var paddedBlock = Transform(input, offset, length);
-
-            if (paddedBlock[0] is not 1 and not 2)
-            {
-                throw new NotSupportedException("Only block type 01 or 02 are supported.");
-            }
-
-            var position = 1;
-
-            while (position < paddedBlock.Length && paddedBlock[position] != 0)
-            {
-                position++;
-            }
-
-            position++;
-
-            var result = new byte[paddedBlock.Length - position];
-            Buffer.BlockCopy(paddedBlock, position, result, 0, result.Length);
-            return result;
-        }
-
-        private byte[] Transform(byte[] data)
-        {
-            return Transform(data, 0, data.Length);
-        }
-
-        private byte[] Transform(byte[] data, int offset, int length)
-        {
-            Array.Reverse(data, offset, length);
-
-            var inputBytes = new byte[length + 1];
-            Buffer.BlockCopy(data, offset, inputBytes, 0, length);
-
-            var input = new BigInteger(inputBytes);
-
-            BigInteger result;
-
-            var isPrivate = !_key.D.IsZero;
-
-            if (isPrivate)
-            {
-                var random = BigInteger.One;
-                var max = _key.Modulus - 1;
-                var bitLength = _key.Modulus.BitLength;
-
-                if (max < BigInteger.One)
-                {
-                    throw new SshException("Invalid RSA key.");
-                }
-
-                while (random <= BigInteger.One || random >= max)
-                {
-                    random = BigInteger.Random(bitLength);
-                }
-
-                var blindedInput = BigInteger.PositiveMod(BigInteger.ModPow(random, _key.Exponent, _key.Modulus) * input, _key.Modulus);
-
-                // mP = ((input Mod p) ^ dP)) Mod p
-                var mP = BigInteger.ModPow(blindedInput % _key.P, _key.DP, _key.P);
-
-                // mQ = ((input Mod q) ^ dQ)) Mod q
-                var mQ = BigInteger.ModPow(blindedInput % _key.Q, _key.DQ, _key.Q);
-
-                var h = BigInteger.PositiveMod((mP - mQ) * _key.InverseQ, _key.P);
-
-                var m = (h * _key.Q) + mQ;
-
-                var rInv = BigInteger.ModInverse(random, _key.Modulus);
-
-                result = BigInteger.PositiveMod(m * rInv, _key.Modulus);
-            }
-            else
-            {
-                result = BigInteger.ModPow(input, _key.Exponent, _key.Modulus);
-            }
-
-            return result.ToByteArray().Reverse();
-        }
-    }
-}

+ 15 - 37
src/Renci.SshNet/Security/Cryptography/RsaDigitalSignature.cs

@@ -1,21 +1,16 @@
-using System;
+#nullable enable
+using System;
 using System.Security.Cryptography;
 using System.Security.Cryptography;
 
 
-using Renci.SshNet.Common;
-using Renci.SshNet.Security.Cryptography.Ciphers;
-
 namespace Renci.SshNet.Security.Cryptography
 namespace Renci.SshNet.Security.Cryptography
 {
 {
     /// <summary>
     /// <summary>
     /// Implements RSA digital signature algorithm.
     /// Implements RSA digital signature algorithm.
     /// </summary>
     /// </summary>
-    public class RsaDigitalSignature : CipherDigitalSignature, IDisposable
+    public class RsaDigitalSignature : DigitalSignature, IDisposable
     {
     {
-#if NET462
-        private readonly HashAlgorithm _hash;
-#else
-        private readonly IncrementalHash _hash;
-#endif
+        private readonly RsaKey _key;
+        private readonly HashAlgorithmName _hashAlgorithmName;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="RsaDigitalSignature"/> class with the SHA-1 hash algorithm.
         /// Initializes a new instance of the <see cref="RsaDigitalSignature"/> class with the SHA-1 hash algorithm.
@@ -32,36 +27,22 @@ namespace Renci.SshNet.Security.Cryptography
         /// <param name="rsaKey">The RSA key.</param>
         /// <param name="rsaKey">The RSA key.</param>
         /// <param name="hashAlgorithmName">The hash algorithm to use in the digital signature.</param>
         /// <param name="hashAlgorithmName">The hash algorithm to use in the digital signature.</param>
         public RsaDigitalSignature(RsaKey rsaKey, HashAlgorithmName hashAlgorithmName)
         public RsaDigitalSignature(RsaKey rsaKey, HashAlgorithmName hashAlgorithmName)
-            : base(ObjectIdentifier.FromHashAlgorithmName(hashAlgorithmName), new RsaCipher(rsaKey))
         {
         {
-#if NET462
-            _hash = CryptoConfig.CreateFromName(hashAlgorithmName.Name) as HashAlgorithm
-                ?? throw new ArgumentException($"Could not create {nameof(HashAlgorithm)} from `{hashAlgorithmName}`.", nameof(hashAlgorithmName));
-#else
-            // CryptoConfig.CreateFromName is a somewhat legacy API and is incompatible with trimming.
-            // Use IncrementalHash instead (which is also more modern and lighter-weight than HashAlgorithm).
-            _hash = IncrementalHash.CreateHash(hashAlgorithmName);
-#endif
+            _key = rsaKey;
+            _hashAlgorithmName = hashAlgorithmName;
         }
         }
 
 
-        /// <summary>
-        /// Hashes the specified input.
-        /// </summary>
-        /// <param name="input">The input.</param>
-        /// <returns>
-        /// Hashed data.
-        /// </returns>
-        protected override byte[] Hash(byte[] input)
+        /// <inheritdoc/>
+        public override bool Verify(byte[] input, byte[] signature)
         {
         {
-#if NET462
-            return _hash.ComputeHash(input);
-#else
-            _hash.AppendData(input);
-            return _hash.GetHashAndReset();
-#endif
+            return _key.RSA.VerifyData(input, signature, _hashAlgorithmName, RSASignaturePadding.Pkcs1);
         }
         }
 
 
-        #region IDisposable Members
+        /// <inheritdoc/>
+        public override byte[] Sign(byte[] input)
+        {
+            return _key.RSA.SignData(input, _hashAlgorithmName, RSASignaturePadding.Pkcs1);
+        }
 
 
         /// <summary>
         /// <summary>
         /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
         /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
@@ -78,9 +59,6 @@ namespace Renci.SshNet.Security.Cryptography
         /// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release only unmanaged resources.</param>
         /// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release only unmanaged resources.</param>
         protected virtual void Dispose(bool disposing)
         protected virtual void Dispose(bool disposing)
         {
         {
-            _hash.Dispose();
         }
         }
-
-        #endregion
     }
     }
 }
 }

+ 54 - 25
src/Renci.SshNet/Security/Cryptography/RsaKey.cs

@@ -1,4 +1,6 @@
-using System;
+#nullable enable
+using System;
+using System.Security.Cryptography;
 
 
 using Renci.SshNet.Common;
 using Renci.SshNet.Common;
 using Renci.SshNet.Security.Cryptography;
 using Renci.SshNet.Security.Cryptography;
@@ -10,8 +12,7 @@ namespace Renci.SshNet.Security
     /// </summary>
     /// </summary>
     public class RsaKey : Key, IDisposable
     public class RsaKey : Key, IDisposable
     {
     {
-        private bool _isDisposed;
-        private RsaDigitalSignature _digitalSignature;
+        private RsaDigitalSignature? _digitalSignature;
 
 
         /// <summary>
         /// <summary>
         /// Gets the name of the key.
         /// Gets the name of the key.
@@ -24,6 +25,8 @@ namespace Renci.SshNet.Security
             return "ssh-rsa";
             return "ssh-rsa";
         }
         }
 
 
+        internal RSA RSA { get; }
+
         /// <summary>
         /// <summary>
         /// Gets the modulus.
         /// Gets the modulus.
         /// </summary>
         /// </summary>
@@ -151,6 +154,9 @@ namespace Renci.SshNet.Security
 
 
             Exponent = publicKeyData.Keys[0];
             Exponent = publicKeyData.Keys[0];
             Modulus = publicKeyData.Keys[1];
             Modulus = publicKeyData.Keys[1];
+
+            RSA = RSA.Create();
+            RSA.ImportParameters(GetRSAParameters());
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -180,6 +186,9 @@ namespace Renci.SshNet.Security
             {
             {
                 throw new InvalidOperationException("Invalid private key (expected EOF).");
                 throw new InvalidOperationException("Invalid private key (expected EOF).");
             }
             }
+
+            RSA = RSA.Create();
+            RSA.ImportParameters(GetRSAParameters());
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -201,6 +210,46 @@ namespace Renci.SshNet.Security
             DP = PrimeExponent(d, p);
             DP = PrimeExponent(d, p);
             DQ = PrimeExponent(d, q);
             DQ = PrimeExponent(d, q);
             InverseQ = inverseQ;
             InverseQ = inverseQ;
+
+            RSA = RSA.Create();
+            RSA.ImportParameters(GetRSAParameters());
+        }
+
+        internal RSAParameters GetRSAParameters()
+        {
+            // Specification of the RSAParameters fields (taken from the CryptographicException
+            // thrown when not done correctly):
+
+            // Exponent and Modulus are required. If D is present, it must have the same length
+            // as Modulus. If D is present, P, Q, DP, DQ, and InverseQ are required and must
+            // have half the length of Modulus, rounded up, otherwise they must be omitted.
+
+            // See also https://github.com/dotnet/runtime/blob/9b57a265c7efd3732b035bade005561a04767128/src/libraries/Common/src/System/Security/Cryptography/RSAKeyFormatHelper.cs#L42
+
+            if (D.IsZero)
+            {
+                // Public key
+                return new RSAParameters()
+                {
+                    Modulus = Modulus.ToByteArray(isUnsigned: true, isBigEndian: true),
+                    Exponent = Exponent.ToByteArray(isUnsigned: true, isBigEndian: true),
+                };
+            }
+
+            var n = Modulus.ToByteArray(isUnsigned: true, isBigEndian: true);
+            var halfModulusLength = (n.Length + 1) / 2;
+
+            return new RSAParameters()
+            {
+                Modulus = n,
+                Exponent = Exponent.ToByteArray(isUnsigned: true, isBigEndian: true),
+                D = D.ExportKeyParameter(n.Length),
+                P = P.ExportKeyParameter(halfModulusLength),
+                Q = Q.ExportKeyParameter(halfModulusLength),
+                DP = DP.ExportKeyParameter(halfModulusLength),
+                DQ = DQ.ExportKeyParameter(halfModulusLength),
+                InverseQ = InverseQ.ExportKeyParameter(halfModulusLength),
+            };
         }
         }
 
 
         private static BigInteger PrimeExponent(BigInteger privateExponent, BigInteger prime)
         private static BigInteger PrimeExponent(BigInteger privateExponent, BigInteger prime)
@@ -224,31 +273,11 @@ namespace Renci.SshNet.Security
         /// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release only unmanaged resources.</param>
         /// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release only unmanaged resources.</param>
         protected virtual void Dispose(bool disposing)
         protected virtual void Dispose(bool disposing)
         {
         {
-            if (_isDisposed)
-            {
-                return;
-            }
-
             if (disposing)
             if (disposing)
             {
             {
-                var digitalSignature = _digitalSignature;
-                if (digitalSignature != null)
-                {
-                    digitalSignature.Dispose();
-                    _digitalSignature = null;
-                }
-
-                _isDisposed = true;
+                _digitalSignature?.Dispose();
+                RSA.Dispose();
             }
             }
         }
         }
-
-        /// <summary>
-        /// Releases unmanaged resources and performs other cleanup operations before the
-        /// <see cref="RsaKey"/> is reclaimed by garbage collection.
-        /// </summary>
-        ~RsaKey()
-        {
-            Dispose(disposing: false);
-        }
     }
     }
 }
 }

+ 1 - 1
test/Renci.SshNet.Benchmarks/Program.cs

@@ -15,7 +15,7 @@ namespace Renci.SshNet.Benchmarks
             //    e.g. "Renci.SshNet.Benchmarks.Security.Cryptography.Ciphers.AesCipherBenchmarks.Encrypt_CBC"):
             //    e.g. "Renci.SshNet.Benchmarks.Security.Cryptography.Ciphers.AesCipherBenchmarks.Encrypt_CBC"):
             //     dotnet run -c Release -- --filter *Ciphers*
             //     dotnet run -c Release -- --filter *Ciphers*
             // 4. Run benchmarks and include memory usage statistics in the output:
             // 4. Run benchmarks and include memory usage statistics in the output:
-            //     dotnet run -c Release -- filter *Rsa* --memory
+            //     dotnet run -c Release -- --filter *Rsa* --memory
             // 3. Print help:
             // 3. Print help:
             //     dotnet run -c Release -- --help
             //     dotnet run -c Release -- --help
 
 

+ 43 - 0
test/Renci.SshNet.Benchmarks/Security/Cryptography/RsaDigitalSignatureBenchmarks.cs

@@ -0,0 +1,43 @@
+using System.Security.Cryptography;
+
+using BenchmarkDotNet.Attributes;
+
+using Renci.SshNet.Security;
+using Renci.SshNet.Security.Cryptography;
+
+namespace Renci.SshNet.Benchmarks.Security.Cryptography
+{
+    [MemoryDiagnoser]
+    public class RsaDigitalSignatureBenchmarks
+    {
+        private readonly RsaKey _key;
+        private readonly byte[] _data;
+        private readonly byte[] _signature;
+
+        public RsaDigitalSignatureBenchmarks()
+        {
+            _data = new byte[128];
+
+            Random random = new(Seed: 12345);
+            random.NextBytes(_data);
+
+            using (var s = typeof(RsaDigitalSignatureBenchmarks).Assembly.GetManifestResourceStream("Renci.SshNet.Benchmarks.Data.Key.OPENSSH.RSA.txt"))
+            {
+                _key = (RsaKey) new PrivateKeyFile(s).Key;
+            }
+            _signature = new RsaDigitalSignature(_key, HashAlgorithmName.SHA256).Sign(_data);
+        }
+
+        [Benchmark]
+        public byte[] Sign()
+        {
+            return new RsaDigitalSignature(_key, HashAlgorithmName.SHA256).Sign(_data);
+        }
+
+        [Benchmark]
+        public bool Verify()
+        {
+            return new RsaDigitalSignature(_key, HashAlgorithmName.SHA256).Verify(_data, _signature);
+        }
+    }
+}

+ 35 - 29
test/Renci.SshNet.Tests/Classes/Security/Cryptography/RsaDigitalSignatureTest.cs

@@ -1,5 +1,4 @@
-using System;
-using System.Security.Cryptography;
+using System.Security.Cryptography;
 using System.Text;
 using System.Text;
 
 
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -53,12 +52,7 @@ namespace Renci.SshNet.Tests.Classes.Security.Cryptography
             // Also verify RsaKey uses SHA-1 by default
             // Also verify RsaKey uses SHA-1 by default
             CollectionAssert.AreEqual(expectedSignedBytes, rsaKey.Sign(data));
             CollectionAssert.AreEqual(expectedSignedBytes, rsaKey.Sign(data));
 
 
-            // The following fails due to the _isPrivate decision in RsaCipher.Transform. Is that really correct?
-            //Assert.IsTrue(digitalSignature.Verify(data, signedBytes));
-
-            // 'Workaround': use a key with no private key information
-            var digitalSignaturePublic = new RsaDigitalSignature(new RsaKey(rsaKey.Modulus, rsaKey.Exponent, default, default, default, default));
-            Assert.IsTrue(digitalSignaturePublic.Verify(data, signedBytes));
+            Assert.IsTrue(digitalSignature.Verify(data, signedBytes));
         }
         }
 
 
         [TestMethod]
         [TestMethod]
@@ -93,15 +87,7 @@ namespace Renci.SshNet.Tests.Classes.Security.Cryptography
                 0xc4, 0xc3, 0x75, 0x51, 0x5f, 0xb7, 0x7c, 0xbc, 0x55, 0x8d, 0x05, 0xc7, 0xed, 0xc7, 0x52, 0x4a
                 0xc4, 0xc3, 0x75, 0x51, 0x5f, 0xb7, 0x7c, 0xbc, 0x55, 0x8d, 0x05, 0xc7, 0xed, 0xc7, 0x52, 0x4a
             }, signedBytes);
             }, signedBytes);
 
 
-
-            // The following fails due to the _isPrivate decision in RsaCipher.Transform. Is that really correct?
-            //Assert.IsTrue(digitalSignature.Verify(data, signedBytes));
-
-            // 'Workaround': use a key with no private key information
-            var digitalSignaturePublic = new RsaDigitalSignature(
-                new RsaKey(rsaKey.Modulus, rsaKey.Exponent, default, default, default, default),
-                HashAlgorithmName.SHA256);
-            Assert.IsTrue(digitalSignaturePublic.Verify(data, signedBytes));
+            Assert.IsTrue(digitalSignature.Verify(data, signedBytes));
         }
         }
 
 
         [TestMethod]
         [TestMethod]
@@ -136,23 +122,43 @@ namespace Renci.SshNet.Tests.Classes.Security.Cryptography
                 0xa0, 0x23, 0x08, 0x80, 0xa6, 0x37, 0x70, 0x06, 0xcc, 0x8f, 0xf4, 0xa0, 0x74, 0x53, 0x26, 0x38
                 0xa0, 0x23, 0x08, 0x80, 0xa6, 0x37, 0x70, 0x06, 0xcc, 0x8f, 0xf4, 0xa0, 0x74, 0x53, 0x26, 0x38
             }, signedBytes);
             }, signedBytes);
 
 
-            // The following fails due to the _isPrivate decision in RsaCipher.Transform. Is that really correct?
-            //Assert.IsTrue(digitalSignature.Verify(data, signedBytes));
-
-            // 'Workaround': use a key with no private key information
-            var digitalSignaturePublic = new RsaDigitalSignature(
-                new RsaKey(rsaKey.Modulus, rsaKey.Exponent, default, default, default, default),
-                HashAlgorithmName.SHA512);
-            Assert.IsTrue(digitalSignaturePublic.Verify(data, signedBytes));
+            Assert.IsTrue(digitalSignature.Verify(data, signedBytes));
         }
         }
 
 
         [TestMethod]
         [TestMethod]
-        public void Constructor_InvalidHashAlgorithm_ThrowsArgumentException()
+        public void SignatureDoesNotTruncateLeadingZeroes()
         {
         {
-            ArgumentException exception = Assert.ThrowsException<ArgumentException>(
-                () => new RsaDigitalSignature(GetRsaKey(), new HashAlgorithmName("invalid")));
+            // A regression test for https://github.com/sshnet/SSH.NET/issues/1388
+
+            byte[] data = { 0x41, 0xdb, 0xf3, 0x09, 0x56 };
+
+            RsaKey rsaKey = GetRsaKey();
+
+            var digitalSignature = new RsaDigitalSignature(rsaKey, HashAlgorithmName.SHA1);
+
+            byte[] signedBytes = digitalSignature.Sign(data);
+
+            CollectionAssert.AreEqual(new byte[]
+            {
+                0x00, 0xa8, 0x35, 0x24, 0xc8, 0xc1, 0x94, 0x97, 0xc3, 0xdf, 0x94, 0x32, 0x62, 0xf2, 0x12, 0x57,
+                0x49, 0x22, 0x7e, 0x52, 0xfe, 0x6e, 0x23, 0x1f, 0x28, 0x84, 0xf9, 0x3e, 0x16, 0xda, 0xc3, 0x6f,
+                0xa0, 0xa4, 0x00, 0x9a, 0x9b, 0xf2, 0x3e, 0xf5, 0x47, 0x3a, 0x7a, 0x8a, 0xd1, 0x1e, 0xf1, 0xd0,
+                0x0b, 0x4d, 0x04, 0x16, 0x6f, 0x29, 0xf1, 0xe7, 0x26, 0xfd, 0x5a, 0x6e, 0x9a, 0xc4, 0x53, 0x7f,
+                0xbe, 0xa3, 0x3b, 0xa0, 0x95, 0x02, 0xe2, 0xcd, 0xd9, 0xa1, 0x4d, 0xae, 0x63, 0x4f, 0x95, 0x5e,
+                0x4f, 0xd6, 0x34, 0x3a, 0x05, 0x93, 0xcb, 0xb4, 0x18, 0xd2, 0xd0, 0xd6, 0x5f, 0x8c, 0xe3, 0x77,
+                0xca, 0x7f, 0x88, 0xfb, 0x72, 0x00, 0x00, 0x74, 0x31, 0xb7, 0xc5, 0xe8, 0xe2, 0x92, 0xf9, 0xab,
+                0x63, 0x8f, 0x87, 0x07, 0xc5, 0x90, 0xf2, 0xdd, 0x6d, 0xc6, 0x38, 0xee, 0x19, 0x65, 0x05, 0xa8,
+                0xda, 0xfc, 0x32, 0xe9, 0xbf, 0x2a, 0x0d, 0x6b, 0x2b, 0xac, 0x92, 0x48, 0xda, 0x3b, 0x8f, 0x4f,
+                0x68, 0x58, 0xa6, 0xa0, 0xcb, 0xdc, 0x44, 0xe3, 0x12, 0x12, 0xb3, 0x0a, 0xf2, 0x4c, 0xa6, 0x17,
+                0xab, 0xdb, 0x5b, 0x79, 0x63, 0x83, 0x8b, 0x10, 0x1c, 0x13, 0xc7, 0xf1, 0x15, 0x88, 0xc6, 0x92,
+                0xcd, 0x6f, 0x81, 0xde, 0x02, 0x1d, 0x35, 0x91, 0xf4, 0x0c, 0x66, 0xe6, 0x7c, 0x3f, 0x02, 0x98,
+                0xa7, 0x79, 0x6c, 0x90, 0x67, 0x14, 0x80, 0x18, 0xeb, 0xe7, 0x52, 0x44, 0x6b, 0x1b, 0x24, 0xac,
+                0x71, 0xaa, 0xde, 0xeb, 0xf7, 0x3c, 0xfc, 0xc0, 0x46, 0x23, 0x40, 0x2f, 0xb0, 0xf2, 0x0b, 0x2e,
+                0xfd, 0xd6, 0xd5, 0x2b, 0x3f, 0x00, 0xd8, 0xfe, 0x30, 0xa6, 0x67, 0xbc, 0x3d, 0xad, 0xb2, 0xf4,
+                0xf3, 0x98, 0xb5, 0x0a, 0x91, 0xf3, 0x9d, 0xfb, 0xe8, 0xeb, 0xe0, 0x35, 0x5b, 0x11, 0xcf, 0xdb,
+            }, signedBytes);
 
 
-            Assert.AreEqual("hashAlgorithmName", exception.ParamName);
+            Assert.IsTrue(digitalSignature.Verify(data, signedBytes));
         }
         }
 
 
         private static RsaKey GetRsaKey()
         private static RsaKey GetRsaKey()

+ 283 - 0
test/Renci.SshNet.Tests/Classes/Security/Cryptography/RsaKeyTest.cs

@@ -0,0 +1,283 @@
+using System;
+using System.IO;
+using System.Security.Cryptography;
+using System.Text;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using Renci.SshNet.Security;
+using Renci.SshNet.Tests.Common;
+
+namespace Renci.SshNet.Tests.Classes.Security.Cryptography
+{
+    [TestClass]
+    public class RsaKeyTest : TestBase
+    {
+        private static RsaKey GetRsaKey(string fileName, string passPhrase = null)
+        {
+            using (var stream = GetData(fileName))
+            {
+                return (RsaKey) new PrivateKeyFile(stream, passPhrase).Key;
+            }
+        }
+
+        // This is just to line up any differences in the assertion message.
+        private static void AssertEqual(byte[] actualBytes, string expectedHex)
+        {
+            string actualHex = BitConverter.ToString(actualBytes).Replace("-", "");
+
+            Assert.AreEqual(expectedHex, actualHex,
+                $"{Environment.NewLine}Expected: {expectedHex}{Environment.NewLine}  Actual: {actualHex}");
+        }
+
+        // These tests generated by converting the keys to PKCS8, importing them to BCL RSA,
+        // and printing out the expected RSAParameter values.
+
+        // e.g. sudo ssh-keygen -f Key.OPENSSH.RSA.Encrypted.txt -m PKCS8 -p
+        //  or  sudo openssl pkcs8 -topk8 -nocrypt -in Key.RSA.Encrypted.Aes.128.CBC.12345.txt -out Key.RSA.Encrypted.Aes.128.CBC.12345.PKCS8.txt
+        
+        /* Something like this:
+
+        using IndentedTextWriter tw = new(Console.Out);
+
+        foreach (string filePath in Directory.EnumerateFiles(dir, "*.RSA.*txt"))
+        {
+            string pkFile = Path.GetFileNameWithoutExtension(filePath);
+
+            if (pkFile.Contains("Des.CBC"))
+            {
+                continue;
+            }
+
+            tw.WriteLine("[TestMethod]");
+            tw.WriteLine($"public void {pkFile.Replace('.', '_')}()");
+            tw.WriteLine("{");
+            tw.Indent++;
+
+            tw.WriteLine($"RsaKey rsaKey = GetRsaKey(\"{pkFile}.txt\");");
+            tw.WriteLine();
+            tw.WriteLine("RSAParameters p = rsaKey.GetRSAParameters();");
+            tw.WriteLine();
+
+            using RSA rsa = RSA.Create();
+
+            rsa.ImportFromPem(File.ReadAllText(filePath));
+            rsa.Dump();
+            RSAParameters p = rsa.ExportParameters(true);
+
+            WriteParamAssert(p.Modulus);
+            WriteParamAssert(p.Exponent);
+            WriteParamAssert(p.D);
+            WriteParamAssert(p.P);
+            WriteParamAssert(p.Q);
+            WriteParamAssert(p.DP);
+            WriteParamAssert(p.DQ);
+            WriteParamAssert(p.InverseQ);
+
+            tw.Indent--;
+            tw.WriteLine("}");
+            tw.WriteLine();
+        }
+
+        void WriteParamAssert(byte[] bytes, [CallerArgumentExpression(nameof(bytes))] string name = null)
+        {
+            tw.WriteLine($"AssertEqual({name}, \"{Convert.ToHexString(bytes)}\");");
+        }
+         */
+
+        [TestMethod]
+        public void Key_OPENSSH_RSA_Encrypted()
+        {
+            RsaKey rsaKey = GetRsaKey("Key.OPENSSH.RSA.Encrypted.txt", "12345");
+
+            RSAParameters p = rsaKey.GetRSAParameters();
+
+            AssertEqual(p.Modulus, "B139F9E6C42D609A65F5F0B0811BF5FBDC1A025D0101100273DD5F70D0531381405E6CADDF91F38C2CDCD69F968C47AD3CC101CE10F6211DF7C225F74947A95458B1D9CF9F846517CCF681E9CAC32D4E8610A06B5BF7133B0F927796759981279AD91EF149EAA90A53799D050C1AEF32D9DD92DE0F68D6FAD375EBBBD6ED3D4053B1D805C13D49828E90AE06537260411031F44CB50E8F0DB3DDBCB335BB295B42A07EFFA8CCB02C8416F94B8ADF46719EC8B4A40DFA3B193663FE7FBCAD5572EC6538CF2DDE66596ADFE93D53D57B7D0C1A0B5E732B9838827FF1178936289C83BA074147BCD502AC3CD9EEBC4879BA9E3CB7ACD50DB2A14AA60BC83E730C37");
+            AssertEqual(p.Exponent, "010001");
+            AssertEqual(p.D, "2DEF12E046D464076089DFFA3F5C59E30F670659C89AD7E56AD663983FC66875C59333A3D52064F95DDF571941D1D5FA069717BAAB16BFFA9E6E899C9037ACA199E36ECAAB538B4821ED7A3A783D220F0A1C6117B25C5575A75195014035AB0DF2CA77849E5DDDB6397079CC07192C0A0801AFC132493FFEFEB3BF878DEB2B46848044B6344EC3C625D63E9BBCFC87589DB55C6339C5184EB95F8BB917EA061FFB3B5D6ADE5DFFFD309226F7E20C67C0520093B7D4ECF19881E2C86F0CCEDBBDA3DFA2B04EF29240A28F73BC1B1F73E19C52FA2F912ABDCE96677922A48B9F54B0A99C35E6C3A767F709049067C98FA319298A890DC001ABD1359D1498759C21");
+            AssertEqual(p.P, "D62A26655E3462A229CE484CFB50A54E3746C6ADC3B816090AC27DA11CD5CABCB30E182869642BB003DEA280FD3039AE17133DEE29C7FEBE3D7FCEA0743DFABC84850758405F7246F68FE636DF88478F282F1BD3F9872A36F771D897FD8E1015AA6DFE6E6B36ADE3D645C5F41B0A3973A8D165A5A09667928D67B933F4DC944F");
+            AssertEqual(p.Q, "D3D8A364635253E35A79910243D783B5DA6D173F0EA1A59A057579F59E3ABB84B551F2847ACE555B107DEA2FF7B0C5601FBBE365E9E95FA15CDABF620D6ADAD734E2B77A9497AEAA7B102A65DF58196B0233B8DAB1AC2961B980F208E122FD0FEFD6CC180DC895917601C127FD2D9F0149913E845A28826E943A6651C51BC799");
+            AssertEqual(p.DP, "697189440174D3573A34193EE5C19812017F3454DB77284A3D64F2CBBF51B7A6DE95C2E92AB2AA1BD85BE63091F80B7E1E5857F689D5B123E34DA8E331384EDA4078EADBD59DE8BAEDAC7DD1688F45844369A64EE09D5EF87F2E2B50F202DF027BCDFA264D5D379264AE27BF7F0CB2AAB2649EA24485A8EAEBFA34A2531DC35B");
+            AssertEqual(p.DQ, "31BE9417758F166DB3880A39EAEE475A43710C5D5D352DEAADCF7914E8D3C7C690C12E5E576BEDF515394BED798F03B9DF99C1FAE3634E765894710E38325832E2933E1C459A1C84A1A3192194C15E2E9774C7BA6AB00F838808B44CCB78E8CD7E3704F3BD00D5C6335C941BACC2B2AC9DB6C26457BB5D6D53D726C19AB43C39");
+            AssertEqual(p.InverseQ, "78C7259CE4638FBE9EC3E5389B0118C3865597F6A1B5C8A338337F9FFE9FB838D789311EAB26E700493A473D1FBCE8DA59C2A7D3CA7BD4A127D8D85165651BF4D8E57AADE29140A7DEB47C953792391DAF3DB006AAD2AC00DCDB9D091A6376BE32CFE5E8D2EBE3A37DF2B7B9ED3086ACE3A7BAACBC11E717A1E1A15608F0D2C0");
+        }
+
+        [TestMethod]
+        public void Key_OPENSSH_RSA()
+        {
+            RsaKey rsaKey = GetRsaKey("Key.OPENSSH.RSA.txt");
+
+            RSAParameters p = rsaKey.GetRSAParameters();
+
+            AssertEqual(p.Modulus, "ED6ECE8A08BB1E3D66B1ADA5F078A62CFCDA815984FC2B1E30A091C4F313B564EFA1974CB7B85F92D85603B876D1AEE8492787DADEC5A9E3A954D18562F455DC1556B0A64931171D2624C26408C7DFCAD893DD17BD9DE62AB28DFF77DC42C8783A23E75AA4FA1D2AA0D78A2257C50FB54C2713AE6C9164B1BF1B7E10B955A42A86E6F1363BF18D0CA404EC5CD8B7ABD08A1305480041AA3BDABB49A35F4D3629888232BEAF825F176240C0E27E7E272412431CE79DFACBF776686AABA344FD9B4F95215E06F3AFC1C5C2EA901AF5111026534C8F018AFFFAB4E227F71DA111A37ABF10CBB5773DF44D9DE8D5BA71DAE2C1A1852788E05560C4E66534D3A5DD3D");
+            AssertEqual(p.Exponent, "010001");
+            AssertEqual(p.D, "E9782AFA0A69CCEB7D9EBB6CCF90237F5B479527AC9FB5DA62E0915603DBDE63994AE116DC151D4DAD12AF67E4D67CD206952B7EA9CDDE27722B68376C3D6C6C1443814291F8068D7023774E9C5FC60C957898502EF12411DA2FCD04547F0AD745FC661378E44467D56EFC7296B1BCA77E717265F275E978EABDA2F1D8FC808637B4AA7444CBD9E1994D6E15873DDE4EC721D62FD7B781B392C88CA34204CA65ECE1CC4AD2FD7FCA27F5D8AB3C8102A36A676460D642A21D19474BA284236B7217061F87E45BE8BF71721D52A8EB508535A2AE6C52DB9769183DA381A9577B28B0F0F9AEA938960AA52650CC7A31D177B45B22AEBE782B8400F64A8C79A44BB5");
+            AssertEqual(p.P, "FE74F0EA7AC163C0C27D497C166EA90B6371F9C978DE0D9184017726E7D2F56EE54772A9240B6BC86503344E840FAA26FAE5B1980CF61F02C8B4F2BA29BC9629191E87303B5DF3053755690F1CDA526F7F6B974C85CD647B1E4BB21391F0623B5852078CFC44390EAF7E6619F75BC892050301591785ECF169D017EBC711A76B");
+            AssertEqual(p.Q, "EEDF6F5E0B05457FD834CCD1DF66EDF5A745AD12B72C46FE7B7EAF94A0FB68BFC9F1505EF5A0E59EE4A4869C6B4C202237D6406D3E151BBDF3A6BAA36B8090638236CF4268B4C52004E4BDD0B8429136395A137FD669D281C338490ED6AEB9893589F92B740569EBB5F2439A6ECAE1A055A40134F6EBC40F60D486F278BE3FF7");
+            AssertEqual(p.DP, "3E959697A55FA1604CB5B1F842302F85AC8CE0E9EBBF79C8DB95DCAF85FF3DFC6B8A1A2A7DC20D5D7972C34FB86D7DE51E7A962696B186491202675CE05D10F5C4C6DC6B417EC701F66D1C6CEA24CC77BDF8417B41C195C02E6061C92C70DA484C5DC89BA5844620558B9A2332D2B14F30CA5F6F8138A061742C0966254D3347");
+            AssertEqual(p.DQ, "2DB030C5E507BA164CCD348BC3D615179249E2639D036C04CAAC1D7B291B4D1AB9CE5D17FC952CB62A774983EB85E0F38A88814423C6B7CAAA2AAEB20922CAFB2D71BC4CB0683AF7F7D7A472A27BA0F8A51994773414837DEC4FFCB148E09C7E20EE65E928C4CC5098396132CD9422799F47C22F56371C05F0F855635532D65B");
+            AssertEqual(p.InverseQ, "B14413D1486AEE5D2E81377863DF3D6C7E9E5B47E8976D7F9BB079CE42507A935A7543D3B35F3CBAFBF88A75BC9F63B74426A75D61D97C227502C47F304116D82E900EDBEA2971DBCDF590942612571075EC28D444A6BC7CD688024F7CCE098D3703B9DCD0657B2AD484F82F9B7C6DEC00A0A735D8E51C0D24BF3F6B458E9A0C");
+        }
+
+        [TestMethod]
+        public void Key_RSA_Encrypted_Aes_128_CBC_12345()
+        {
+            RsaKey rsaKey = GetRsaKey("Key.RSA.Encrypted.Aes.128.CBC.12345.txt", "12345");
+
+            RSAParameters p = rsaKey.GetRSAParameters();
+
+            AssertEqual(p.Modulus, "A16BFBCAA93DCD7F4EE5D1BFFB03906D49D4F2F969C2B7DCFC41532AF394638187389F7702874B449B213712347D06673D6C1211DB05941E72DCDA09835DEE18D5818A4325823D35673CFA4137E79A99AF8160F6DF4039FCC1136C12086D7364E05C7D8E8F6F321A719FEC9338EAB482B32270C7D76812FAC5A6D4068807570A4E9A2E74731610B8ADE55EF62062D72D40E13E665649638F075546920C658B211C48BC99CFE6BD5F9FEEDF5A5C6DF7599FABEAB1944F872CEA1A5CC7283C012B67969C18B8B029797E88FF5AEA18B92D2423C615E140252F8ECBF235B0AEC882F2AA48C4A290A453E612A806B6B810746CA5EE208AA45015FE34DA37EBE042AB");
+            AssertEqual(p.Exponent, "010001");
+            AssertEqual(p.D, "40AAF0F974A9B4BCCECFD522D31DCF6B690BADB76BBE3D4883AA6BF62615097427C6F0638E18C3779DAA45D4BE40642352010C9028AD68747651B1EBE3BBEA99FA56E61C1657C1CBB62B409A8619FBDE9BE7F9FF8CDABF2059FD3CEB5083009AA623878D683B04D88132AF651F852F81B8E0104C8F283B8A494A97607278764EA0C184A64CBC481AEDE5148ECFE2EBCD7CC4A7D3E6013AD54D4DFC0E9A61F2993A25CAE94B9A8FAC9C8761D2C9E4A2A711EFBEF93FCACDCE777504A270C0DC465688A1851282100D20EB38DD9D24377CE45EE213613DBF7C0EEA9FA10442B1E12165B8EE049FD084EFFC362F6EA5159665FFC0F9D2729BB0A4A84DB9C6F05C79");
+            AssertEqual(p.P, "D3B430EFF50E0DF3B5CE1E61B315AD79E8F127D1D09C8983E4C6CBC129653FD4586C7C8A21EFCCFA6166AE753A6D9D4EE6945872A26EEA4796C41F6099B29A3A39484B9073DAD966D9C12E8D59BACF104407F56FC46DA4FB69A98B5BDAA10167C97301E90ECB50A9AE47AA9C48769D50006605F9EB582D5BC01CCAA48F2FA28D");
+            AssertEqual(p.Q, "C33275AF4D9842F97C44D261636D3EC1F935C9F484887BDE250CC656796FC5A5D1AFB811E0CE23E066B1C8861664E4D0703A6BA7421AABE1354F6EDECBE4D29FFD79C0AD0C2E6FD6922C50FEA5E34E26F078E3A16DE30A54DD28BBF9FC156BB9D41D62CE25361499D786493AC31486DF64A709D2D4F5C89ED717088674A54817");
+            AssertEqual(p.DP, "86C8B9437CF496C36C23AFCA2A178A1C0CC7C4BF5B9EDE1A8004656334B4C6BFAE105BF6220727ACDD685F78395BE284825F3E34B5E001FED4294784DC360EE534E5275EEFB40D27E3499016E4630043EDDC5751F2709052950221BCB8643B9DD56F086F5640F1B92641708911C59B007D50774B979FCB391805FCAA6A933C5D");
+            AssertEqual(p.DQ, "00893EF180510EBE08AD57B694C3540CCC566D7B92719D857AAC924EE27E8018787EF70E75839B6D9E5A96A667A1574F8B3F5DD453C626FAB35EC87B2C2C30B7E7C651D527D768CF3A0F8D1965F33DDFB5815F7EAE0295A0AE0AD299592BE5E3D8C54BE42D0DFCF87F330B387218B8561CBCA8B63F877D499265A9CC54BDAF79");
+            AssertEqual(p.InverseQ, "3A5615D08581D13AB9C690B8E4A3E0F6D5093DA14C7BA1B80ACFD0D7D292571F79E19079D25F56EA556DA1F64B657FDEA3A070B3BCA446534B311E5FF41B709CEA8CCCF4CA3D0A42CB060F191B9A5625B64D9910CD8344CD014382BFE92CC97C8CD297FA18A01F314F42D8E7CAAD044BAB4D913CD669A688757F830EB0F9F416");
+        }
+
+        [TestMethod]
+        public void Key_RSA_Encrypted_Aes_192_CBC_12345()
+        {
+            RsaKey rsaKey = GetRsaKey("Key.RSA.Encrypted.Aes.192.CBC.12345.txt", "12345");
+
+            RSAParameters p = rsaKey.GetRSAParameters();
+
+            AssertEqual(p.Modulus, "B89C9B25E22E35D00C85A4B6E1EC0592ABE5FEABA95B452516F17AA149BE9BD800A543BA419303E6DBF54BD49937F9DD62B730A11C04204168EDD04F6E87F04BFD1CF47C75EACEB6CE9F562EC2AD0DF5932C22CB3CB9EEAAD93C62F2D9F6A81221841DFB31970E6581F6569E3E90B7F94A6A8EAFFD3F232CF81FBECD9C9CFD1FC5563679880BF9559859E976BCC2927E51CCAAE57D9125AD03DF61733033A8AF2A80778F94B9113867D110322E9189B03DA707EFF46CB6CFCAF21AF84133CDFB0806F3BD43D50B58F1011897522ECCFEF8DBCD19ACB8D2E553ECF0EC501E7D9FFBEC4F5FE067E91B97A82778CFAA9E14F1654173C6D0EA257C7D757C32999AB3");
+            AssertEqual(p.Exponent, "010001");
+            AssertEqual(p.D, "00F3053537A9872B4FB52C38A886BF2C96E7B2DC8D1D6408A1CA9217DC3A37B0EFA7E9E63E4A143E0EA97F3F539E5F118D5D65C99E4C432232F8EE164A7A36791D8F158EC24877D1A6D458EC1AA22B2D35198800458AAB3883AF74CECCE220D11F0CFFDE77CDD00C2393F1B4149C47E5E7450F00C6AB9971B4C0ED093FA1EB07498F11D394D1F5523BC822BB1B5E9BE57F52C9E02A1F36C0FDC56CD5CAC54A868ACB4E6A83251D8A1AFEFB0C722CFF4B474866D8A2D042505E75C723A54A549FBFF37D87786F29184E0042402A20F07D13DDAFE40D7A32C5C60E48D4FA542662FEF1C8F8DEEAA35F00AC11AAB20DE25BE2632D2FB65FA8466E2896B4FD32B831");
+            AssertEqual(p.P, "DC82FD955920B802CCE555F8C464F5E6C1C14864F8348AF23E87DB908DB99F4F43458ED5BCD8BCC6A79288E1A35E40530EB7E15AFAA6C88A96B9CF09290BD4876500889A1CE4350C8ADE720DD827A44FC2FED83B8B6B65B108723AFFAEEA31C016DBDE438D124A3C3741E53AF90B248E78CCCDBB76233A98343FF549BD30ECB9");
+            AssertEqual(p.Q, "D6528CB7A1F994BD110FE9E83E77FF43D5B6E9C117AF7FFC7605C7CF2EF2985077AD74A3632C88B1277D8B8C629A3DC2899322C14B2282AEF7563A23F16EAFB4877ED646E217EFC5DE7D9DDBFD5E0C908E291B0C304DFF13C2B30272F6F26C6DB255A1FD43C14581E852402438EFAEE04D3FE5C03EED00C468217EFFA27304CB");
+            AssertEqual(p.DP, "B0FF03846931F601696D7B805E439B8D496057379472B8449124837C9805B275F0CD5844FF7C08AA7BBF06D7F645A4E406205A66A8624EFDCED5FDF4A059BA91A2E4B6C0DD07DFFCDD4A87C9CD02A4C4D103B594B5E7D28C32F75D9BF54E4DF89D60861B3A94BFB96DB11ACA2734667765471EABA5BBC9407A500C16D0210121");
+            AssertEqual(p.DQ, "BA0D1753222D434D8DCE3EC3F9D6B9A5ED94C7FF73CC4A52A332096E75D6EB1A8F89E8431E08BEF0EAD3359D3CCBA0E98BFC4AA379D20F984BE672BA5910393F82903CFB73B24BD77D7996DC2E6AC2858AA36C737C4B792F72496BCAD94D418B48D0C5BAD262A93048B97E2CF3B03E23416C42CA4CD5F96086F4ED0685C34B59");
+            AssertEqual(p.InverseQ, "404C8D45530D9EF5DC59BE86AB0B7BC95CCE1A542A8EE914D503A1F9A1B6BE667B1B151FACC6195138DE55377E808A9DD29A513A258C94AC5849BEB27BEB1546C1D7287ED9ED2BF85DB73F422D4BAAAD18B277EED9BC1127E1B14686A3B22812DC35A2EDE4D5B0D33F0AB72C847B83CB061AC580AF6BFD76EC4C45CD45C25F0A");
+        }
+
+        [TestMethod]
+        public void Key_RSA_Encrypted_Aes_256_CBC_12345()
+        {
+            RsaKey rsaKey = GetRsaKey("Key.RSA.Encrypted.Aes.256.CBC.12345.txt", "12345");
+
+            RSAParameters p = rsaKey.GetRSAParameters();
+
+            AssertEqual(p.Modulus, "BA2A9270457E077E81629AB453C6A1ECC80A0F80C0FE766DBFE8D8C8662CB608FB8D19F722F842A34A67501E1A8CF2396104A8528809EC12E5B8E961A50B3A509B6B920358450B3FE9744C4DC3DA277D8DA1749C0EAC59433BDACE71E75E661490E600D6FDCFCCADE4100E01124FCB4104CF18BAE93D87AEFADFE09B77B4C0BCEB247E6C7D45744C7856AA60E7BCB95BCD4030182A571F86344449F903B54BC9494CA365890A474C279BD7C518BE8AFE342B6C3606EF9C04C81EBE6D23C797764B6A48C726234E5D594E809564F46C35E3B84B3DDFB3C7213878957927BAD0BFDE90516A2E60F15C303E047E08A77E421B360D36039A279234D355406720ADF5");
+            AssertEqual(p.Exponent, "010001");
+            AssertEqual(p.D, "32D25A65C1E2D4E2E92964F863826F43F79C1F7D53B263DC342EB9B783852330B293638A3F06AD126CC6656DC26BC5D87A2ECE50B1889BE4D0C7B91689FAD5B199AC35112AB0B6907D07DDA8C115B6002580F517813DF5A4501BBC8E05CE94697AE0FF4BB1C05FACFCFC910623DC9AAD2645EF075F93078E723859D5B74925F0358FA101F0F4351FBE6B42DCC61320A45C56A65D792AE3D3DDBE219E63DA02BBF84967AA057C497C963C358AA7CE69C6BA38C7FF87516431EF14FB29B16717FE36ADC5D405B7305C3B12B0F09A7A22448613C66003F948F2A7FD46A2205D23B519C3C6ED22B9069D0E3890038E4A1EFB4CBC559F1DE9DE905B985B04060DF001");
+            AssertEqual(p.P, "E76717EE79AF3F476451F5D189C4AF1F6FAA69C3BEADD3AF167A49D9967EF54CCDB6BE4AC7AEEDC19BA35C717769416FE02B44831B8094C95CC97874EF3FF9ED12D1F5F389CF7065FA73482C4002C72654B4567BACED4E7C773913CF10A44DC0076FB630BCA04DC71B9088C56884D97AEBE07E0613ABBF7FE9060C7C207DB9F5");
+            AssertEqual(p.Q, "CDF4828EDED33BBB3C957E2494B3A5CB7BB0D6484B16D1008BF3FDE96765A9C74A0AF887A4C7113D8304258C043884A64BA6B8B3582AFF1D443A697D240C8A03AAB1B341CBA4DE5512B84E788537BF61333BED08E864BA7E6C1C7BD968DFA6865D96EE49E3E806C8DA463A24569EF83502702EDA7DC67A5ADE508436F23AA401");
+            AssertEqual(p.DP, "E312E88A0D00EAB0AD023C9E057A6B7B06C20C8D9874CD7058376276D1D6FDF203405B95D843916E56C344F8D77C0349BC89D8EE6B338F4DD5EEA274E53E3B8BE7D6FF6AA0065EBEC3B912CED175A865B5E34DA6897849779C2AF7844F495C3E9F4D81546C15469DA72FFAF6F4ECC0D97DD44AF6BDA0E107ACEC519E10E82F6D");
+            AssertEqual(p.DQ, "5BEFA5042FD5AD59448F1B132B4A125D86268751182BE43A513F267E3548BD8417BA53FD010257E0D4865A58695DED60EA6E4F0EC7F1D195F7E9F743047F907058972084E36A0251899CBA72B44D11624B6399C91B2C33CFD164D9A850575DD7A71983DF7A1C0E4344A48AB9C3ED0CB165183CE20DB936CF2F6453D0CF4A7C01");
+            AssertEqual(p.InverseQ, "A12255F23426886D6622F0DBC23D8005F13C6ECB1670D736A136C335F0641909287DC2BD9CAC0DA8B5658EAAFFD5B49E35F409A7A1AEC2AC6B0F1C360CA50CB045F127C6C7CF3AAAA40E2EA68301EA459B3BA9F76D4A1BA315834AFF0C0FCB178F28F0569AC34011CC5971E8C159EACE4F8437C26E2B0087A705C438966DC82D");
+        }
+
+        [TestMethod]
+        public void Key_RSA_Encrypted_Des_Ede3_CBC_12345()
+        {
+            RsaKey rsaKey = GetRsaKey("Key.RSA.Encrypted.Des.Ede3.CBC.12345.txt", "12345");
+
+            RSAParameters p = rsaKey.GetRSAParameters();
+
+            AssertEqual(p.Modulus, "98DDECDA2329F076AC0D05A8149015615C44D1D7AF8A146072F5B29CA1C80099FBC94751D9735C62C983C5321DF66CDF1934CEA7F4F568056C2D484994C946D42DADBF10E8CE47E560F4ACC50B64B0D72BEEE43C916678055A9657678AD81D55D9085D9C35E436B82E738A581DC664A67431A8907043B95BFCF67D83CEAC329AE08F14E930F855C27726842EBA36B54985D5504A1CF8F922DE54F476AE3DF7159965F8643D70B0411598A652FF4DE1F8C0C60A3510139F43188C19AD6B0890E63DFD0214D34E345D854E5AC1DD5E52F9F1F8EF78EA120F3EFC7556956D313B1D126FD223BBD68EF1C3273FEA3B354A81E344595034FE1BEAAE59F0D62A88BE93");
+            AssertEqual(p.Exponent, "010001");
+            AssertEqual(p.D, "44E5F174B7D8953AC77CEF09BC8ADC380F802D1C0B502EB2F1DD6F6D4D35799FED6DE0A9315ADF4EDF0876FE89A6A19744231AE9746C8F3FB26C0E62AB9EB43FB97ECFD8ED34DC0EB44E52F85494436817B30A478A3926EB32D303FDF50446E4847752C9AF3876B938A676BA146170C7DD22EC987D20E9105EC931F51057684ED784AA1FE0AB14C47E982B9DEDAFAA3E97A422DDCED05D115673E9DED5AE228A2733F7C4C65528E265523E52C913C9066A19A7376450F820A720F13E7CE36B945748048A471D4D3F5BC3AC249A09A88CC410FA0C32228CCE3468A8A060541A73C17C847904DCC4B2184222990843F55BA9F1B178D0F9676FC30F1048C1D92C31");
+            AssertEqual(p.P, "C874E6C096E1F628179CB898799D20F09EFF9CF9C87B6AF28BDB85F8283A8479671498DB7BBC690F24BC946289B694E339129422EFD3DAA21CDC3BBA89F899A08E4CA29E5F16EA601A8AA0E53D496B536A887429A48010E2310FA0AA04F5BD65A9C571B6DF8708E341BDA4FFD3A371C0E80EB792F46BB530C52549CA86FF7717");
+            AssertEqual(p.Q, "C3395086C4BCB1BBBA843F2632567FC0B24171152378334B3ED314060F0FD6DD7E95AD2EAC4C3B343E93F721F3B493A150DB5E7CF08FAC1205A3D59070C23CEBB338CEA7BC84A0E0A6DE59EAFE499C7D027E2C9ADCAD9D7EE9F2EF94597A3281D4CA49C63428C091430FC1F54857E2EA2326D57E34BF9A8F98681646D968E1E5");
+            AssertEqual(p.DP, "011D051B14BB748E8C730663726979015D20B6527CD692A57E395DE91EF01444485A48EF0F24E0C080C7BB5B40C08EDB243A02309F7C61BE7CC28C6260D9CCA0A3A9B14D87E1ACB393F2292A9CC8879C12FF6705C43021E7DFB5E2828F3C7E2CCE60965D19A8BA57E8C990DD0AE3E181DCC81824ECCE9C5705014C039FF45349");
+            AssertEqual(p.DQ, "186DEEA6BEADC22BF4BE21CCDCD5B82CDBE1968E079F3E03F77BA070D5A7ACC290D9980F541E41F65AB2576597428C8ACE3C75497B2CEF625DF44F8040ABFDC59CEAC9E9718CC338F988AF85D9864A9FD61505EDE7198EB0D803BF7937ED33AECD7AF6DA67A285DE679EEC2D8E7CF8F745A8D16476A3576B0AAE53C34FE77105");
+            AssertEqual(p.InverseQ, "C0C0C959707C6ABABA1B908C104EC037CE2C9187B17A659342A22BB44231A8AA53BD4AAD277349D87557F114197265001A1384F63E4746FE4CA1545A8D3BF803C2B7A6F46122F5659885DA60F1F88D1891DDFAA274B58A8AE08C2F2F52E89F9C6E3F79CBE16DD4DF2F9DC96442FA67758DCEF2A6F84451476F8347B3E67CAD5D");
+        }
+
+        [TestMethod]
+        public void Key_RSA_Encrypted_Des_Ede3_CFB_1234567890()
+        {
+            RsaKey rsaKey = GetRsaKey("Key.RSA.Encrypted.Des.Ede3.CFB.1234567890.txt", "1234567890");
+
+            RSAParameters p = rsaKey.GetRSAParameters();
+
+            AssertEqual(p.Modulus, "B3CB66D69596F093904E9BAFB0695FFF1EECBB7F17B8AA3BCCE2E263FEA007E6415ACE940B74E73F55271B5DEAC92F5EB969885AA573FF777A38CFCEF72B23C20CC18D11902327A4C5B5F10DBF88A58AD947C4F94075C58CFA7FC9719C927BAB51817C1B8B9BC6A24050B7B9942CE0FDBE1A37D571BF5B4C4D175A02B2CF95D7");
+            AssertEqual(p.Exponent, "23");
+            AssertEqual(p.D, "7103827840C546C32781DE33EB3AEBE230B20F74754F80F263874C795E38B4821A7397CAC55F6CC8B92E863B089BB7601CC5FDEFD5B6A0943E3256BC9B5CF1E6A1D0842ABE79CAA6044D1B1276D75116E57CE6C19DD690EDFB988384023734D27F4D795CE6472793A855A53C5A69C1CA3D8AA21EB113D8833F1F6B93B419F94B");
+            AssertEqual(p.P, "E6DADD79CFEE89E89EA263F95B6D475BB472E631720F7EC4B49956A4BBC00F62C852AB1C3F10ADAB80F71EA247D01B1F06524798C8FAB07033302D60AF4F01A7");
+            AssertEqual(p.Q, "C760C60DEE444FB408F5F948787F00165AA56FAB0D79A607D88B15EF3A63526E0479615FDE312B7490A2CC65D3CBCBBFE182A88031F68C96EFA98D1FC2117051");
+            AssertEqual(p.DP, "3B5CD9DD7E9C6C9AE6F68EBC7698716F5A496E647C6A626D187F3389721B71ABAFDABE491787F224C9641DD1F53583511EE1F527499F8C749F72C9D7088207BD");
+            AssertEqual(p.DQ, "60D73BA05DC9688AA54445E15F191D4CAFAF70C0CC07E2EDDE34ED659163712E1F6E27FB5D4B151B5C31D0FE424D0B315046C6DF2E35EC83D37E3D3B4FA211FB");
+            AssertEqual(p.InverseQ, "C2A3CAFBAA7370C2692C83B953AB0705B1BB497513BD6798893D41579318D11D6ACE21F8321C229E9DF5A25A404262C9C60D4BD314435C103A9B18E4AD7F07C4");
+        }
+
+        [TestMethod]
+        public void Key_RSA()
+        {
+            RsaKey rsaKey = GetRsaKey("Key.RSA.txt");
+
+            RSAParameters p = rsaKey.GetRSAParameters();
+
+            AssertEqual(p.Modulus, "B93B579FE05AB57D6826EBE1A9F259C398DCFE9708C4950F9AEA05087DFE6D77CA049FFDE22C4D113CD905AB32BD3FE8CDBA006C21B7A9C24E6317F60447930085DED632C0A1377518A0B032F64ECA39EC3CDF79FE50A1C1F76705B333A5961319FA14CA55E67BF9B38E32EEFC9D2A5E047997293D1C54FEC79604B5197C5521E20E42CA4D9DFB77086CAA072CF8F91FBD83142BE0BC7AF9DF134B605A029993411AB65F3B9CB5B25570782F38520ED18A2C23C03A0AD7EDF61FA650F027658AD4DEA71B4167C56D478437922BB7B64DB01ADAF65082F1573169CEE0EFCD64AA7808EA4E45ECA589685DB4A023AFFF9C0F8C837CF8E18E328E61FC5BBDD446E1");
+            AssertEqual(p.Exponent, "23");
+            AssertEqual(p.D, "2A56B4F157DA381CAA17865F774D565897F7FFAD7E58D19D2AB925B8CC57697A775183A7BEAB0A4D1538EB5A54BD8AF3623924AB00647E92D016A6641E3C3EDB8C5030F5A85F5D221441FC636B7FB931CF98E29F8A961658388C9396ACB8224D8247CA3CE06F3247B4033ED78A328D57517AE0B8F80679D3D5D925A5BCAEB45F2017AAC7B00E18BB49E22666898ACDB877DD25E35716A4CC77DAF0111478821E47DA0595032E61DA381AB1E114ED93FA0FE0AB0FE455DD7391E130B74E9F6550C4AD5C29FADC1EC31C6FC2B93808A502CDABDF8437E2E6C27961DCE10C1460932060248F9FD7974ACAAA8E880A5AAD5F7EBB3AC3EAF22E367955A2A4008AE80B");
+            AssertEqual(p.P, "F0658B2DF67BCDE7B210649C19F91CE2B6ACEDDAD731F1BD8AFF3732135ED7593A36AD3727D7B64C222E5FDE979B59849BCA7BD9C8B5223FE30EE6BE5BD765F5BC877311D2A6FC6ADE2359AF326B687011EAEA689A27AA38DBF9E061836EE5B59210464F01BFA0295D301691CF9D146844232B28903417ED5EF605DAE2CEEA75");
+            AssertEqual(p.Q, "C5412C72B4E4815BF31EDD6A79409835FA6EA08E6C87783DC7B639E38D1488F58C8A30BC25DA134B7DCD4E17E5274DE6E8EABB80EA5E0CD474D78A709C56A453BBE0C0D1C53DC26CCCF7E9F8A4267BF15A5FFEAB41DA958B8263C826D78558713854040AE8DDCF78D4836E7B26C634B200F6373B44C9EC16DC954EF358A5A53D");
+            AssertEqual(p.DP, "149AFD4D15208DFDEAB0F2AE4B5E7ED19AA11BB3AC0B9860B4248112EBB7AC0EF65C753F3DEDEB0DD70B4A0BC3DA1D9DA6F4193E8D8BDE5D3F590C76B76A3BF07DDFB967ED7C0701D886AFEA720934D667EF906F5DABA0E04D5E8F9354689758B4BF90FF752DAEA475B3AA29C1569B5964ED1253EF1A68736E8A1DC24DF47A7F");
+            AssertEqual(p.DQ, "32B8FCCD09F19D9B4D25319F093C7EE94F06810EAE3176B0CCF458075029DA1341655CFD2E4E04F627A9D240A8A3B4F23BE4969D7E182F2F513EBD32E65F6C15881538E57BDCAE567DD9596BD2704BC1B827501D68B48CDABB20F8F40B88B057A0C5259C67C402266288325A2E8ABD17D45C91DC0A5FCEFE907E2A3E932347BF");
+            AssertEqual(p.InverseQ, "ADC31A239335127D5DF30EDE9B6122C1CE05E0A3F66BF654BE8128203FBD83CC83665B681B0347E433666E265B5C7DA90ABF88155FFD73A28CFEEEDFB86E3E9DAADF9B97B858CF45674D20D91AEA392CA0E27FFE20486ADD6E50B640B4EE5F61E358265DB2E7BB991C0BEDC5D1E33948A04F7DF91CA1771BDCE701D27D8AFF24");
+        }
+
+        [TestMethod]
+        public void Key_SSH2_RSA()
+        {
+            RsaKey rsaKey = GetRsaKey("Key.SSH2.RSA.txt");
+
+            RSAParameters p = rsaKey.GetRSAParameters();
+
+            AssertEqual(p.Modulus, "D5C19C6D7382399E7802F2A02EEEE26C97DC84A9C700E4C6D41C4C931CBFD290A84CF483017887C8DC63378AA393C9EA83575FB0F7635C24695BE781B10321259D4FA88878581EDF90AEFE5A6E2DC21A9084B43BB8FC239CD7E5358A918B3798279C14A1FB8AD88DF40E088D423C36855E93A2CF2292F540EC609E7639D271161B51AB95F2BAD70BAF979F2EA60F617704613F0907584D95CDB3B98A2240A96FDCE225730CEE3B827C148EA904B0672ED77842AE32ACCFF5D887081CAAA82722A691DCD116532CE3424D66BB9DDA2A01D859A6493FA43AA6153116109B943CDF23582AAE912E11CF15C413795BCD96C89D89AC95D95721E6E842814933EE165F");
+            AssertEqual(p.Exponent, "010001");
+            AssertEqual(p.D, "9A05EB68767DD474DA770D4ADCE6A6E2A6BE701D41D6E4025CDE9352C1B97AC95F6C2C17A5DCAAC2D202F00786FCBBD6AE932BD18140E829187DEF9FE4E3A363CE2992FFDAEF6C379DB26A76D0C34A05083387072DB1091C3FF8DA0EA1F715B51E7D79E48A332EFD826906427B02962A9DCE75D7194B994B11C84106617DC09B62E1870A303A07C76C15E573AF3C23BB596BDDE12DB82842A609D61DEBDBC9AB0E6164E3DFF32DFD6C745C4010401F63BAD2F8569CCDCEC1350F8EDD2DF55601F958A6909CC451FC2D2ED2AB9344505B345637B52C92BA5E1ADF89E1FC62341FA6C8A46E971321E6C81CFEF85B746306170AB7B781A0785C4A3F2F539A4F8F71");
+            AssertEqual(p.P, "F416D8B6B9494FA220E684E219505EEAB37A71902F8E8D97160608BB22C123881C59566607DE20FBDB68266C0E289BABA5031B3B176AB5D25D185D8D5DB36E7414EC50B304F6BDAAF5F00D0C8CE59AED18070A106A887C62FA4ACB5BA17D52F96559899BE1BCF81354F7ADF138381D21E545B6A070F81E7B263C0FEDF4C99699");
+            AssertEqual(p.Q, "E02FD8B0B9ED6EEC3E88ACBDBA3F0B9D6C9499DE53EDDD8919976E4B0C856B194E54D313C7DE8F8540F193910BF9AAB738DC8F7A25CF8FF5DEC0D84C84EA3F80AED4962AAE4EAD5500860D7A3B8F6B736BDEE6FE30E777CE1F43F6D011EF935431204839BC7009C207D9309EBCB9BEBEEF782D9121C9E96EFC4F6429A3E747B7");
+            AssertEqual(p.DP, "4B9D20394AA0C9488244A6F99BB9E80D70D8078211196A0759CD38BB300300CCD36B61766E0D83CE41A3BBF351578711DF265D332B07E5976B3B770FA9ED41D437FF14E8C02AE78AA4715EB97A944E3010776DC8B417DFE09184C60676BA6A0FD433AE8B599CBE84CE97838CEC4B85CAD0498E4F996F6391E6C048770093CA51");
+            AssertEqual(p.DQ, "44CC6A2351EB9B6EC4820D00EC770C897318E75F63424C0A16E071E8761F3B8EB88B2F7B928AAA60AA5D787BBAC113B2C6B069178322FF7AE4B129AFD1DA51429C6480F0F82D0CE45914CA6E08792AF070DA26E93AF8CDBB63BCE7B152CFC2D6C37CB4AD83369EC7FCC09A3B1C8FA409D4537D285CC629ECF56127CBDF90CA61");
+            AssertEqual(p.InverseQ, "BCE1B16B5B42F51361AD815A76B99A2255ED729E85E6AFB9C77C299B5494B99C4641613293FBADBF55D94388B9BB4259D0AA9931C98C3E777A4CDEBBC72C06FFEA7B6822C9791C91B85960E5D2F415C23411E4242946BB4B4220C884C03105D2864B9425F51A5D40ABE1148FBB493CEDB6A70309EACE1154064A54F7BCBE7650");
+        }
+
+        [TestMethod]
+        public void Key_RSA_1277()
+        {
+            // 1277 bits, exponent = 3
+            // openssl genrsa -traditional -3 1277
+            var keyString = """
+            -----BEGIN RSA PRIVATE KEY-----
+            MIIC5gIBAAKBoBUKXQC9ocP8K/mdXgG33wmzhEkahLWCkheVd8MwIlJ8N4NfEhiQ
+            k1H5pygn/Ux6gJMwgCH5TI6rMgtGu4TDUuk+aC16ABb14T1RLOiFRiSxuiLzE/Bx
+            MH0VYMx5wxKI3c5ndTzPx2w7xXtZ6LqKhmVPBA6/E3+f6XHF1cTHXrwONUOKR7tx
+            RTOpMoR4GfJQKfusk8UmnBu1MPqtKKPmJncCAQMCgaAOBuirKRaCqB1RE5QBJT9b
+            zQLbZwMjrGFlDk/XdWw2/XpXlLa7CwzhURoaxVOIUasMywAWpjMJx3ayLyet14yb
+            fvAeUVVko+t+Nh3wWNlty2q//LjQMg8XQeDZcGlXJ0KA9JQnLIVHrs/h/qdiSKng
+            AECZ+3v6kuaIYTuImV4s1kAvyj1vstJEdE0yAIjIYF/VRF0FMQHu2cN/8kYk/g2r
+            AlBkUcwlrkIbiwUokbYH4DnCoWQJdjQm95geEfzCIVhEXJaszFlW++ZwUmxLYE42
+            h1FtnesTiX/U8O0WPLS4RD8E8XYiYXH9a9+bMj0qAEYvNwJQNbEr2Qnj/k8tZvSb
+            HS+UN2uUjYvY2Ikdb5ZfK4XFQzi39ltsgovdH1LHsReTGvF7Z13v2AplNNwNqHzC
+            lIGAlUpP5dvrG8mO8IiElWwi4sECUELhMsPJgWeyA3BhJAVAJoHA7VukIsSlEBQL
+            /dbA5YLoZHMy5jn9RErhnYeViXmvi55pR2Jbqo3182QoeHrYKgNLpBbroVOdP7zM
+            KMaq2XTPAlAjy3KQsUKpih5EoxITdQ16R7heXTs7BhOfuZTHroOCJc/5kkhXB+i/
+            jIUgumIR9lJE6UqQBu4jPV5wUyxjAQBjht/ukpy9MQn1sFhjnWyXKwJQEnbmS2Pl
+            GRDqEnMJEBqKjnvFRX/vyoa8ey3UdnTU8u6HYMousA8aYXKy4iz60ju0JFl+E4Fc
+            a1wk0j6pQa4XVPZ+ff/6GqC5wX5DEY2dk4c=
+            -----END RSA PRIVATE KEY-----
+            """;
+
+            using MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(keyString));
+
+            RsaKey rsaKey = (RsaKey) new PrivateKeyFile(stream).Key;
+
+            RSAParameters p = rsaKey.GetRSAParameters();
+
+            AssertEqual(p.Modulus, "150A5D00BDA1C3FC2BF99D5E01B7DF09B384491A84B58292179577C33022527C37835F1218909351F9A72827FD4C7A8093308021F94C8EAB320B46BB84C352E93E682D7A0016F5E13D512CE8854624B1BA22F313F071307D1560CC79C31288DDCE67753CCFC76C3BC57B59E8BA8A86654F040EBF137F9FE971C5D5C4C75EBC0E35438A47BB714533A932847819F25029FBAC93C5269C1BB530FAAD28A3E62677");
+            AssertEqual(p.Exponent, "03");
+            AssertEqual(p.D, "0E06E8AB291682A81D51139401253F5BCD02DB670323AC61650E4FD7756C36FD7A5794B6BB0B0CE1511A1AC5538851AB0CCB0016A63309C776B22F27ADD78C9B7EF01E515564A3EB7E361DF058D96DCB6ABFFCB8D0320F1741E0D9706957274280F494272C8547AECFE1FEA76248A9E0004099FB7BFA92E688613B88995E2CD6402FCA3D6FB2D244744D320088C8605FD5445D053101EED9C37FF24624FE0DAB");
+            AssertEqual(p.P, "6451CC25AE421B8B052891B607E039C2A16409763426F7981E11FCC22158445C96ACCC5956FBE670526C4B604E3687516D9DEB13897FD4F0ED163CB4B8443F04F176226171FD6BDF9B323D2A00462F37");
+            AssertEqual(p.Q, "35B12BD909E3FE4F2D66F49B1D2F94376B948D8BD8D8891D6F965F2B85C54338B7F65B6C828BDD1F52C7B117931AF17B675DEFD80A6534DC0DA87CC2948180954A4FE5DBEB1BC98EF08884956C22E2C1");
+            AssertEqual(p.DP, "42E132C3C98167B20370612405402681C0ED5BA422C4A510140BFDD6C0E582E8647332E639FD444AE19D87958979AF8B9E6947625BAA8DF5F36428787AD82A034BA416EBA1539D3FBCCC28C6AAD974CF");
+            AssertEqual(p.DQ, "23CB7290B142A98A1E44A31213750D7A47B85E5D3B3B06139FB994C7AE838225CFF992485707E8BF8C8520BA6211F65244E94A9006EE233D5E70532C6301006386DFEE929CBD3109F5B058639D6C972B");
+            AssertEqual(p.InverseQ, "1276E64B63E51910EA127309101A8A8E7BC5457FEFCA86BC7B2DD47674D4F2EE8760CA2EB00F1A6172B2E22CFAD23BB424597E13815C6B5C24D23EA941AE1754F67E7DFFFA1AA0B9C17E43118D9D9387");
+        }
+    }
+}