summary refs log tree commit diff
path: root/crypto
diff options
context:
space:
mode:
authorAnh Vo <anhvo@microsoft.com>2023-03-27 18:38:27 -0400
committerPeter Dettman <peter.dettman@bouncycastle.org>2023-04-15 19:17:18 +0700
commitd0d06afd4ab9dbd680fa30db70428f35a9442d12 (patch)
treede13a4b2fa7373bab57aa2e8ce007c808cba5826 /crypto
parentRefactor NameConstraints (diff)
downloadBouncyCastle.NET-ed25519-d0d06afd4ab9dbd680fa30db70428f35a9442d12.tar.xz
port OpenSSH key support from java api
Diffstat (limited to 'crypto')
-rw-r--r--crypto/src/crypto/ec/CustomNamedCurves.cs12
-rw-r--r--crypto/src/pkcs/PrivateKeyInfoFactory.cs20
-rw-r--r--crypto/src/util/ssh/OpenSSHPrivateKeyUtil.cs314
-rw-r--r--crypto/src/util/ssh/OpenSSHPublicKeyUtil.cs178
-rw-r--r--crypto/src/util/ssh/SSHBuffer.cs150
-rw-r--r--crypto/src/util/ssh/SSHBuilder.cs83
-rw-r--r--crypto/src/util/ssh/SSHNamedCurves.cs96
-rw-r--r--crypto/test/src/crypto/test/OpenSSHKeyParsingTests.cs521
8 files changed, 1364 insertions, 10 deletions
diff --git a/crypto/src/crypto/ec/CustomNamedCurves.cs b/crypto/src/crypto/ec/CustomNamedCurves.cs
index d256dba73..2f6fdfeaa 100644
--- a/crypto/src/crypto/ec/CustomNamedCurves.cs
+++ b/crypto/src/crypto/ec/CustomNamedCurves.cs
@@ -782,14 +782,16 @@ namespace Org.BouncyCastle.Crypto.EC
             new Dictionary<string, DerObjectIdentifier>(StringComparer.OrdinalIgnoreCase);
         private static readonly Dictionary<DerObjectIdentifier, X9ECParametersHolder> curves =
             new Dictionary<DerObjectIdentifier, X9ECParametersHolder>();
-        private static readonly Dictionary<DerObjectIdentifier, string> names =
+        private static readonly Dictionary<DerObjectIdentifier, string> objIdToName =
             new Dictionary<DerObjectIdentifier, string>();
+        private static readonly List<string> names = new List<string>();
 
         private static void DefineCurve(string name, DerObjectIdentifier oid, X9ECParametersHolder holder)
         {
             objIds.Add(name, oid);
-            names.Add(oid, name);
+            objIdToName.Add(oid, name);
             curves.Add(oid, holder);
+            names.Add(name);
         }
 
         private static void DefineCurveAlias(string name, DerObjectIdentifier oid)
@@ -902,7 +904,7 @@ namespace Org.BouncyCastle.Crypto.EC
         /// <param name="oid">The <see cref="DerObjectIdentifier">OID</see> for the curve.</param>
         public static string GetName(DerObjectIdentifier oid)
         {
-            return CollectionUtilities.GetValueOrNull(names, oid);
+            return CollectionUtilities.GetValueOrNull(objIdToName, oid);
         }
 
         /// <summary>Look up the <see cref="DerObjectIdentifier">OID</see> of the curve with the given name.</summary>
@@ -912,10 +914,10 @@ namespace Org.BouncyCastle.Crypto.EC
             return CollectionUtilities.GetValueOrNull(objIds, name);
         }
 
-        /// <summary>Enumerate the available curve names in this registry.</summary>
+        /// <summary>Enumerate the available curve objIdToName in this registry.</summary>
         public static IEnumerable<string> Names
         {
-            get { return CollectionUtilities.Proxy(objIds.Keys); }
+            get { return CollectionUtilities.Proxy(names); }
         }
     }
 }
