فهرست منبع

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 سال پیش
والد
کامیت
f9908a22b5

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

@@ -64,6 +64,41 @@ namespace Renci.SshNet.Common
             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>
         /// Reverses the sequence of the elements in the entire one-dimensional <see cref="Array"/>.
         /// </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 Renci.SshNet.Common;
-using Renci.SshNet.Security.Cryptography.Ciphers;
-
 namespace Renci.SshNet.Security.Cryptography
 {
     /// <summary>
     /// Implements RSA digital signature algorithm.
     /// </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>
         /// 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="hashAlgorithmName">The hash algorithm to use in the digital signature.</param>
         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>
         /// 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>
         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.Security.Cryptography;
@@ -10,8 +12,7 @@ namespace Renci.SshNet.Security
     /// </summary>
     public class RsaKey : Key, IDisposable
     {
-        private bool _isDisposed;
-        private RsaDigitalSignature _digitalSignature;
+        private RsaDigitalSignature? _digitalSignature;
 
         /// <summary>
         /// Gets the name of the key.
@@ -24,6 +25,8 @@ namespace Renci.SshNet.Security
             return "ssh-rsa";
         }
 
+        internal RSA RSA { get; }
+
         /// <summary>
         /// Gets the modulus.
         /// </summary>
@@ -151,6 +154,9 @@ namespace Renci.SshNet.Security
 
             Exponent = publicKeyData.Keys[0];
             Modulus = publicKeyData.Keys[1];
+
+            RSA = RSA.Create();
+            RSA.ImportParameters(GetRSAParameters());
         }
 
         /// <summary>
@@ -180,6 +186,9 @@ namespace Renci.SshNet.Security
             {
                 throw new InvalidOperationException("Invalid private key (expected EOF).");
             }
+
+            RSA = RSA.Create();
+            RSA.ImportParameters(GetRSAParameters());
         }
 
         /// <summary>
@@ -201,6 +210,46 @@ namespace Renci.SshNet.Security
             DP = PrimeExponent(d, p);
             DQ = PrimeExponent(d, q);
             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)
@@ -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>
         protected virtual void Dispose(bool disposing)
         {
-            if (_isDisposed)
-            {
-                return;
-            }
-
             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"):
             //     dotnet run -c Release -- --filter *Ciphers*
             // 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:
             //     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 Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -53,12 +52,7 @@ namespace Renci.SshNet.Tests.Classes.Security.Cryptography
             // Also verify RsaKey uses SHA-1 by default
             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]
@@ -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
             }, 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]
@@ -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
             }, 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]
-        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()

+ 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");
+        }
+    }
+}