Add support for post-quantum key exchange methods sntrup761x25519-sha512 and mlkem768x25519-sha256
Add support for PuTTY private key files
Add logging capability via Microsoft.Extensions.Logging
Breaking changes
Support for DSA was dropped in #1558
CipherPadding was deleted in #1546 and uses replaced with Org.BouncyCastle.Crypto.Paddings.IBlockCipherPadding
See the full API diff at the end
What's Changed
Bump version by @Rob-Hague in https://github.com/sshnet/SSH.NET/pull/1533
fix Package Downgrade Warning with .NET 9 SDK by @mus65 in https://github.com/sshnet/SSH.NET/pull/1538
Update PrivateKeyFile.cs by @scott-xu in https://github.com/sshnet/SSH.NET/pull/1541
Handle lower-case hex in private key's salt field by @RiJo in https://github.com/sshnet/SSH.NET/pull/179
Split PrivateKeyFile into different implementations. by @scott-xu in https://github.com/sshnet/SSH.NET/pull/1542
Migrate from AppVeyor to GitHub Actions by @mus65 in https://github.com/sshnet/SSH.NET/pull/1539
Add .NET 9 target by @mus65 in https://github.com/sshnet/SSH.NET/pull/1480
Remove appveyor.yml by @mus65 in https://github.com/sshnet/SSH.NET/pull/1547
Replace DiagnosticAbstration with Microsoft.Extensions.Logging.Abstractions by @mus65 in https://github.com/sshnet/SSH.NET/pull/1509
[Private Key] Add support for PuTTY private key file format (V3 and V2) by @scott-xu in https://github.com/sshnet/SSH.NET/pull/1543
fix newline characters in comment by @Varorbc in https://github.com/sshnet/SSH.NET/pull/1550
Pin Alpine Linux image to 3.20 by @Rob-Hague in https://github.com/sshnet/SSH.NET/pull/1554
Fix consumption of NBGV version properties in build by @Rob-Hague in https://github.com/sshnet/SSH.NET/pull/1544
Drop net7.0 target by @Rob-Hague in https://github.com/sshnet/SSH.NET/pull/1468
Add padding when encrypt and remove padding when decrypt by @scott-xu in https://github.com/sshnet/SSH.NET/pull/1545
Use System.Security.Cryptography for TripleDesCipher by @scott-xu in https://github.com/sshnet/SSH.NET/pull/1546
[PQC] Add support for sntrup761x25519-sha512 key exchange method by @scott-xu in https://github.com/sshnet/SSH.NET/pull/1562
Drop DSA by @Rob-Hague in https://github.com/sshnet/SSH.NET/pull/1558
Create dependabot.yml for docker image by @Rob-Hague in https://github.com/sshnet/SSH.NET/pull/1559
Fix hang/unhandled exception in SshCommand upon disconnect by @Rob-Hague in https://github.com/sshnet/SSH.NET/pull/1565
Bump alpine from 3.20 to 3.21 in /test/Renci.SshNet.IntegrationTests by @dependabot in https://github.com/sshnet/SSH.NET/pull/1567
[PQC] Add support for mlkem768x25519-sha256 key exchange method by @scott-xu in https://github.com/sshnet/SSH.NET/pull/1563
Move IDisposable implementation declaration from inheritees to parent by @bad-samaritan in https://github.com/sshnet/SSH.NET/pull/746
Update bound port on ForwardedPortDynamic after connection (in case original was passed as zero) by @timyhac in https://github.com/sshnet/SSH.NET/pull/1577
Configure Dependabot to also update NuGet and GitHub Actions by @mus65 in https://github.com/sshnet/SSH.NET/pull/1581
Bump Microsoft.NET.Test.Sdk from 17.11.1 to 17.12.0 by @dependabot in https://github.com/sshnet/SSH.NET/pull/1585
Bump actions/configure-pages from 4 to 5 by @dependabot in https://github.com/sshnet/SSH.NET/pull/1582
fix: UnhandledException: System.ObjectDisposedException. by @SoftStoneDevelop in https://github.com/sshnet/SSH.NET/pull/1590
Bump test dependencies by @dependabot in https://github.com/sshnet/SSH.NET/pull/1583
Tweak logging.md by @Rob-Hague in https://github.com/sshnet/SSH.NET/pull/1601
Reply to global requests when want_reply is true by @Rob-Hague in https://github.com/sshnet/SSH.NET/pull/1600
Don't dispose channel when completing SshCommand by @Rob-Hague in https://github.com/sshnet/SSH.NET/pull/1596
Drop net6.0 target by @mus65 in https://github.com/sshnet/SSH.NET/pull/1580
remove Reverse extension to avoid source-breaking change with .NET 10 by @mus65 in https://github.com/sshnet/SSH.NET/pull/1606
Bump the dependencies group with 7 updates by @dependabot in https://github.com/sshnet/SSH.NET/pull/1607
CI: run .NET Framework Integration Tests on Windows by @mus65 in https://github.com/sshnet/SSH.NET/pull/1615
Fix API break on KeyExchange by @Rob-Hague in https://github.com/sshnet/SSH.NET/pull/1609
Add a Stream buffer validation helper by @Rob-Hague in https://github.com/sshnet/SSH.NET/pull/1605
Add an OrderedDictionary implementation for algorithm priorities by @Rob-Hague in https://github.com/sshnet/SSH.NET/pull/1611
lock SendData to fix random connection failures by @mus65 in https://github.com/sshnet/SSH.NET/pull/1623
Only enable TreatWarningsAsErrors in Release by @mus65 in https://github.com/sshnet/SSH.NET/pull/1624
Remove unused bcrypt password hashing code by @mus65 in https://github.com/sshnet/SSH.NET/pull/1626
Bump the dependencies group with 5 updates by @dependabot in https://github.com/sshnet/SSH.NET/pull/1625
Added ExistsAsync and GetAsync to ISftpClient by @Maarty in https://github.com/sshnet/SSH.NET/pull/1628
New Contributors
@RiJo made their first contribution in https://github.com/sshnet/SSH.NET/pull/179
@Varorbc made their first contribution in https://github.com/sshnet/SSH.NET/pull/1550
@dependabot made their first contribution in https://github.com/sshnet/SSH.NET/pull/1567
@bad-samaritan made their first contribution in https://github.com/sshnet/SSH.NET/pull/746
@timyhac made their first contribution in https://github.com/sshnet/SSH.NET/pull/1577
@SoftStoneDevelop made their first contribution in https://github.com/sshnet/SSH.NET/pull/1590
@Maarty made their first contribution in https://github.com/sshnet/SSH.NET/pull/1628
Full Changelog: https://github.com/sshnet/SSH.NET/compare/2024.2.0...2025.0.0
API diff
namespace Renci.SshNet
{
public abstract class AuthenticationMethod
{
+ public void Dispose();
+ protected virtual void Dispose(bool disposing);
}
public class ConnectionInfo
{
- public System.Collections.Generic.IDictionary<string, System.Func<Renci.SshNet.Compression.Compressor>> CompressionAlgorithms { get; }
+ public Renci.SshNet.IOrderedDictionary<string, System.Func<Renci.SshNet.Compression.Compressor>> CompressionAlgorithms { get; }
- public System.Collections.Generic.IDictionary<string, Renci.SshNet.CipherInfo> Encryptions { get; }
+ public Renci.SshNet.IOrderedDictionary<string, Renci.SshNet.CipherInfo> Encryptions { get; }
- public System.Collections.Generic.IDictionary<string, Renci.SshNet.HashInfo> HmacAlgorithms { get; }
+ public Renci.SshNet.IOrderedDictionary<string, Renci.SshNet.HashInfo> HmacAlgorithms { get; }
- public System.Collections.Generic.IDictionary<string, System.Func<byte[], Renci.SshNet.Security.KeyHostAlgorithm>> HostKeyAlgorithms { get; }
+ public Renci.SshNet.IOrderedDictionary<string, System.Func<byte[], Renci.SshNet.Security.KeyHostAlgorithm>> HostKeyAlgorithms { get; }
- public System.Collections.Generic.IDictionary<string, System.Func<Renci.SshNet.Security.IKeyExchange>> KeyExchangeAlgorithms { get; }
+ public Renci.SshNet.IOrderedDictionary<string, System.Func<Renci.SshNet.Security.IKeyExchange>> KeyExchangeAlgorithms { get; }
}
public interface ISftpClient : Renci.SshNet.IBaseClient
{
+ System.Threading.Tasks.Task<bool> ExistsAsync(string path, System.Threading.CancellationToken cancellationToken = null);
+ System.Threading.Tasks.Task<Renci.SshNet.Sftp.ISftpFile> GetAsync(string path, System.Threading.CancellationToken cancellationToken);
}
public class KeyboardInteractiveAuthenticationMethod : Renci.SshNet.AuthenticationMethod
{
- public void Dispose();
- protected virtual void Dispose(bool disposing);
+ protected override void Dispose(bool disposing);
}
public class NoneAuthenticationMethod : Renci.SshNet.AuthenticationMethod
{
- public void Dispose();
- protected virtual void Dispose(bool disposing);
+ protected override void Dispose(bool disposing);
}
public class PasswordAuthenticationMethod : Renci.SshNet.AuthenticationMethod
{
- public void Dispose();
- protected virtual void Dispose(bool disposing);
+ protected override void Dispose(bool disposing);
}
public class PrivateKeyAuthenticationMethod : Renci.SshNet.AuthenticationMethod
{
- public void Dispose();
- protected virtual void Dispose(bool disposing);
+ protected override void Dispose(bool disposing);
}
+ public interface IOrderedDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue>
+ {
+ bool ContainsKey(TKey key);
+ bool ContainsValue(TValue value);
+ System.Collections.Generic.KeyValuePair<TKey, TValue> GetAt(int index);
+ int IndexOf(TKey key);
+ void Insert(int index, TKey key, TValue value);
+ bool Remove(TKey key, out TValue value);
+ void RemoveAt(int index);
+ void SetAt(int index, TKey key, TValue value);
+ void SetAt(int index, TValue value);
+ void SetPosition(TKey key, int newIndex);
+ void SetPosition(int index, int newIndex);
+ bool TryAdd(TKey key, TValue value, out int index);
+ bool TryAdd(TKey key, TValue value);
+ bool TryGetValue(TKey key, out TValue value, out int index);
+ bool TryGetValue(TKey key, out TValue value);
+ int Count { get; }
+ TValue this[TKey key] { get; set; }
+ System.Collections.Generic.ICollection<TKey> Keys { get; }
+ System.Collections.Generic.ICollection<TValue> Values { get; }
+ }
+ public static class SshNetLoggingConfiguration
+ {
+ public static void InitializeLogging(Microsoft.Extensions.Logging.ILoggerFactory loggerFactory);
+ }
}
namespace Renci.SshNet.Messages.Transport
{
+ public class KeyExchangeHybridReplyMessage : Renci.SshNet.Messages.Message
+ {
+ public KeyExchangeHybridReplyMessage();
+ protected override void LoadData();
+ protected override void SaveData();
+ protected override int BufferCapacity { get; }
+ public byte[] KS { get; }
+ public override string MessageName { get; }
+ public override byte MessageNumber { get; }
+ public byte[] Signature { get; }
+ public byte[] SReply { get; }
+ }
}
namespace Renci.SshNet.Security
{
- public class DsaKey : Renci.SshNet.Security.Key
- {
- public DsaKey(Renci.SshNet.Security.SshKeyData publicKeyData);
- public DsaKey(byte[] privateKeyData);
- public DsaKey(System.Numerics.BigInteger p, System.Numerics.BigInteger q, System.Numerics.BigInteger g, System.Numerics.BigInteger y, System.Numerics.BigInteger x);
- public void Dispose();
- protected virtual void Dispose(bool disposing);
- protected internal override Renci.SshNet.Security.Cryptography.DigitalSignature DigitalSignature { get; }
- public System.Numerics.BigInteger G { get; }
- public override int KeyLength { get; }
- public System.Numerics.BigInteger P { get; }
- public override System.Numerics.BigInteger[] Public { get; }
- public System.Numerics.BigInteger Q { get; }
- public System.Numerics.BigInteger X { get; }
- public System.Numerics.BigInteger Y { get; }
- }
}
namespace Renci.SshNet.Security.Cryptography
{
public abstract class BlockCipher : Renci.SshNet.Security.Cryptography.SymmetricCipher
{
- protected BlockCipher(byte[] key, byte blockSize, Renci.SshNet.Security.Cryptography.Ciphers.CipherMode mode, Renci.SshNet.Security.Cryptography.Ciphers.CipherPadding padding)
+ protected BlockCipher(byte[] key, byte blockSize, Renci.SshNet.Security.Cryptography.Ciphers.CipherMode mode, Org.BouncyCastle.Crypto.Paddings.IBlockCipherPadding padding)
}
- public class DsaDigitalSignature : Renci.SshNet.Security.Cryptography.DigitalSignature
- {
- public DsaDigitalSignature(Renci.SshNet.Security.DsaKey key);
- public void Dispose();
- protected virtual void Dispose(bool disposing);
- public override byte[] Sign(byte[] input);
- public override bool Verify(byte[] input, byte[] signature);
- }
}
namespace Renci.SshNet.Security.Cryptography.Ciphers
{
public sealed class AesCipher : Renci.SshNet.Security.Cryptography.BlockCipher
{
- public void Dispose(bool disposing);
}
- public abstract class CipherPadding
- {
- protected CipherPadding();
- public abstract byte[] Pad(byte[] input, int offset, int length, int paddinglength);
- public byte[] Pad(byte[] input, int paddinglength);
- public abstract byte[] Pad(int blockSize, byte[] input, int offset, int length);
- public byte[] Pad(int blockSize, byte[] input);
- }
- public class DesCipher : Renci.SshNet.Security.Cryptography.BlockCipher
- {
- public DesCipher(byte[] key, Renci.SshNet.Security.Cryptography.Ciphers.CipherMode mode, Renci.SshNet.Security.Cryptography.Ciphers.CipherPadding padding) : base(default(byte[])!, default(byte)!, default(Renci.SshNet.Security.Cryptography.Ciphers.CipherMode)!, default(Renci.SshNet.Security.Cryptography.Ciphers.CipherPadding)!);
- public override int DecryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset);
- protected static void DesFunc(int[] wKey, byte[] input, int inOff, byte[] outBytes, int outOff);
- public override int EncryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset);
- protected int[] GenerateWorkingKey(bool encrypting, byte[] key);
- protected virtual void ValidateKey();
- }
public sealed class TripleDesCipher : Renci.SshNet.Security.Cryptography.BlockCipher
{
- public TripleDesCipher(byte[] key, Renci.SshNet.Security.Cryptography.Ciphers.CipherMode mode, Renci.SshNet.Security.Cryptography.Ciphers.CipherPadding padding);
- protected override void ValidateKey();
+ public TripleDesCipher(byte[] key, byte[] iv, System.Security.Cryptography.CipherMode mode, bool pkcs7Padding);
+ public override byte[] Decrypt(byte[] input, int offset, int length);
+ public void Dispose();
+ public override byte[] Encrypt(byte[] input, int offset, int length);
}
}
- namespace Renci.SshNet.Security.Cryptography.Ciphers.Paddings
- {
- public class PKCS7Padding : Renci.SshNet.Security.Cryptography.Ciphers.CipherPadding
- {
- public PKCS7Padding();
- public override byte[] Pad(byte[] input, int offset, int length, int paddinglength);
- public override byte[] Pad(int blockSize, byte[] input, int offset, int length);
- }
- }