diff --git a/crypto/src/pkcs/PrivateKeyInfoFactory.cs b/crypto/src/pkcs/PrivateKeyInfoFactory.cs
index d56831f35..b180e49f2 100644
--- a/crypto/src/pkcs/PrivateKeyInfoFactory.cs
+++ b/crypto/src/pkcs/PrivateKeyInfoFactory.cs
@@ -164,15 +164,25 @@ namespace Org.BouncyCastle.Pkcs
                 else
                 {
                     X962Parameters x962;
-                    if (priv.PublicKeyParamSet == null)
+                    //if (priv.PublicKeyParamSet == null)
+                    //{
+                    //    X9ECParameters ecP = new X9ECParameters(dp.Curve, new X9ECPoint(dp.G, false), dp.N, dp.H,
+                    //        dp.GetSeed());
+                    //    x962 = new X962Parameters(ecP);
+                    //}
+                    //else
+                    //{
+                    //    x962 = new X962Parameters(priv.PublicKeyParamSet);
+                    //}
+                    if (dp is ECNamedDomainParameters _dp)
                     {
-                        X9ECParameters ecP = new X9ECParameters(dp.Curve, new X9ECPoint(dp.G, false), dp.N, dp.H,
-                            dp.GetSeed());
-                        x962 = new X962Parameters(ecP);
+                        x962 = new X962Parameters(_dp.Name);  
                     }
                     else
                     {
-                        x962 = new X962Parameters(priv.PublicKeyParamSet);
+                        X9ECParameters ecP = new X9ECParameters(dp.Curve, new X9ECPoint(dp.G, false), dp.N, dp.H,
+                            dp.GetSeed());
+                        x962 = new X962Parameters(ecP);
                     }
 
                     ec = new ECPrivateKeyStructure(orderBitLength, priv.D, publicKey, x962);
diff --git a/crypto/src/util/ssh/OpenSSHPrivateKeyUtil.cs b/crypto/src/util/ssh/OpenSSHPrivateKeyUtil.cs
new file mode 100644
index 000000000..a918d3483
--- /dev/null
+++ b/crypto/src/util/ssh/OpenSSHPrivateKeyUtil.cs
@@ -0,0 +1,314 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading.Tasks;
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Nist;
+using Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.Sec;
+using Org.BouncyCastle.Asn1.X9;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Pkcs;
+
+namespace Org.BouncyCastle.Utilities.SSH
+{
+    public class OpenSSHPrivateKeyUtil
+    {
+        private OpenSSHPrivateKeyUtil()
+        {
+
+        }
+
+        /**
+         * Magic value for proprietary OpenSSH private key.
+         **/
+        static readonly byte[] AUTH_MAGIC = Strings.ToByteArray("openssh-key-v1\0"); // C string so null terminated
+
+        /**
+         * Encode a cipher parameters into an OpenSSH private key.
+         * This does not add headers like ----BEGIN RSA PRIVATE KEY----
+         *
+         * @param parameters the cipher parameters.
+         * @return a byte array
+         */
+        public static byte[] EncodePrivateKey(AsymmetricKeyParameter parameters)
+        {
+            if (parameters == null)
+            {
+                throw new ArgumentException("parameters is null");
+            }
+
+            if (parameters is RsaPrivateCrtKeyParameters || parameters is ECPrivateKeyParameters)
+            {
+                PrivateKeyInfo pInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(parameters);
+                return pInfo.ParsePrivateKey().GetEncoded();
+            }
+            else if (parameters is DsaPrivateKeyParameters dsaPrivateKey)
+            {
+                DsaParameters dsaparameters = dsaPrivateKey.Parameters;
+
+                Asn1EncodableVector vec = new Asn1EncodableVector
+                {
+                    new DerInteger(0),
+                    new DerInteger(dsaparameters.P),
+                    new DerInteger(dsaparameters.Q),
+                    new DerInteger(dsaparameters.G)
+                };
+
+                // public key = g.modPow(x, p);
+                BigInteger pubKey = dsaparameters.P.ModPow(dsaPrivateKey.X, dsaparameters.P);
+                vec.Add(new DerInteger(pubKey));
+                vec.Add(new DerInteger(dsaPrivateKey.X));
+                try
+                {
+                    return new DerSequence(vec).GetEncoded();
+                }
+                catch (Exception ex)
+                {
+                    throw new InvalidOperationException("unable to encode DSAPrivateKeyParameters " + ex.Message);
+                }
+            }
+            else if (parameters is Ed25519PrivateKeyParameters ed25519PrivateKey)
+            {
+                Ed25519PublicKeyParameters publicKeyParameters = ed25519PrivateKey.GeneratePublicKey();
+
+                SSHBuilder builder = new SSHBuilder();
+                builder.WriteBytes(AUTH_MAGIC);
+                builder.WriteString("none");    // cipher name
+                builder.WriteString("none");    // KDF name
+                builder.WriteString("");        // KDF options
+
+                builder.U32(1); // Number of keys
+
+                {
+                    byte[] pkEncoded = OpenSSHPublicKeyUtil.EncodePublicKey(publicKeyParameters);
+                    builder.WriteBlock(pkEncoded);
+                }
+
+                {
+                    SSHBuilder pkBuild = new SSHBuilder();
+
+                    int checkint = CryptoServicesRegistrar.GetSecureRandom().NextInt();
+                    pkBuild.U32((uint)checkint);
+                    pkBuild.U32((uint)checkint);
+
+                    pkBuild.WriteString("ssh-ed25519");
+
+                    // Public key (as part of private key pair)
+                    byte[] pubKeyEncoded = publicKeyParameters.GetEncoded();
+                    pkBuild.WriteBlock(pubKeyEncoded);
+
+                    // The private key in SSH is 64 bytes long and is the concatenation of the private and the public keys
+                    pkBuild.WriteBlock(Arrays.Concatenate(ed25519PrivateKey.GetEncoded(), pubKeyEncoded));
+
+                    // Comment for this private key (empty)
+                    pkBuild.WriteString("");    
+
+                    builder.WriteBlock(pkBuild.GetPaddedBytes());
+                }
+
+                return builder.GetBytes();
+            }
+
+            throw new ArgumentException("unable to convert " + parameters.GetType().Name + " to openssh private key");
+
+        }
+
+        /**
+         * Parse a private key.
+         * <p>
+         * This method accepts the body of the OpenSSH private key.
+         * The easiest way to extract the body is to use PemReader, for example:
+         * <p>
+         * byte[] blob = new PemReader([reader]).readPemObject().getContent();
+         * CipherParameters params = parsePrivateKeyBlob(blob);
+         *
+         * @param blob The key.
+         * @return A cipher parameters instance.
+         */
+        public static AsymmetricKeyParameter ParsePrivateKeyBlob(byte[] blob)
+        {
+            AsymmetricKeyParameter result = null;
+
+            if (blob[0] == 0x30)
+            {
+                Asn1Sequence sequence = Asn1Sequence.GetInstance(blob);
+
+                if (sequence.Count == 6)
+                {
+                    if (AllIntegers(sequence) && ((DerInteger)sequence[0]).PositiveValue.Equals(BigIntegers.Zero))
+                    {
+                        // length of 6 and all Integers -- DSA
+                        result = new DsaPrivateKeyParameters(
+                            ((DerInteger)sequence[5]).PositiveValue,
+                            new DsaParameters(
+                                ((DerInteger)sequence[1]).PositiveValue,
+                                ((DerInteger)sequence[2]).PositiveValue,
+                                ((DerInteger)sequence[3]).PositiveValue)
+                        );
+                    }
+                }
+                else if (sequence.Count == 9)
+                {
+                    if (AllIntegers(sequence) && ((DerInteger)sequence[0]).PositiveValue.Equals(BigIntegers.Zero))
+                    {
+                        // length of 8 and all Integers -- RSA
+                        RsaPrivateKeyStructure rsaPrivateKey = RsaPrivateKeyStructure.GetInstance(sequence);
+
+                        result = new RsaPrivateCrtKeyParameters(
+                            rsaPrivateKey.Modulus,
+                            rsaPrivateKey.PublicExponent,
+                            rsaPrivateKey.PrivateExponent,
+                            rsaPrivateKey.Prime1,
+                            rsaPrivateKey.Prime2,
+                            rsaPrivateKey.Exponent1,
+                            rsaPrivateKey.Exponent2,
+                            rsaPrivateKey.Coefficient);
+                    }
+                }
+                else if (sequence.Count == 4)
+                {
+                    if (sequence[3] is Asn1TaggedObject && sequence[2] is Asn1TaggedObject)
+                    {
+                        ECPrivateKeyStructure ecPrivateKey = ECPrivateKeyStructure.GetInstance(sequence);
+                        DerObjectIdentifier curveOID = DerObjectIdentifier.GetInstance(ecPrivateKey.GetParameters());
+                        X9ECParameters x9Params = ECNamedCurveTable.GetByOid(curveOID);
+                        result = new ECPrivateKeyParameters(
+                            ecPrivateKey.GetKey(),
+                            new ECNamedDomainParameters(
+                                curveOID,
+                                x9Params));
+                    }
+                }
+            }
+            else
+            {
+                SSHBuffer kIn = new SSHBuffer(AUTH_MAGIC, blob);
+
+                String cipherName = kIn.ReadString();
+                if (!"none".Equals(cipherName))
+                {
+                    throw new InvalidOperationException("encrypted keys not supported");
+                }
+
+                // KDF name
+                kIn.SkipBlock();
+
+                // KDF options
+                kIn.SkipBlock();
+
+                int publicKeyCount = kIn.ReadU32();
+                if (publicKeyCount != 1)
+                {
+                    throw new InvalidOperationException("multiple keys not supported");
+                }
+
+                // Burn off public key.
+                OpenSSHPublicKeyUtil.ParsePublicKey(kIn.ReadBlock());
+
+                byte[] privateKeyBlock = kIn.ReadPaddedBlock();
+
+                if (kIn.HasRemaining())
+                {
+                    throw new InvalidOperationException("decoded key has trailing data");
+                }
+
+                SSHBuffer pkIn = new SSHBuffer(privateKeyBlock);
+                int check1 = pkIn.ReadU32();
+                int check2 = pkIn.ReadU32();
+
+                if (check1 != check2)
+                {
+                    throw new InvalidOperationException("private key check values are not the same");
+                }
+
+                String keyType = pkIn.ReadString();
+
+                if ("ssh-ed25519".Equals(keyType))
+                {
+                    // Public key
+                    pkIn.ReadBlock();
+                    // Private key value..
+                    byte[] edPrivateKey = pkIn.ReadBlock();
+                    if (edPrivateKey.Length != Ed25519PrivateKeyParameters.KeySize + Ed25519PublicKeyParameters.KeySize)
+                    {
+                        throw new InvalidOperationException("private key value of wrong length");
+                    }
+
+                    result = new Ed25519PrivateKeyParameters(edPrivateKey, 0);
+                }
+                else if (keyType.StartsWith("ecdsa"))
+                {
+                    DerObjectIdentifier oid = SSHNamedCurves.GetByName(Strings.FromByteArray(pkIn.ReadBlock())) ?? 
+                        throw new InvalidOperationException("OID not found for: " + keyType);
+                    X9ECParameters curveParams = NistNamedCurves.GetByOid(oid) ?? throw new InvalidOperationException("Curve not found for: " + oid);
+
+                    // Skip public key.
+                    pkIn.ReadBlock();
+                    byte[] privKey = pkIn.ReadBlock();
+
+                    result = new ECPrivateKeyParameters(new BigInteger(1, privKey),
+                        new ECNamedDomainParameters(oid, curveParams));
+                }
+                else if (keyType.StartsWith("ssh-rsa"))
+                {
+                    BigInteger modulus = new BigInteger(1, pkIn.ReadBlock());
+                    BigInteger pubExp = new BigInteger(1, pkIn.ReadBlock());
+                    BigInteger privExp = new BigInteger(1, pkIn.ReadBlock());
+                    BigInteger coef = new BigInteger(1, pkIn.ReadBlock());
+                    BigInteger p = new BigInteger(1, pkIn.ReadBlock());
+                    BigInteger q = new BigInteger(1, pkIn.ReadBlock());
+
+                    BigInteger pSub1 = p.Subtract(BigIntegers.One);
+                    BigInteger qSub1 = q.Subtract(BigIntegers.One);
+                    BigInteger dP = privExp.Remainder(pSub1);
+                    BigInteger dQ = privExp.Remainder(qSub1);
+
+                    result = new RsaPrivateCrtKeyParameters(
+                                    modulus,
+                                    pubExp,
+                                    privExp,
+                                    p,
+                                    q,
+                                    dP,
+                                    dQ,
+                                    coef);
+                }
+
+                // Comment for private key
+                pkIn.SkipBlock();
+
+                if (pkIn.HasRemaining())
+                {
+                    throw new ArgumentException("private key block has trailing data");
+                }
+            }
+
+            if (result == null)
+            {
+                throw new ArgumentException("unable to parse key");
+            }
+
+            return result;
+        }
+
+        /**
+         * allIntegers returns true if the sequence holds only DerInteger types.
+         **/
+        private static Boolean AllIntegers(Asn1Sequence sequence)
+        {
+            for (int t = 0; t < sequence.Count; t++)
+            {
+                if (!(sequence[t] is DerInteger))
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+}
+}
diff --git a/crypto/src/util/ssh/OpenSSHPublicKeyUtil.cs b/crypto/src/util/ssh/OpenSSHPublicKeyUtil.cs
new file mode 100644
index 000000000..8f1fa8ec1
--- /dev/null
+++ b/crypto/src/util/ssh/OpenSSHPublicKeyUtil.cs
@@ -0,0 +1,178 @@
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.X9;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Math.EC;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Org.BouncyCastle.Utilities.SSH
+{
+    public class OpenSSHPublicKeyUtil
+    {
+        private OpenSSHPublicKeyUtil()
+        {
+
+        }
+
+        private static readonly String RSA = "ssh-rsa";
+        private static readonly String ECDSA = "ecdsa";
+        private static readonly String ED_25519 = "ssh-ed25519";
+        private static readonly String DSS = "ssh-dss";
+
+        /**
+         * Parse a public key.
+         * <p>
+         * This method accepts the bytes that are Base64 encoded in an OpenSSH public key file.
+         *
+         * @param encoded The key.
+         * @return An AsymmetricKeyParameter instance.
+         */
+        public static AsymmetricKeyParameter ParsePublicKey(byte[] encoded)
+        {
+            SSHBuffer buffer = new SSHBuffer(encoded);
+            return ParsePublicKey(buffer);
+        }
+
+        /**
+         * Encode a public key from an AsymmetricKeyParameter instance.
+         *
+         * @param cipherParameters The key to encode.
+         * @return the key OpenSSH encoded.
+         * @throws IOException
+         */
+        public static byte[] EncodePublicKey(AsymmetricKeyParameter cipherParameters)
+        {
+            if (cipherParameters == null)
+            {
+                throw new ArgumentException("cipherParameters was null.");
+            }
+
+            if (cipherParameters is RsaKeyParameters)
+            {
+                if (cipherParameters.IsPrivate)
+                {
+                    throw new ArgumentException("RSAKeyParamaters was for encryption");
+                }
+
+                RsaKeyParameters rsaPubKey = (RsaKeyParameters)cipherParameters;
+
+                SSHBuilder builder = new SSHBuilder();
+                builder.WriteString(RSA);
+                builder.WriteBigNum(rsaPubKey.Exponent);
+                builder.WriteBigNum(rsaPubKey.Modulus);
+
+                return builder.GetBytes();
+
+            }
+            else if (cipherParameters is ECPublicKeyParameters ecPublicKey)
+            {
+                SSHBuilder builder = new SSHBuilder();
+
+                //
+                // checked for named curve parameters..
+                //
+                String name = SSHNamedCurves.GetNameForParameters(ecPublicKey.Parameters);
+
+                if (name == null)
+                {
+                    throw new ArgumentException("unable to derive ssh curve name for " + ecPublicKey.Parameters.Curve.GetType().Name);
+                }
+
+                builder.WriteString(ECDSA + "-sha2-" + name); // Magic
+                builder.WriteString(name);
+                builder.WriteBlock(ecPublicKey.Q.GetEncoded(false)); //Uncompressed
+                return builder.GetBytes();
+            }
+            else if (cipherParameters is DsaPublicKeyParameters dsaPubKey)
+            {
+                DsaParameters dsaParams = dsaPubKey.Parameters;
+
+                SSHBuilder builder = new SSHBuilder();
+                builder.WriteString(DSS);
+                builder.WriteBigNum(dsaParams.P);
+                builder.WriteBigNum(dsaParams.Q);
+                builder.WriteBigNum(dsaParams.G);
+                builder.WriteBigNum(dsaPubKey.Y);
+                return builder.GetBytes();
+            }
+            else if (cipherParameters is Ed25519PublicKeyParameters ed25519PublicKey)
+            {
+                SSHBuilder builder = new SSHBuilder();
+                builder.WriteString(ED_25519);
+                builder.WriteBlock(ed25519PublicKey.GetEncoded());
+                return builder.GetBytes();
+            }
+
+            throw new ArgumentException("unable to convert " + cipherParameters.GetType().Name + " to private key");
+        }
+
+        /**
+         * Parse a public key from an SSHBuffer instance.
+         *
+         * @param buffer containing the SSH public key.
+         * @return A CipherParameters instance.
+         */
+        public static AsymmetricKeyParameter ParsePublicKey(SSHBuffer buffer)
+        {
+            AsymmetricKeyParameter result = null;
+
+            string magic = buffer.ReadString();
+            if (RSA.Equals(magic))
+            {
+                BigInteger e = buffer.ReadBigNumPositive();
+                BigInteger n = buffer.ReadBigNumPositive();
+                result = new RsaKeyParameters(false, n, e);
+            }
+            else if (DSS.Equals(magic))
+            {
+                BigInteger p = buffer.ReadBigNumPositive();
+                BigInteger q = buffer.ReadBigNumPositive();
+                BigInteger g = buffer.ReadBigNumPositive();
+                BigInteger pubKey = buffer.ReadBigNumPositive();
+
+                result = new DsaPublicKeyParameters(pubKey, new DsaParameters(p, q, g));
+            }
+            else if (magic.StartsWith(ECDSA))
+            {
+                String curveName = buffer.ReadString();
+                DerObjectIdentifier oid = SSHNamedCurves.GetByName(curveName);
+                X9ECParameters x9ECParameters = SSHNamedCurves.GetParameters(oid) ?? 
+                    throw new InvalidOperationException("unable to find curve for " + magic + " using curve name " + curveName);
+                var curve = x9ECParameters.Curve;
+                byte[] pointRaw = buffer.ReadBlock();
+
+                result = new ECPublicKeyParameters(
+                    curve.DecodePoint(pointRaw),
+                    new ECNamedDomainParameters(oid, x9ECParameters));
+            }
+            else if (ED_25519.Equals(magic))
+            {
+                byte[] pubKeyBytes = buffer.ReadBlock();
+                if (pubKeyBytes.Length != Ed25519PublicKeyParameters.KeySize)
+                {
+                    throw new InvalidOperationException("public key value of wrong length");
+                }
+
+                result = new Ed25519PublicKeyParameters(pubKeyBytes, 0);
+            }
+
+            if (result == null)
+            {
+                throw new ArgumentException("unable to parse key");
+            }
+
+            if (buffer.HasRemaining())
+            {
+                throw new ArgumentException("decoded key has trailing data");
+            }
+
+            return result;
+        }
+    }
+}
diff --git a/crypto/src/util/ssh/SSHBuffer.cs b/crypto/src/util/ssh/SSHBuffer.cs
new file mode 100644
index 000000000..8d3c3f977
--- /dev/null
+++ b/crypto/src/util/ssh/SSHBuffer.cs
@@ -0,0 +1,150 @@
+using System;
+using Org.BouncyCastle.Math;
+
+namespace Org.BouncyCastle.Utilities.SSH
+{
+    public class SSHBuffer
+    {
+        private readonly byte[] buffer;
+        private int pos = 0;
+
+        public SSHBuffer(byte[] magic, byte[] buffer)
+        {
+            this.buffer = buffer;
+            for (int i = 0; i != magic.Length; i++)
+            {
+                if (magic[i] != buffer[i])
+                {
+                    throw new ArgumentException("magic-number incorrect");
+                }
+            }
+
+            pos += magic.Length;
+        }
+
+        public SSHBuffer(byte[] buffer)
+        {
+            this.buffer = buffer;
+        }
+
+        public int ReadU32()
+        {
+            if (pos > (buffer.Length - 4))
+            {
+                throw new ArgumentOutOfRangeException("4 bytes for U32 exceeds buffer.");
+            }
+
+            int i = (buffer[pos++] & 0xFF) << 24;
+            i |= (buffer[pos++] & 0xFF) << 16;
+            i |= (buffer[pos++] & 0xFF) << 8;
+            i |= (buffer[pos++] & 0xFF);
+
+            return i;
+        }
+
+        public String ReadString()
+        {
+            return Strings.FromByteArray(ReadBlock());
+        }
+
+        public byte[] ReadBlock()
+        {
+            int len = ReadU32();
+            if (len == 0)
+            {
+                return new byte[0];
+            }
+
+            if (pos > (buffer.Length - len))
+            {
+                throw new ArgumentException("not enough data for block");
+            }
+
+            int start = pos; pos += len;
+            return Arrays.CopyOfRange(buffer, start, pos);
+        }
+
+        public void SkipBlock()
+        {
+            int len = ReadU32();
+            if (pos > (buffer.Length - len))
+            {
+                throw new ArgumentException("not enough data for block");
+            }
+
+            pos += len;
+        }
+
+        public byte[] ReadPaddedBlock()
+        {
+            return ReadPaddedBlock(8);
+        }
+
+        public byte[] ReadPaddedBlock(int blockSize)
+        {
+            int len = ReadU32();
+            if (len == 0)
+            {
+                return new byte[0];
+            }
+
+            if (pos > (buffer.Length - len))
+            {
+                throw new ArgumentException("not enough data for block");
+            }
+
+            int align = len % blockSize;
+            if (0 != align)
+            {
+                throw new ArgumentException("missing padding");
+            }
+
+            int start = pos; pos += len;
+            int end = pos;
+
+            if (len > 0)
+            {
+                // TODO If encryption is supported, should be constant-time
+                int lastByte = buffer[pos - 1] & 0xFF;
+                if (0 < lastByte && lastByte < blockSize)
+                {
+                    int padCount = lastByte;
+                    end -= padCount;
+
+                    for (int i = 1, padPos = end; i <= padCount; ++i, ++padPos)
+                    {
+                        if (i != (buffer[padPos] & 0xFF))
+                        {
+                            throw new ArgumentException("incorrect padding");
+                        }
+                    }
+                }
+            }
+
+            return Arrays.CopyOfRange(buffer, start, end);
+        }
+
+        public BigInteger ReadBigNumPositive()
+        {
+            int len = ReadU32();
+            if (pos + len > buffer.Length)
+            {
+                throw new ArgumentException("not enough data for big num");
+            }
+
+            int start = pos; pos += len;
+            byte[] d = Arrays.CopyOfRange(buffer, start, pos);
+            return new BigInteger(1, d);
+        }
+
+        public byte[] GetBuffer()
+        {
+            return Arrays.Clone(buffer);
+        }
+
+        public Boolean HasRemaining()
+        {
+            return pos < buffer.Length;
+        }
+    }
+}
diff --git a/crypto/src/util/ssh/SSHBuilder.cs b/crypto/src/util/ssh/SSHBuilder.cs
new file mode 100644
index 000000000..5fa92de4b
--- /dev/null
+++ b/crypto/src/util/ssh/SSHBuilder.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Org.BouncyCastle.Math;
+
+namespace Org.BouncyCastle.Utilities.SSH
+{
+    public class SSHBuilder
+    {
+        private readonly MemoryStream bos = new MemoryStream();
+
+        [CLSCompliant(false)]
+        public void U32(uint value)
+        {
+            bos.WriteByte(Convert.ToByte((value >> 24) & 0xFF));
+            bos.WriteByte(Convert.ToByte((value >> 16) & 0xFF));
+            bos.WriteByte(Convert.ToByte((value >> 8) & 0xFF));
+            bos.WriteByte(Convert.ToByte(value & 0xFF));
+        }
+
+        public void WriteBigNum(BigInteger n)
+        {
+            WriteBlock(n.ToByteArray());
+        }
+
+        public void WriteBlock(byte[] value)
+        {
+            U32((uint)value.Length);
+            try
+            {
+                bos.Write(value, 0, value.Length);
+            }
+            catch (IOException e)
+            {
+                throw new InvalidOperationException(e.Message, e);
+            }
+        }
+
+        public void WriteBytes(byte[] value)
+        {
+            try
+            {
+                bos.Write(value, 0, value.Length);
+            }
+            catch (IOException e)
+            {
+                throw new InvalidOperationException(e.Message, e);
+            }
+        }
+
+        public void WriteString(String str)
+        {
+            WriteBlock(Strings.ToByteArray(str));
+        }
+
+        public byte[] GetBytes()
+        {
+            return bos.ToArray();
+        }
+
+        public byte[] GetPaddedBytes()
+        {
+            return GetPaddedBytes(8);
+        }
+
+        public byte[] GetPaddedBytes(int blockSize)
+        {
+            int align = (int)bos.Length % blockSize;
+            if (0 != align)
+            {
+                int padCount = blockSize - align;
+                for (int i = 1; i <= padCount; ++i)
+                {
+                    bos.WriteByte(Convert.ToByte(i));
+                }
+            }
+            return bos.ToArray();
+        }
+    }
+}
diff --git a/crypto/src/util/ssh/SSHNamedCurves.cs b/crypto/src/util/ssh/SSHNamedCurves.cs
new file mode 100644
index 000000000..31c350128
--- /dev/null
+++ b/crypto/src/util/ssh/SSHNamedCurves.cs
@@ -0,0 +1,96 @@
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Nist;
+using Org.BouncyCastle.Asn1.Sec;
+using Org.BouncyCastle.Asn1.X9;
+using Org.BouncyCastle.Crypto.EC;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math.EC;
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Org.BouncyCastle.Utilities.SSH
+{
+    public class SSHNamedCurves
+    {
+        private static readonly Dictionary<string, DerObjectIdentifier> OidMap =
+            new Dictionary<string, DerObjectIdentifier>
+            {
+                { "nistp256", SecObjectIdentifiers.SecP256r1 },
+                { "nistp384", SecObjectIdentifiers.SecP384r1 },
+                { "nistp521", SecObjectIdentifiers.SecP521r1 },
+                { "nistk163", SecObjectIdentifiers.SecT163k1 },
+                { "nistp192", SecObjectIdentifiers.SecP192r1 },
+                { "nistp224", SecObjectIdentifiers.SecP224r1 },
+                { "nistk233", SecObjectIdentifiers.SecT233k1 },
+                { "nistb233", SecObjectIdentifiers.SecT233r1 },
+                { "nistk283", SecObjectIdentifiers.SecT283k1 },
+                { "nistk409", SecObjectIdentifiers.SecT409k1 },
+                { "nistb409", SecObjectIdentifiers.SecT409r1 },
+                { "nistt571", SecObjectIdentifiers.SecT571k1 }
+            };
+
+
+        private static readonly Dictionary<string, string> CurveNameToSSHName = 
+            new Dictionary<string, string>
+            {
+                {"secp256r1", "nistp256"},
+                {"secp384r1", "nistp384"},
+                {"secp521r1", "nistp521"},
+                {"sect163k1", "nistk163"},
+                {"secp192r1", "nistp192"},
+                {"secp224r1", "nistp224"},
+                {"sect233k1", "nistk233"},
+                {"sect233r1", "nistb233"},
+                {"sect283k1", "nistk283"},
+                {"sect409k1", "nistk409"},
+                {"sect409r1", "nistb409"},
+                {"sect571k1", "nistt571"}
+            };
+
+        private static readonly Dictionary<ECCurve, string> CurveMap = 
+            CustomNamedCurves.Names.ToDictionary(k => CustomNamedCurves.GetByNameLazy(k).Curve, v => v);
+
+        private static readonly Dictionary<DerObjectIdentifier, string> OidToName =
+            OidMap.ToDictionary(k => k.Value, v => v.Key);
+
+
+        public static DerObjectIdentifier GetByName(string sshName)
+        {
+            return OidMap[sshName];
+        }
+
+        public static X9ECParameters GetParameters(string sshName)
+        {
+            return NistNamedCurves.GetByOid(OidMap[sshName.ToLower()]);
+        }
+
+        public static X9ECParameters GetParameters(DerObjectIdentifier oid)
+        {
+            return NistNamedCurves.GetByOid(oid);
+        }
+
+        public static string GetName(DerObjectIdentifier oid)
+        {
+            return OidToName[oid];
+        }
+
+        public static string GetNameForParameters(ECDomainParameters parameters)
+        {
+            if (parameters is ECNamedDomainParameters)
+            {
+                return GetName(((ECNamedDomainParameters)parameters).Name);
+            }
+
+            return GetNameForParameters(parameters.Curve);
+        }
+
+        public static string GetNameForParameters(ECCurve curve)
+        {
+            return CurveNameToSSHName[CurveMap[curve]];
+        }
+    }
+}
diff --git a/crypto/test/src/crypto/test/OpenSSHKeyParsingTests.cs b/crypto/test/src/crypto/test/OpenSSHKeyParsingTests.cs
new file mode 100644
index 000000000..c347dcc4b
--- /dev/null
+++ b/crypto/test/src/crypto/test/OpenSSHKeyParsingTests.cs
@@ -0,0 +1,521 @@
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Crypto.Engines;
+using Org.BouncyCastle.Crypto.Paddings;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Utilities.Encoders;
+using Org.BouncyCastle.Utilities.Test;
+using Org.BouncyCastle.Utilities.SSH;
+using Org.BouncyCastle.Utilities.IO.Pem;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Crypto.Signers;
+using System.Linq;
+
+namespace Org.BouncyCastle.Crypto.Tests
+{
+    [TestFixture]
+    public class OpenSSHKeyParsingTests : SimpleTest
+    {
+        private static SecureRandom secureRandom = new SecureRandom();
+
+        String rsa1024Key =
+              "-----BEGIN OPENSSH PRIVATE KEY-----\n"
+            + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn\n"
+            + "NhAAAAAwEAAQAAAIEA37C9iHf9kS3ekS8xVE4p5/bmA7Yc37gXqN10W6c53FzVMiT9ZzVm\n"
+            + "GXqJCRTpLjlX4NgRGHK3nLwyrEhR5JmTrLAXfwb04y3AcdZWZwkZBiXR2rFToEnXNobrvG\n"
+            + "gmXEshBvCq6kUcGWf1FnW4av0kbVRkfiAjM1aMae1KIwlNMDcAAAIIVPY+b1T2Pm8AAAAH\n"
+            + "c3NoLXJzYQAAAIEA37C9iHf9kS3ekS8xVE4p5/bmA7Yc37gXqN10W6c53FzVMiT9ZzVmGX\n"
+            + "qJCRTpLjlX4NgRGHK3nLwyrEhR5JmTrLAXfwb04y3AcdZWZwkZBiXR2rFToEnXNobrvGgm\n"
+            + "XEshBvCq6kUcGWf1FnW4av0kbVRkfiAjM1aMae1KIwlNMDcAAAADAQABAAAAgCWqIc/HvH\n"
+            + "dkjNRPaPP0EVRQm1xGnsgAvGMfnscL+k8jhnZiChUFxcJGgqp3zeeNmkFuwDoonsgSXEns\n"
+            + "B3YBcf7SE//XNMGrGi2FAQTccoTm80NLY77wONST2DNPqxY5xTsTiOJx/DPnru84laq1ae\n"
+            + "t7WiNZCxsmuC0sPYDAG515AAAAQQDzeUo4QQbByJ9JVS0zcj26HKwGZSxxVb1Flq2Y+w0W\n"
+            + "E/4GuYvh3ujXlwEankjYUNGNI0/u0NCzuDPZzBx9LZdeAAAAQQD9TiakDmscY9Dd8bEBL+\n"
+            + "cAhCHrdxtx9ND/793cQNkpm10NL0Fz4jXQfn2/Z7nLFKmMzJlQXzUHH/itzWg9s0MlAAAA\n"
+            + "QQDiEe/BJMLRZ+94n98VCEr7E+eG2isQctxiAowH7o/wp5WAkFSD9W58dqUobuneXleG+F\n"
+            + "DAfXzFhYvNE+TdLXUrAAAADm1hcmtAYmFybmFjbGVzAQIDBA==\n"
+            + "-----END OPENSSH PRIVATE KEY-----\n";
+        String rsa2048Key =
+            "-----BEGIN OPENSSH PRIVATE KEY-----\n"
+          + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn\n"
+          + "NhAAAAAwEAAQAAAQEArxWa1zW+Uf0lUrYoL1yqgTYUT1TfUkfojrhguPB1s/1AEMj8sueu\n"
+          + "YDtLozZW/GB+KwO+nzC48CmqsCbCEOqalmdRIQCCQIBs776c0KLnhqzHCmj0Q+6gM0KvUG\n"
+          + "z8elzJ8LZuTj5xGRDvFxli4yl2M119X7K2JMci18N95rszioxDECSWg2Arvd25kMKBK5MA\n"
+          + "qJjosvxr46soRmxiAHeGzinoLXgpLh9axwySpJ0WVGPl079ZtaYs/XpSoh9HXqCgwnsVy9\n"
+          + "JscWbmtaAktjMw2zTfOvmFs9PVJXtXQRzP4nvtT6myK/7v8tPeg8yLnAot9erklHcUOEyb\n"
+          + "1LsOrk68+QAAA8j/Xs/E/17PxAAAAAdzc2gtcnNhAAABAQCvFZrXNb5R/SVStigvXKqBNh\n"
+          + "RPVN9SR+iOuGC48HWz/UAQyPyy565gO0ujNlb8YH4rA76fMLjwKaqwJsIQ6pqWZ1EhAIJA\n"
+          + "gGzvvpzQoueGrMcKaPRD7qAzQq9QbPx6XMnwtm5OPnEZEO8XGWLjKXYzXX1fsrYkxyLXw3\n"
+          + "3muzOKjEMQJJaDYCu93bmQwoErkwComOiy/GvjqyhGbGIAd4bOKegteCkuH1rHDJKknRZU\n"
+          + "Y+XTv1m1piz9elKiH0deoKDCexXL0mxxZua1oCS2MzDbNN86+YWz09Ule1dBHM/ie+1Pqb\n"
+          + "Ir/u/y096DzIucCi316uSUdxQ4TJvUuw6uTrz5AAAAAwEAAQAAAQBPpNBO3Y+51CHKQjp9\n"
+          + "cPXO2T7b54u+7h8H7S9ycU/ZlHY0LHlnGKTl+ZMqp2liXLKH9qgb2hoGha2ze64D6/RuPo\n"
+          + "lVLdoSZVkopdjHv5L6XFYekierTz1olAkT2L/xGYxzB0meJiFkeaOJKm8lTpMKQpjpk23v\n"
+          + "xPZAmBkJgFatyueHaVWGYp0KzUDpdMcS97R6CWCGrYlAUP3F1meC9+Sb3d94qxeqLZsgEn\n"
+          + "PYJs1Q7fyL4jYBYm9/pA9O5RLKMQwqY7Qln7l2XTyhavZCIxTmAa6lEf32yB3+EoQR+YEz\n"
+          + "eCXXSClbMcnnx83jYyV5uNxN27VJAlgeN7J2ZyJTLlKRAAAAgAUnKuxYaYezMWyBShwR4N\n"
+          + "eVAW8vT3CBxsMR/v3u6XmLTzjq4r0gKCxofnnj972uK0LvyTZ21/00MSl0KaAjJySl2hLj\n"
+          + "BNQA3TcDXnLEc5KcsKZdDhuWkHGmaoajDp/okfQd6CxuKaBKG/OFdbYqVgOOVeACUUWxT4\n"
+          + "NN4e3CxTWQAAAAgQDV3vzDCQanGAXMKZSxfHUU63Tmh+2NcB1I6Sb0/CwpBgLH1y0CTB9r\n"
+          + "c8TLSs6HoHx1lfzOp6Yj7BQ9CWHS94Mi+RYBF+SpaMLoZKqCU4Q3UWiHiOyPnMaohAdvRE\n"
+          + "gJkaY2OAkFaaCI31rwBrs6b5U/ErtRTUZNJEI7OCi6wDBfBwAAAIEA0ZKyuUW5+VFcTyuR\n"
+          + "1G0ky5uihtJryFCjA2fzu7tgobm0gsIgSDClp9TdMh5CDyJo0R9fQnH8Lki0Ku+jgc4X+a\n"
+          + "/XMw47d1iL7Hdu9NAJsplezKD5Unso4xJRXhLnXUT5FT8lSgwE+9xUBuILKUmZQa20ejKM\n"
+          + "20U6szOxEEclA/8AAAAObWFya0BiYXJuYWNsZXMBAgMEBQ==\n"
+          + "-----END OPENSSH PRIVATE KEY-----\n";
+        String rsa3072Key =
+            "-----BEGIN OPENSSH PRIVATE KEY-----\n"
+          + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn\n"
+          + "NhAAAAAwEAAQAAAYEA34VbMJbV2+ZJyRQANnFkTPNcSPkjBllNbnUrlGFQ9wxRyr6xiVRj\n"
+          + "LrjIL+dXaJRhFNktI9191AJI9Eiq9+aWnrjH0/SB38L1MRcktuvBwraPO8K0Pj8A2FkqI0\n"
+          + "uc/XrHLrkg7YbW/So1us1TppOYzuBtGzb8yg2/r+i3ghWT8+h7DWo55pTQGaTHnVyoPPqz\n"
+          + "IDV9yt63tGeL9M3T+Ts9VIkidjV1XXqitkEtksB7cykt4AV0lkN1BWDNbt71YuYhLjDvTK\n"
+          + "jzVq3MfYV91Ux9XaL2uF6pD+0kmn8oNEQ7VRAUFlno1/tsdp578vZDd/ycRfOy9GRLqJ5L\n"
+          + "4mXBsbqxKH9wscHptMgDtqe8B7CxEgU5EZyp8zySPSlwPBebfnr1vemgH4GBfDOA1gZeTK\n"
+          + "HxiWXXUZBMj+S/fJ1YFJ3c5L3ZcHoES3FiIEy2w7tAwfSubkKbP5Wx0hl5/gfM8bAwrPgj\n"
+          + "MMMXR1yKozDbpAzqo2eb+mTkN6FK3U47leFEe3gVAAAFiHAYBoRwGAaEAAAAB3NzaC1yc2\n"
+          + "EAAAGBAN+FWzCW1dvmSckUADZxZEzzXEj5IwZZTW51K5RhUPcMUcq+sYlUYy64yC/nV2iU\n"
+          + "YRTZLSPdfdQCSPRIqvfmlp64x9P0gd/C9TEXJLbrwcK2jzvCtD4/ANhZKiNLnP16xy65IO\n"
+          + "2G1v0qNbrNU6aTmM7gbRs2/MoNv6/ot4IVk/Poew1qOeaU0Bmkx51cqDz6syA1fcret7Rn\n"
+          + "i/TN0/k7PVSJInY1dV16orZBLZLAe3MpLeAFdJZDdQVgzW7e9WLmIS4w70yo81atzH2Ffd\n"
+          + "VMfV2i9rheqQ/tJJp/KDREO1UQFBZZ6Nf7bHaee/L2Q3f8nEXzsvRkS6ieS+JlwbG6sSh/\n"
+          + "cLHB6bTIA7anvAewsRIFORGcqfM8kj0pcDwXm3569b3poB+BgXwzgNYGXkyh8Yll11GQTI\n"
+          + "/kv3ydWBSd3OS92XB6BEtxYiBMtsO7QMH0rm5Cmz+VsdIZef4HzPGwMKz4IzDDF0dciqMw\n"
+          + "26QM6qNnm/pk5DehSt1OO5XhRHt4FQAAAAMBAAEAAAGATJ9obTWnxiQhcx66G++vFHnwTs\n"
+          + "uo6ApA8vaTo9/gY3ADsd7A+XTGM0QAy/sgCaejyAPS55KMCdtmqucmRKj1RR/O0KfmxZAN\n"
+          + "gXCPk20qFNeELlZGd3gdkAyw1zyaaoJmOWwZD5PDqzGHDaxJWrcKERD6FfQ5oAIqjeDW12\n"
+          + "8SMvClDio2AwdMdx33l8glnBHMyePMZXkHvH4qihbs7WkTUyFXgPI+c3cQxC1/s+jr6MRb\n"
+          + "B4qXNtOVD+zpP3KK6AY/AY+hFEjXXTHMwPIAy5Thxt2QncmlgW73zSyvgoXMIxBRy2vni5\n"
+          + "Y8LmcPQ+lkuZPJUXxf+7lb0m2qKav4Ey9FdaNVcBOw1Y1l3ZPGt3Uvd1+v8QikNzurNUuu\n"
+          + "EBjaVBIjXjgGujTZRuEkpdblHDnoMoSha8JRkBFmokJJT/pF42BwptUHZ07tHT7dqn6zvQ\n"
+          + "TRTq+HqAmOibx2mxp+aT5KtUuJA/krMNlhqlTKqvOFx/4t5kZ6ciYoVg/DZe717ONZAAAA\n"
+          + "wCK0Mvik0w29hYEE+hsyLBFQ2CkEpKzvyyIVmckJGJ8zAjjhhs/XUrZGRiKD1swK36YGyd\n"
+          + "+EnZ7gPATWo6CUJmIbkZvZ3hfVljXSvCHPwg8iGo1MiqHWY4MfIhOgVf/lKB7Mfuj9iF0i\n"
+          + "WZK3bZvaFY3+uVfTtWO/JfcmWevLeALBDJztaGmO1IPpleh9FMSDa5fK0w3MJfHSAz/YUc\n"
+          + "maU/1Hz/GdLzgaViewb7Me+Iys27d1YyPwbeXip/vaCPt7bAAAAMEA8+eoaMTJP6FLYC8a\n"
+          + "IVFfx5OLw4xJo6xRNR2QvcNlneh+uvGK0wEwBnkAL9PURmlQEhFcQdSmIHGKQBUGpQ1Huw\n"
+          + "ahWPlaSW9xau2vAvJH3oNoocak35fBfLjEI2UNpRHqhLST7aAe4sEw8xP+5khr/NptEk5C\n"
+          + "X4mRq/4p8REm21tFZt8+VX2DtEKMlYqgAfacgcgV4i2aeo8CJJocH1magby5ZaHJIectAX\n"
+          + "XHszQAm/CaRNWk7rYyjWLxZgASJ4a/AAAAwQDqmu0ZPlkyULFOk/iScA7vYbfnGWT4qC/m\n"
+          + "I54sFDulMN847grXfbyri7kf57KBFXbCh7tQb1Xn947MEKu+ibchkntlKxgCJQzH6dxktq\n"
+          + "yy49d+WLqfjYq1AyDIpgmcdu3pVc8Rxi4GpsHZG1DBf4H9Kc5hw9YnqYXhHHwINNWa07ry\n"
+          + "xcxQuK2sGkRT7Q2NdfEQ9LG4GNIusJeISJgY9NdDBaXrSODSkJI2KCOxDlNY5NsNXXc0Ty\n"
+          + "7fLQW04MPjqisAAAAObWFya0BiYXJuYWNsZXMBAgMEBQ==\n"
+          + "-----END OPENSSH PRIVATE KEY-----\n";
+        String rsa4096Key =
+            "-----BEGIN OPENSSH PRIVATE KEY-----\n"
+          + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAACFwAAAAdzc2gtcn\n"
+          + "NhAAAAAwEAAQAAAgEA2UjzaFgy2oYc6eyCk2tHEhMw/D807dSgVmOJz6ZXfbxIgh5aptbj\n"
+          + "ZG7s2nCR+eURRjVv8avxtB7+sYPirqdch30yaysDAbWtOKTw4efCoxv7ENlcK+hki1Hy+I\n"
+          + "b0epKQ5qit1k83i5XQbKK98GpKkdunMu2XsOrdZfeM/ALzPKN0ZB+vCbyBQdOy+cauRIdl\n"
+          + "5ON26RxeTXGFF1UvCZ1d+vZjdce27fxLnM0Df6SdLY5H9m3Y9lm6R4DFHEttvvSnpVj4Ra\n"
+          + "3lahm7BMBIY5RARuBP4PFewKWcc+ubpq4o01r3D7RX/HswRn2QC86ZiAh6LltRjmSnp0yU\n"
+          + "iM7SBP7Pdsccb/Vx571YWTileWz1Wc6eEEaBSDaV4aTCSsgEbFpxsqDQ95pee59oJmdLkT\n"
+          + "NK1sT2ydhfMORCOcl1b4mJhll5zEoICZ8yJt4VNc5zCnu5d77taQKEh7XTaym8Hkp4ROqb\n"
+          + "Tk/en9HIqvfiTNnVmRGg/S0xkmsIChD7U4ax8CPUHL9EdpwA548PzEyM3ZCH4Zn8V3BTkA\n"
+          + "1qzDATdB788yGGOfMUmPrj2MMKd+RRuIN8FbHxP8jFVMKZSwwSU0qURItjnb5Xb/qiQr2h\n"
+          + "Zt6qv3HHFjEhKzKa7H+hDh/CeCQjgH1BnltkzrGgxwQgMDboIq8R2CPkcf1xuNs4PFbxfn\n"
+          + "cAAAdIBSO/QAUjv0AAAAAHc3NoLXJzYQAAAgEA2UjzaFgy2oYc6eyCk2tHEhMw/D807dSg\n"
+          + "VmOJz6ZXfbxIgh5aptbjZG7s2nCR+eURRjVv8avxtB7+sYPirqdch30yaysDAbWtOKTw4e\n"
+          + "fCoxv7ENlcK+hki1Hy+Ib0epKQ5qit1k83i5XQbKK98GpKkdunMu2XsOrdZfeM/ALzPKN0\n"
+          + "ZB+vCbyBQdOy+cauRIdl5ON26RxeTXGFF1UvCZ1d+vZjdce27fxLnM0Df6SdLY5H9m3Y9l\n"
+          + "m6R4DFHEttvvSnpVj4Ra3lahm7BMBIY5RARuBP4PFewKWcc+ubpq4o01r3D7RX/HswRn2Q\n"
+          + "C86ZiAh6LltRjmSnp0yUiM7SBP7Pdsccb/Vx571YWTileWz1Wc6eEEaBSDaV4aTCSsgEbF\n"
+          + "pxsqDQ95pee59oJmdLkTNK1sT2ydhfMORCOcl1b4mJhll5zEoICZ8yJt4VNc5zCnu5d77t\n"
+          + "aQKEh7XTaym8Hkp4ROqbTk/en9HIqvfiTNnVmRGg/S0xkmsIChD7U4ax8CPUHL9EdpwA54\n"
+          + "8PzEyM3ZCH4Zn8V3BTkA1qzDATdB788yGGOfMUmPrj2MMKd+RRuIN8FbHxP8jFVMKZSwwS\n"
+          + "U0qURItjnb5Xb/qiQr2hZt6qv3HHFjEhKzKa7H+hDh/CeCQjgH1BnltkzrGgxwQgMDboIq\n"
+          + "8R2CPkcf1xuNs4PFbxfncAAAADAQABAAACAF/G4EQmXIQmiagzMHt61iEJhJYr5lDPYL2z\n"
+          + "spNtZzNtQyjX6G2SWzlyC8VdyXq1lh+0fluwxyH2Z54n3EvQSeEPNqI2m2StiGVnjyaE2i\n"
+          + "67rreGmDJiha9DuC4Ejs9Yu7Zws++7i2hj6TN5qO/IaoZQpCq2wB6j6GOB8wtC4aThB/T6\n"
+          + "YlWQWgmCH2oqQbbDWA7ElS2763WHjHr0eX9rdnmhEcZg+il9BHdhhyFElmP2S5I8aV5tvs\n"
+          + "a15CzMsttxTFR+GzHbrTxPizhU6ZO7TXnwdkVZH8MbPRN7z2hxbF19w1mQzRfl1Sm9Pzl1\n"
+          + "IAfudKzqY9C4XY5JG1ASmlDJYPjSZrQOvC+jzvQYYy8iY3LQUlEJHvNG+jmsgaGlW+oye9\n"
+          + "g3nIPo4w5HPE7gmp3vhB3GpaMpH6EmmpoBfWabzNq0SYqEM+l8HIadUKFoE5pVayfj9MGF\n"
+          + "DO36g9ezSPy4hh4QuctTsg2ylBNs/brErjkDspguabqbCCeoVvDYlMrJxqPUiiC2vRAb47\n"
+          + "8qIKFQz56Q2Egm1g4VzCwNz1gkO/IIp7ZCidi3Fbjx5tgMhk5VzqrqTzTIp3oKtV8unpZ0\n"
+          + "UEKyNBjnm4Frwl+hlUjTummpWWwtLObbsvE0CDg09hCU/47sgwtU/KpNdwZJ6gGcScS5dE\n"
+          + "f0uEmDtfxBPI9hsScBAAABAQCJOIDnOOwaNe7mRdF4cDuX1jq9tYSMA365mWc7FhA4ORYF\n"
+          + "2AY744mPsTF936G2zpIveXPxPqQ83SQjQufkGPrMBU9n+O/DtLTZbXafK3VFGQ1VApkGNE\n"
+          + "6RJA61OR8L3GYAujdzAJ1Lxg7SzOqXkL1pUSGSi2brBceqjkEuuOfUk+AT/7NAMo0E07me\n"
+          + "Bte1v31ijrCPJMgpFMLLXimKQDBrdeox8cd0uVEqFIzdp0kn/2H4n3X/XMvGMsVBLVbmh4\n"
+          + "LtZZdkW3/f7WK9GSTAkpBKixtgTm4oEonKTT6yM4zvsY1gzq+jzF4mkRhed6nhXq0B7lII\n"
+          + "TWnzwaSBT0HAgM+9AAABAQDwI8C7UJObpJpHbcbGZ14RQXajkNG3NE9a8OKdvImxb1jXWR\n"
+          + "OQEmjRx08J6GjtXy9MS87PHvAjD6NmWU8DPegoFYGI1Npl9jLB1d1x9vvjF9DQA4YvQurB\n"
+          + "WIAOavMHF4tc+tNzTPYC0l/IY3SVwgd/bbLHlAdozclHmYMD2WQT3lOhPMAgQLpnZk0ETR\n"
+          + "3EGVCDRX2qCxCGvmaLtuwyW9VxESfYTgCxAeIHf1ru5ezvH5ZBieKi8WBbJOFUrTQHjW+j\n"
+          + "2cFwNq2s/FdIzl1ZBsO3cUoCtgVau2Tr1TkccunXFrbvtKYqFGCywxcB5hcymKXIF13SpP\n"
+          + "Z7iaI1jp42w4BxAAABAQDnosYmEJyoIWulpoKbC/zXMVeslMHgdTQUsFDHZ5RD3C+A1Jbu\n"
+          + "Fx9m8zzFcnvElz4rJmRTT53s+iZokvIB1XM4g7V4jAknLHlyUe5L/iCGAinP9mLGFWtDTH\n"
+          + "Z+NXL+uhB66XcgXgovFEy8WkPu7GJoxBf7ZgYKEodAWv+Etcdp6Zzr/yivzpQDrzZr6SgS\n"
+          + "U1lKaBP8mrEwX/TU0rrvyIx04/WVxtmA1vmSWweEyMiQxbLmWngWwrQVXTa1N5AZorzHSs\n"
+          + "7NalafAFnf+Sg12wVD6f0ujP/ozQ24Arzc5rmE/AV+XJ7vqnjS1CeHSxTHPYrpKtC6mFQy\n"
+          + "S+iAb4yzfmFnAAAADm1hcmtAYmFybmFjbGVzAQIDBA==\n"
+          + "-----END OPENSSH PRIVATE KEY-----\n";
+        String ecdsa256Key =
+            "-----BEGIN OPENSSH PRIVATE KEY-----\n"
+          + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS\n"
+          + "1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQS9VjynnoLGUcT2hXXPkFwGfbleI4Ln\n"
+          + "1kkgt2UgibKXw9NtesSqpKdEBDW5Kh2nmqLCIk+fdbsTGkxlfaYBtUrkAAAAqBQCER8UAh\n"
+          + "EfAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL1WPKeegsZRxPaF\n"
+          + "dc+QXAZ9uV4jgufWSSC3ZSCJspfD0216xKqkp0QENbkqHaeaosIiT591uxMaTGV9pgG1Su\n"
+          + "QAAAAgbAJJUVcjwwU/olgrxgINJ1DViX6GcCBhgeH8wAXiNKoAAAAObWFya0BiYXJuYWNs\n"
+          + "ZXMBAg==\n"
+          + "-----END OPENSSH PRIVATE KEY-----\n";
+        String ecdsa384Key =
+            "-----BEGIN OPENSSH PRIVATE KEY-----\n"
+          + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNlY2RzYS\n"
+          + "1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQS0yKimt2kBeyNKUqNivPfSPBVyU4jH\n"
+          + "9+6hNsRIJG4NKRgKdIOIiOOLm6pGLUmwN4yDS+0ssdPxwRthQzL879HRtwbqAAb1ShK0CT\n"
+          + "rljAhk9+SUgrOqWnKL2Ngo1uU5KZgAAADYJC2IQSQtiEEAAAATZWNkc2Etc2hhMi1uaXN0\n"
+          + "cDM4NAAAAAhuaXN0cDM4NAAAAGEEtMioprdpAXsjSlKjYrz30jwVclOIx/fuoTbESCRuDS\n"
+          + "kYCnSDiIjji5uqRi1JsDeMg0vtLLHT8cEbYUMy/O/R0bcG6gAG9UoStAk65YwIZPfklIKz\n"
+          + "qlpyi9jYKNblOSmYAAAAMQChvecXe7PGUVG0Pz2IgM9f80YLXdarf98sRptbGSIPwu8KlW\n"
+          + "OlGv0Any5ue51/I5wAAAAObWFya0BiYXJuYWNsZXMB\n"
+          + "-----END OPENSSH PRIVATE KEY-----\n";
+        String ecdsa521Key =
+            "-----BEGIN OPENSSH PRIVATE KEY-----\n"
+          + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS\n"
+          + "1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQA90An5exsl3UEU0d8fhqV8rgmoyzJ\n"
+          + "21sZYrjFV+bs583tbSIMYAapk8jSKtk+r1z48KQdsR9czydmy2yYbdXruXMBPdQrf+11BB\n"
+          + "dCs1E9iFet1UB8OruVeduD5dm0In7yJK1Qo18xe0NpOjOHeZ1ixAxdOt9zuolAlBTwZYth\n"
+          + "FMESME8AAAEQApLNRAKSzUQAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQ\n"
+          + "AAAIUEAPdAJ+XsbJd1BFNHfH4alfK4JqMsydtbGWK4xVfm7OfN7W0iDGAGqZPI0irZPq9c\n"
+          + "+PCkHbEfXM8nZstsmG3V67lzAT3UK3/tdQQXQrNRPYhXrdVAfDq7lXnbg+XZtCJ+8iStUK\n"
+          + "NfMXtDaTozh3mdYsQMXTrfc7qJQJQU8GWLYRTBEjBPAAAAQgFHl5a1JDqcCeaAx84z3u/v\n"
+          + "z7dyVl4uohlQPaiZ+hhtbbUg6oLMnVGGjjmviR0C0aDzx0xDEsK8TseFd16mBWpOnAAAAA\n"
+          + "5tYXJrQGJhcm5hY2xlcwECAwQ=\n"
+          + "-----END OPENSSH PRIVATE KEY-----\n";
+
+        public static void main(
+            String[] args)
+        {
+            RunTest(new OpenSSHKeyParsingTests());
+        }
+
+
+        public void TestDSA()
+        {
+            var pubSpec = OpenSSHPublicKeyUtil.ParsePublicKey(Base64.Decode("AAAAB3NzaC1kc3MAAACBAJBB5+S4kZZYZLswaQ/zm3GM7YWmHsumwo/Xxu+z6Cg2l5PUoiBBZ4ET9EhhQuL2ja/zrCMCi0ZwiSRuSp36ayPrHLbNJb3VdOuJg8xExRa6F3YfVZfcTPUEKh6FU72fI31HrQmi4rpyHnWxL/iDX496ZG2Hdq6UkPISQpQwj4TtAAAAFQCP9TXcVahR/2rpfEhvdXR0PfhbRwAAAIBdXzAVqoOtb9zog6lNF1cGS1S06W9W/clvuwq2xF1s3bkoI/xUbFSc0IAPsGl2kcB61PAZqcop50lgpvYzt8cq/tbqz3ypq1dCQ0xdmJHj975QsRFax+w6xQ0kgpBhwcS2EOizKb+C+tRzndGpcDSoSMuVXp9i4wn5pJSTZxAYFQAAAIEAhQZc687zYxrEDR/1q6m4hw5GFxuVvLsC+bSHtMF0c11Qy4IPg7mBeP7K5Kq4WyJPtmZhuc5Bb12bJQR6qgd1uLn692fe1UK2kM6eWXBzhlzZ54BslfSKHGNN4qH+ln3Zaf/4rpKE7fvoinkrgkOZmj0PMx9D6wlpHKkXMUxeXtc="));
+
+            var privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(new PemReader(new StringReader("-----BEGIN DSA PRIVATE KEY-----\n" +
+                "MIIBuwIBAAKBgQCQQefkuJGWWGS7MGkP85txjO2Fph7LpsKP18bvs+goNpeT1KIg\n" +
+                "QWeBE/RIYULi9o2v86wjAotGcIkkbkqd+msj6xy2zSW91XTriYPMRMUWuhd2H1WX\n" +
+                "3Ez1BCoehVO9nyN9R60JouK6ch51sS/4g1+PemRth3aulJDyEkKUMI+E7QIVAI/1\n" +
+                "NdxVqFH/aul8SG91dHQ9+FtHAoGAXV8wFaqDrW/c6IOpTRdXBktUtOlvVv3Jb7sK\n" +
+                "tsRdbN25KCP8VGxUnNCAD7BpdpHAetTwGanKKedJYKb2M7fHKv7W6s98qatXQkNM\n" +
+                "XZiR4/e+ULERWsfsOsUNJIKQYcHEthDosym/gvrUc53RqXA0qEjLlV6fYuMJ+aSU\n" +
+                "k2cQGBUCgYEAhQZc687zYxrEDR/1q6m4hw5GFxuVvLsC+bSHtMF0c11Qy4IPg7mB\n" +
+                "eP7K5Kq4WyJPtmZhuc5Bb12bJQR6qgd1uLn692fe1UK2kM6eWXBzhlzZ54BslfSK\n" +
+                "HGNN4qH+ln3Zaf/4rpKE7fvoinkrgkOZmj0PMx9D6wlpHKkXMUxeXtcCFELnLOJ8\n" +
+                "D0akSCUFY/iDLo/KnOIH\n" +
+                "-----END DSA PRIVATE KEY-----\n")).ReadPemObject().Content);
+
+            var signer = new DsaSigner();
+            signer.Init(true, privSpec);
+
+            byte[] originalMessage = new byte[10];
+            secureRandom.NextBytes(originalMessage);
+
+            BigInteger[] rs = signer.GenerateSignature(originalMessage);
+
+            signer.Init(false, pubSpec);
+
+            IsTrue("DSA test", signer.VerifySignature(originalMessage, rs[0], rs[1]));
+        }
+
+
+        public void TestECDSA_curvesFromSSHKeyGen()
+        {
+            var pairs = new Tuple<string,string> []{
+                Tuple.Create(
+                    "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBbxKE+/DXstQZmwH7Wso8SUt8LvYoMQpxN/7INC0lMn7mNCbxJcSOCfucBuWOrdoFyFZUkGli2mzKj3hJlcPiI=",
+                    "-----BEGIN OPENSSH PRIVATE KEY-----\n" +
+                    "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS\n" +
+                    "1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQQW8ShPvw17LUGZsB+1rKPElLfC72KD\n" +
+                    "EKcTf+yDQtJTJ+5jQm8SXEjgn7nAbljq3aBchWVJBpYtpsyo94SZXD4iAAAAuKFclDShXJ\n" +
+                    "Q0AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBbxKE+/DXstQZmw\n" +
+                    "H7Wso8SUt8LvYoMQpxN/7INC0lMn7mNCbxJcSOCfucBuWOrdoFyFZUkGli2mzKj3hJlcPi\n" +
+                    "IAAAAhAP4L/ciGBDF4HoQSvMaKM8svW4Ss0uYi7HkZ1sn/zCe0AAAAHW1lZ2Fud29vZHNA\n" +
+                    "dHljaGUtMzI2NS5nYXRld2F5AQI=\n" +
+                    "-----END OPENSSH PRIVATE KEY-----\n"
+                ),
+                Tuple.Create(
+                    "AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBOT0Cc/zauJsOWo/0P0sMNeyFI5Enz3+lKJtjWXQD7DpFgZmG5Ise8IXR5/ot7fo0kWlYQrye/uSmNmWBuDvOpBCHOnyR6Kaej36qoOO/gwbH+mezSYXSxCTA9Qb8VzxLA==",
+                    "-----BEGIN OPENSSH PRIVATE KEY-----\n" +
+                    "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNlY2RzYS\n" +
+                    "1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQTk9AnP82ribDlqP9D9LDDXshSORJ89\n" +
+                    "/pSibY1l0A+w6RYGZhuSLHvCF0ef6Le36NJFpWEK8nv7kpjZlgbg7zqQQhzp8keimno9+q\n" +
+                    "qDjv4MGx/pns0mF0sQkwPUG/Fc8SwAAADorZ3naK2d52gAAAATZWNkc2Etc2hhMi1uaXN0\n" +
+                    "cDM4NAAAAAhuaXN0cDM4NAAAAGEE5PQJz/Nq4mw5aj/Q/Sww17IUjkSfPf6Uom2NZdAPsO\n" +
+                    "kWBmYbkix7whdHn+i3t+jSRaVhCvJ7+5KY2ZYG4O86kEIc6fJHopp6Pfqqg47+DBsf6Z7N\n" +
+                    "JhdLEJMD1BvxXPEsAAAAMQDLno+rINnY7/Ht1WmSGZYJ3EMPtysbxuBnQFEL4USa3kyAb1\n" +
+                    "QMR6+jtqraKtE7kLwAAAAdbWVnYW53b29kc0B0eWNoZS0zMjY1LmdhdGV3YXkBAg==\n" +
+                    "-----END OPENSSH PRIVATE KEY-----\n"
+                ),
+                Tuple.Create(
+                    "AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBADXE/q1WSR002vRI+tiPLpdRjzeymSk+RjD7ZIC9CndqLmI0rhTMh5xReAzved12BH9lQJIGIw4YoIQDudsMbRUsQEjFvbFzSXLJBYWdZf8Voa/97/R9w/i8bKUMUPP0disypZlGdQn5+XvzHG6bhX2Qr9aJacGFZoVHugF/M8QyC+GyA==",
+                    "-----BEGIN OPENSSH PRIVATE KEY-----\n" +
+                    "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS\n" +
+                    "1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQA1xP6tVkkdNNr0SPrYjy6XUY83spk\n" +
+                    "pPkYw+2SAvQp3ai5iNK4UzIecUXgM73nddgR/ZUCSBiMOGKCEA7nbDG0VLEBIxb2xc0lyy\n" +
+                    "QWFnWX/FaGv/e/0fcP4vGylDFDz9HYrMqWZRnUJ+fl78xxum4V9kK/WiWnBhWaFR7oBfzP\n" +
+                    "EMgvhsgAAAEgs+rbdbPq23UAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQ\n" +
+                    "AAAIUEANcT+rVZJHTTa9Ej62I8ul1GPN7KZKT5GMPtkgL0Kd2ouYjSuFMyHnFF4DO953XY\n" +
+                    "Ef2VAkgYjDhighAO52wxtFSxASMW9sXNJcskFhZ1l/xWhr/3v9H3D+LxspQxQ8/R2KzKlm\n" +
+                    "UZ1Cfn5e/McbpuFfZCv1olpwYVmhUe6AX8zxDIL4bIAAAAQgCM8ojULpNk3UhBZhPfK+Tw\n" +
+                    "QjT9MHU0OTi4twvKPAE0vOLQ/C1g9AMlspyKxS2NKx2gxxXISowFGNL6Jkx9198ElQAAAB\n" +
+                    "1tZWdhbndvb2RzQHR5Y2hlLTMyNjUuZ2F0ZXdheQECAwQF\n" +
+                    "-----END OPENSSH PRIVATE KEY-----\n"
+                )
+            };
+
+            String[] ecPriv = new String[] { ecdsa256Key, ecdsa384Key, ecdsa521Key };
+            for (int i = 0; i != ecPriv.Length; i++)
+            {
+                ECPrivateKeyParameters privKey = (ECPrivateKeyParameters)OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(
+                    new PemReader(
+                        new StringReader(ecPriv[i])).ReadPemObject().Content);
+                var q = privKey.Parameters.G.Multiply(privKey.D);
+
+                DoECSigTest(new ECPublicKeyParameters(q, privKey.Parameters), privKey);
+            }
+
+
+            for (int i = 0; i != pairs.Length; i++)
+            {
+                var pair = pairs[i];
+
+                var pubSpec = OpenSSHPublicKeyUtil.ParsePublicKey(
+                    Base64.Decode(pair.Item1));
+
+                var privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(
+                    new PemReader(
+                        new StringReader(pair.Item2)).ReadPemObject().Content);
+
+                DoECSigTest(pubSpec, privSpec);
+
+                byte[] originalMessage;
+                BigInteger[] rs;
+
+                //
+                // Test encode
+                //
+                var recoveredPubKey = OpenSSHPublicKeyUtil.ParsePublicKey(OpenSSHPublicKeyUtil.EncodePublicKey((AsymmetricKeyParameter)pubSpec));
+                var recoveredPrivateKey = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(OpenSSHPrivateKeyUtil.EncodePrivateKey((AsymmetricKeyParameter)privSpec));
+
+                var signer = new ECDsaSigner();
+                signer.Init(true, privSpec);
+
+                originalMessage = new byte[10];
+                secureRandom.NextBytes(originalMessage);
+
+                rs = signer.GenerateSignature(originalMessage);
+
+                signer.Init(false, pubSpec);
+                IsTrue("ECDSA test post encoded / Decode", signer.VerifySignature(originalMessage, rs[0], rs[1]));
+            }
+
+        }
+
+        private void DoECSigTest(AsymmetricKeyParameter pubSpec, AsymmetricKeyParameter privSpec)
+        {
+            var signer = new ECDsaSigner();
+            signer.Init(true, privSpec);
+
+            byte[] originalMessage = new byte[10];
+            secureRandom.NextBytes(originalMessage);
+
+            BigInteger[] rs = signer.GenerateSignature(originalMessage);
+
+            signer.Init(false, pubSpec);
+
+            IsTrue("ECDSA test", signer.VerifySignature(originalMessage, rs[0], rs[1]));
+        }
+
+
+        public void TestECDSA()
+
+        {
+            var pubSpec = OpenSSHPublicKeyUtil.ParsePublicKey(Base64.Decode("AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHq5qxGqnh93Gpbj2w1Avx1UwBl6z5bZC3Viog1yNHDZYcV6Da4YQ3i0/hN7xY7sUy9dNF6g16tJSYXQQ4tvO3g="));
+
+            var privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(new PemReader(new StringReader("-----BEGIN EC PRIVATE KEY-----\n" +
+                "MHcCAQEEIHeg/+m02j6nr4bO8ubfbzhs0fqOjiuIoWbvGnVg+FmpoAoGCCqGSM49\n" +
+                "AwEHoUQDQgAEermrEaqeH3caluPbDUC/HVTAGXrPltkLdWKiDXI0cNlhxXoNrhhD\n" +
+                "eLT+E3vFjuxTL100XqDXq0lJhdBDi287eA==\n" +
+                "-----END EC PRIVATE KEY-----\n")).ReadPemObject().Content);
+
+            DoECSigTest(pubSpec, privSpec);
+
+        }
+
+
+        public void TestED25519()
+
+        {
+            var pubSpec = OpenSSHPublicKeyUtil.ParsePublicKey(Base64.Decode("AAAAC3NzaC1lZDI1NTE5AAAAIM4CaV7WQcy0lht0hclgXf4Olyvzvv2fnUvQ3J8IYsWF"));
+            var privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(new PemReader(new StringReader("-----BEGIN OPENSSH PRIVATE KEY-----\n" +
+                "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n" +
+                "QyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQAAAKBTr4PvU6+D\n" +
+                "7wAAAAtzc2gtZWQyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQ\n" +
+                "AAAED4BTHeR3YD7CFQqusztfL5K+YSD4mRGLBwb7jHiXxIJM4CaV7WQcy0lht0hclgXf4O\n" +
+                "lyvzvv2fnUvQ3J8IYsWFAAAAG21lZ2Fud29vZHNAdHljaGUtMzI2NS5sb2NhbAEC\n" +
+                "-----END OPENSSH PRIVATE KEY-----\n")).ReadPemObject().Content);
+
+            Ed25519Signer signer = new Ed25519Signer();
+            signer.Init(true, privSpec);
+
+            byte[] originalMessage = new byte[10];
+            secureRandom.NextBytes(originalMessage);
+            signer.BlockUpdate(originalMessage, 0, originalMessage.Length);
+
+            byte[] sig = signer.GenerateSignature();
+
+            signer.Init(false, pubSpec);
+
+            signer.BlockUpdate(originalMessage, 0, originalMessage.Length);
+
+            IsTrue("ED25519Signer test", signer.VerifySignature(sig));
+
+        }
+
+        public void TestFailures()
+        {
+            byte[]
+            blob = new PemReader(new StringReader("-----BEGIN OPENSSH PRIVATE KEY-----\n" +
+                    "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n" +
+                    "QyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQAAAKBTr4PvU6+D\n" +
+                    "7wAAAAtzc2gtZWQyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQ\n" +
+                    "AAAED4BTHeR3YD7CFQqusztfL5K+YSD4mRGLBwb7jHiXxIJM4CaV7WQcy0lht0hclgXf4O\n" +
+                    "lyvzvv2fnUvQ3J8IYsWFAAAAG21lZ2Fud29vZHNAdHljaGUtMzI2NS5sb2NhbAEC\n" +
+                    "-----END OPENSSH PRIVATE KEY-----\n")).ReadPemObject().Content;
+
+            //
+            // Altering the check value.
+            //
+            blob[98] ^= 1;
+
+            try
+            {
+                var privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(blob);
+                Fail("Change should trigger failure.");
+            }
+            catch (InvalidOperationException iles)
+            {
+                IsEquals("Check value mismatch ", iles.Message, "private key check values are not the same");
+            }
+
+            //
+            // Altering the cipher name.
+            //
+
+
+            blob = new PemReader(new StringReader("-----BEGIN OPENSSH PRIVATE KEY-----\n" +
+                "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n" +
+                "QyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQAAAKBTr4PvU6+D\n" +
+                "7wAAAAtzc2gtZWQyNTUxOQAAACDOAmle1kHMtJYbdIXJYF3+Dpcr8779n51L0NyfCGLFhQ\n" +
+                "AAAED4BTHeR3YD7CFQqusztfL5K+YSD4mRGLBwb7jHiXxIJM4CaV7WQcy0lht0hclgXf4O\n" +
+                "lyvzvv2fnUvQ3J8IYsWFAAAAG21lZ2Fud29vZHNAdHljaGUtMzI2NS5sb2NhbAEC\n" +
+                "-----END OPENSSH PRIVATE KEY-----\n")).ReadPemObject().Content;
+            blob[19] = (byte)'C';
+
+            try
+            {
+                var privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(blob);
+                Fail("Change should trigger failure.");
+            }
+            catch (InvalidOperationException iles)
+            {
+                IsEquals("enc keys not supported ", iles.Message, "encrypted keys not supported");
+            }
+        }
+
+        public override string Name
+        {
+            get { return "OpenSSHTest"; }
+        }
+
+        public override void PerformTest()
+        {
+            TestECDSA_curvesFromSSHKeyGen();
+            TestDSA();
+            TestECDSA();
+            TestRSA();
+            TestED25519();
+            TestFailures();
+        }
+
+        public void TestRSA()
+        {
+            var pubSpec = OpenSSHPublicKeyUtil.ParsePublicKey(Base64.Decode("AAAAB3NzaC1yc2EAAAADAQABAAAAgQDvh2BophdIp8ojwGZQR0FQ/awowXnV24nAPm+/na8MOUrdySNhOnlek4LAZl82/+Eu2t21XD6hQUiHKAj6XaNFBthTuss7Cz/tA348DLEMHD9wUtT0FXVmsxqN4BfusunbcULxxVWG2z8FvqeaGgc/Unkp9y7/kyf54pPUCBcClw=="));
+
+            var privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(new PemReader(new StringReader("-----BEGIN RSA PRIVATE KEY-----\n" +
+                "MIICXgIBAAKBgQDvh2BophdIp8ojwGZQR0FQ/awowXnV24nAPm+/na8MOUrdySNh\n" +
+                "Onlek4LAZl82/+Eu2t21XD6hQUiHKAj6XaNFBthTuss7Cz/tA348DLEMHD9wUtT0\n" +
+                "FXVmsxqN4BfusunbcULxxVWG2z8FvqeaGgc/Unkp9y7/kyf54pPUCBcClwIDAQAB\n" +
+                "AoGBAOMXYEoXHgAeREE9CkOWKtDUkEJbnF0rNSB0kZIDt5BJSTeYmNh3jdYi2FX9\n" +
+                "OMx2MFIx4v0tJZvQvyiUxl5IJJ9ZJsYUWF+6VbcTVwYYfdVzZzP2TNyGmF9/ADZW\n" +
+                "wBehqP04uRlYjt94kqb4HoOKF3gJ3LC4uW9xcEltTBeHWCfhAkEA/2biF5St9/Ya\n" +
+                "540E4zu/FKPsxLSaT8LWCo9+X7IqIzlBQCB4GjM+nZeTm7eZOkfAFZoxwfiNde/9\n" +
+                "qleXXf6B2QJBAPAW+jDBC3QF4/g8n9cDxm/A3ICmcOFSychLSrydk9ZyRPbTRyQC\n" +
+                "YlC2mf/pCrO/yO7h189BXyQ3PXOEhnujce8CQQD7gDy0K90EiH0F94AQpA0OLj5B\n" +
+                "lfc/BAXycEtpwPBtrzvqAg9C/aNzXIgmly10jqNAoo7NDA2BTcrlq0uLa8xBAkBl\n" +
+                "7Hs+I1XnZXDIO4Rn1VRysN9rRj15ipnbDAuoUwUl7tDUMBFteg2e0kZCW/6NHIgC\n" +
+                "0aG6fLgVOdY+qi4lYtfFAkEAqqiBgEgSrDmnJLTm6j/Pv1mBA6b9bJbjOqomrDtr\n" +
+                "AWTXe+/kSCv/jYYdpNA/tDgAwEmtkWWEie6+SwJB5cXXqg==\n" +
+                "-----END RSA PRIVATE KEY-----\n")).ReadPemObject().Content);
+
+            DoRSATest(pubSpec, privSpec);
+
+            privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(new PemReader(new StringReader(rsa1024Key)).ReadPemObject().Content);
+            pubSpec = new RsaKeyParameters(false, ((RsaKeyParameters)privSpec).Modulus, ((RsaPrivateCrtKeyParameters)privSpec).PublicExponent);
+
+            DoRSATest(pubSpec, privSpec);
+
+            privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(new PemReader(new StringReader(rsa2048Key)).ReadPemObject().Content);
+            pubSpec = new RsaKeyParameters(false, ((RsaKeyParameters)privSpec).Modulus, ((RsaPrivateCrtKeyParameters)privSpec).PublicExponent);
+
+            DoRSATest(pubSpec, privSpec);
+
+            privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(new PemReader(new StringReader(rsa3072Key)).ReadPemObject().Content);
+            pubSpec = new RsaKeyParameters(false, ((RsaKeyParameters)privSpec).Modulus, ((RsaPrivateCrtKeyParameters)privSpec).PublicExponent);
+
+            DoRSATest(pubSpec, privSpec);
+
+            privSpec = OpenSSHPrivateKeyUtil.ParsePrivateKeyBlob(new PemReader(new StringReader(rsa4096Key)).ReadPemObject().Content);
+            pubSpec = new RsaKeyParameters(false, ((RsaKeyParameters)privSpec).Modulus, ((RsaPrivateCrtKeyParameters)privSpec).PublicExponent);
+
+            DoRSATest(pubSpec, privSpec);
+        }
+
+        private void DoRSATest(AsymmetricKeyParameter pubSpec, AsymmetricKeyParameter privSpec)
+        {
+            byte[] originalMessage = new byte[10];
+            secureRandom.NextBytes(originalMessage);
+
+            originalMessage[0] |= 1;
+
+            var rsaEngine = new RsaEngine();
+            rsaEngine.Init(true, privSpec);
+
+            byte[] ct = rsaEngine.ProcessBlock(originalMessage, 0, originalMessage.Length);
+
+            rsaEngine.Init(false, pubSpec);
+            byte[] result = rsaEngine.ProcessBlock(ct, 0, ct.Length);
+
+            IsTrue("Result did not match original message", Enumerable.SequenceEqual(originalMessage, result));
+        }
+
+        [Test]
+        public void TestFunction()
+        {
+            string resultText = Perform().ToString();
+
+            Assert.AreEqual(Name + ": Okay", resultText);
+        }
+    }
+}