<PackageReference Include="SSH.NET" Version="2025.0.0" />

EcdsaKey

public class EcdsaKey : Key, IDisposable
Contains ECDSA (ecdsa-sha2-nistp{256,384,521}) private and public key.
using Renci.SshNet.Common; using Renci.SshNet.Security.Cryptography; using System; using System.Formats.Asn1; using System.Numerics; using System.Runtime.CompilerServices; using System.Security.Cryptography; using System.Text; namespace Renci.SshNet.Security { [NullableContext(1)] [Nullable(0)] public class EcdsaKey : Key, IDisposable { [Nullable(0)] private sealed class BclImpl : Impl { private readonly HashAlgorithmName _hashAlgorithmName; [Nullable(2)] [field: Nullable(2)] public override byte[] PrivateKey { [NullableContext(2)] get; } public override ECDsa Ecdsa { get; } public override int KeyLength => Ecdsa.KeySize; public BclImpl(string curve_oid, int cord_size, byte[] qx, byte[] qy, [Nullable(2)] byte[] privatekey) { ECCurve curve = ECCurve.CreateFromValue(curve_oid); ECParameters eCParameters = new ECParameters { Curve = curve }; eCParameters.Q.X = qx; eCParameters.Q.Y = qy; if (privatekey != null) { eCParameters.D = privatekey.TrimLeadingZeros().Pad(cord_size); PrivateKey = eCParameters.D; } Ecdsa = ECDsa.Create(eCParameters); int keyLength = KeyLength; HashAlgorithmName hashAlgorithmName = _hashAlgorithmName = ((keyLength <= 256) ? HashAlgorithmName.SHA256 : ((keyLength > 384) ? HashAlgorithmName.SHA512 : HashAlgorithmName.SHA384)); } public override byte[] Sign(byte[] input) { return Ecdsa.SignData(input, _hashAlgorithmName); } public override bool Verify(byte[] input, byte[] signature) { return Ecdsa.VerifyData(input, signature, _hashAlgorithmName); } public override void Export(out byte[] qx, out byte[] qy) { ECParameters eCParameters = Ecdsa.ExportParameters(false); qx = eCParameters.Q.X; qy = eCParameters.Q.Y; } protected override void Dispose(bool disposing) { if (disposing) Ecdsa.Dispose(); } } [Nullable(0)] internal abstract class Impl : IDisposable { public abstract int KeyLength { get; } [Nullable(2)] public abstract byte[] PrivateKey { [NullableContext(2)] get; } public abstract ECDsa Ecdsa { get; } public abstract bool Verify(byte[] input, byte[] signature); public abstract byte[] Sign(byte[] input); public abstract void Export(out byte[] qx, out byte[] qy); protected abstract void Dispose(bool disposing); public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } } private const string ECDSA_P256_OID_VALUE = "1.2.840.10045.3.1.7"; private const string ECDSA_P384_OID_VALUE = "1.3.132.0.34"; private const string ECDSA_P521_OID_VALUE = "1.3.132.0.35"; private static readonly BigInteger Encoded256 = new ReadOnlySpan<byte>(&global::<PrivateImplementationDetails>.E4FB69AD864E06534C35D5133C4923F03E856CB1E35FDC1CE9AAD2B5B2FE2C3D, 8).ToBigInteger(); private static readonly BigInteger Encoded384 = new ReadOnlySpan<byte>(&global::<PrivateImplementationDetails>.5A07D3258D64CC99968850E8882D7274BF6A63FDD7E1F1F9825B7F264A829DBC, 8).ToBigInteger(); private static readonly BigInteger Encoded521 = new ReadOnlySpan<byte>(&global::<PrivateImplementationDetails>.587D06A68D49DC8EAE2FAACD5E744F37EF2ADC222B106B77BC7AB67B66F30409, 8).ToBigInteger(); [Nullable(2)] private EcdsaDigitalSignature _digitalSignature; internal readonly Impl _impl; public HashAlgorithmName HashAlgorithm { get { switch (KeyLength) { case 256: return HashAlgorithmName.SHA256; case 384: return HashAlgorithmName.SHA384; case 521: return HashAlgorithmName.SHA512; default: return HashAlgorithmName.SHA256; } } } public override int KeyLength => _impl.KeyLength; protected internal override DigitalSignature DigitalSignature { get { if (_digitalSignature == null) _digitalSignature = new EcdsaDigitalSignature(this); return _digitalSignature; } } public override BigInteger[] Public { get { BigInteger bigInteger; switch (KeyLength) { case 256: bigInteger = Encoded256; break; case 384: bigInteger = Encoded384; break; default: bigInteger = Encoded521; break; } _impl.Export(out byte[] qx, out byte[] qy); byte[] array = new byte[1 + qx.Length + qy.Length]; array[0] = 4; Buffer.BlockCopy(qx, 0, array, 1, qx.Length); Buffer.BlockCopy(qy, 0, array, qx.Length + 1, qy.Length); return new BigInteger[2] { bigInteger, new BigInteger(array, false, true) }; } } [Nullable(2)] public byte[] PrivateKey { [NullableContext(2)] get { return _impl.PrivateKey; } } public ECDsa Ecdsa => _impl.Ecdsa; public override string ToString() { return $"""{KeyLength}"; } public EcdsaKey(SshKeyData publicKeyData) { ThrowHelper.ThrowIfNull(publicKeyData, "publicKeyData"); if (!publicKeyData.Name.StartsWith("ecdsa-sha2-", StringComparison.Ordinal) || publicKeyData.Keys.Length != 2) { DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(36, 2); defaultInterpolatedStringHandler.AppendLiteral("Invalid ECDSA public key data. ("); defaultInterpolatedStringHandler.AppendFormatted(publicKeyData.Name); defaultInterpolatedStringHandler.AppendLiteral(", "); defaultInterpolatedStringHandler.AppendFormatted(publicKeyData.Keys.Length); defaultInterpolatedStringHandler.AppendLiteral(")."); throw new ArgumentException(defaultInterpolatedStringHandler.ToStringAndClear(), "publicKeyData"); } string curveOid = GetCurveOid(Encoding.ASCII.GetString(publicKeyData.Keys[0].ToByteArray(false, true))); byte[] publickey = publicKeyData.Keys[1].ToByteArray(false, true); _impl = Import(curveOid, publickey, null); } public EcdsaKey(string curve, byte[] publickey, byte[] privatekey) { _impl = Import(GetCurveOid(curve), publickey, privatekey); } public EcdsaKey(byte[] data) { AsnReader asnReader = new AsnReader(data, AsnEncodingRules.DER, default(AsnReaderOptions)); AsnReader asnReader2 = asnReader.ReadSequence(null); asnReader.ThrowIfNotEmpty(); asnReader2.ReadInteger(null); byte[] privatekey = asnReader2.ReadOctetString(null).TrimLeadingZeros(); string curve_oid = asnReader2.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 0, true)).ReadObjectIdentifier(null); int unusedBitCount = default(int); byte[] publickey = asnReader2.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 1, true)).ReadBitString(out unusedBitCount, null); asnReader2.ThrowIfNotEmpty(); _impl = Import(curve_oid, publickey, privatekey); } private static Impl Import(string curve_oid, byte[] publickey, [Nullable(2)] byte[] privatekey) { int num = (publickey.Length - 1) / 2; byte[] array = new byte[num]; Buffer.BlockCopy(publickey, 1, array, 0, array.Length); byte[] array2 = new byte[num]; Buffer.BlockCopy(publickey, num + 1, array2, 0, array2.Length); return new BclImpl(curve_oid, num, array, array2, privatekey); } private static string GetCurveOid(string curve) { if (string.Equals(curve, "nistp256", StringComparison.OrdinalIgnoreCase) || string.Equals(curve, "1.2.840.10045.3.1.7")) return "1.2.840.10045.3.1.7"; if (string.Equals(curve, "nistp384", StringComparison.OrdinalIgnoreCase) || string.Equals(curve, "1.3.132.0.34")) return "1.3.132.0.34"; if (string.Equals(curve, "nistp521", StringComparison.OrdinalIgnoreCase) || string.Equals(curve, "1.3.132.0.35")) return "1.3.132.0.35"; throw new SshException("Unexpected Curve: " + curve); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { _digitalSignature?.Dispose(); _impl.Dispose(); } } } }