diff --git a/.gitignore b/.gitignore
index 7d6bdbf60..37793444d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,6 @@ project.lock.json
# Build outputs
build/target/
+
+# Benchmark results
+BenchmarkDotNet.Artifacts/
diff --git a/src/Renci.SshNet.Tests/Data/Key.ECDSA.Encrypted.txt b/src/Data/Key.ECDSA.Encrypted.txt
similarity index 100%
rename from src/Renci.SshNet.Tests/Data/Key.ECDSA.Encrypted.txt
rename to src/Data/Key.ECDSA.Encrypted.txt
diff --git a/src/Renci.SshNet.Tests/Data/Key.ECDSA.txt b/src/Data/Key.ECDSA.txt
similarity index 100%
rename from src/Renci.SshNet.Tests/Data/Key.ECDSA.txt
rename to src/Data/Key.ECDSA.txt
diff --git a/src/Renci.SshNet.Tests/Data/Key.ECDSA384.Encrypted.txt b/src/Data/Key.ECDSA384.Encrypted.txt
similarity index 100%
rename from src/Renci.SshNet.Tests/Data/Key.ECDSA384.Encrypted.txt
rename to src/Data/Key.ECDSA384.Encrypted.txt
diff --git a/src/Renci.SshNet.Tests/Data/Key.ECDSA384.txt b/src/Data/Key.ECDSA384.txt
similarity index 100%
rename from src/Renci.SshNet.Tests/Data/Key.ECDSA384.txt
rename to src/Data/Key.ECDSA384.txt
diff --git a/src/Renci.SshNet.Tests/Data/Key.ECDSA521.Encrypted.txt b/src/Data/Key.ECDSA521.Encrypted.txt
similarity index 100%
rename from src/Renci.SshNet.Tests/Data/Key.ECDSA521.Encrypted.txt
rename to src/Data/Key.ECDSA521.Encrypted.txt
diff --git a/src/Renci.SshNet.Tests/Data/Key.ECDSA521.txt b/src/Data/Key.ECDSA521.txt
similarity index 100%
rename from src/Renci.SshNet.Tests/Data/Key.ECDSA521.txt
rename to src/Data/Key.ECDSA521.txt
diff --git a/src/Renci.SshNet.Tests/Data/Key.OPENSSH.ECDSA.Encrypted.txt b/src/Data/Key.OPENSSH.ECDSA.Encrypted.txt
similarity index 100%
rename from src/Renci.SshNet.Tests/Data/Key.OPENSSH.ECDSA.Encrypted.txt
rename to src/Data/Key.OPENSSH.ECDSA.Encrypted.txt
diff --git a/src/Renci.SshNet.Tests/Data/Key.OPENSSH.ECDSA.txt b/src/Data/Key.OPENSSH.ECDSA.txt
similarity index 100%
rename from src/Renci.SshNet.Tests/Data/Key.OPENSSH.ECDSA.txt
rename to src/Data/Key.OPENSSH.ECDSA.txt
diff --git a/src/Renci.SshNet.Tests/Data/Key.OPENSSH.ECDSA384.Encrypted.txt b/src/Data/Key.OPENSSH.ECDSA384.Encrypted.txt
similarity index 100%
rename from src/Renci.SshNet.Tests/Data/Key.OPENSSH.ECDSA384.Encrypted.txt
rename to src/Data/Key.OPENSSH.ECDSA384.Encrypted.txt
diff --git a/src/Renci.SshNet.Tests/Data/Key.OPENSSH.ECDSA384.txt b/src/Data/Key.OPENSSH.ECDSA384.txt
similarity index 100%
rename from src/Renci.SshNet.Tests/Data/Key.OPENSSH.ECDSA384.txt
rename to src/Data/Key.OPENSSH.ECDSA384.txt
diff --git a/src/Renci.SshNet.Tests/Data/Key.OPENSSH.ECDSA521.Encrypted.txt b/src/Data/Key.OPENSSH.ECDSA521.Encrypted.txt
similarity index 100%
rename from src/Renci.SshNet.Tests/Data/Key.OPENSSH.ECDSA521.Encrypted.txt
rename to src/Data/Key.OPENSSH.ECDSA521.Encrypted.txt
diff --git a/src/Renci.SshNet.Tests/Data/Key.OPENSSH.ECDSA521.txt b/src/Data/Key.OPENSSH.ECDSA521.txt
similarity index 100%
rename from src/Renci.SshNet.Tests/Data/Key.OPENSSH.ECDSA521.txt
rename to src/Data/Key.OPENSSH.ECDSA521.txt
diff --git a/src/Renci.SshNet.Tests/Data/Key.OPENSSH.ED25519.Encrypted.txt b/src/Data/Key.OPENSSH.ED25519.Encrypted.txt
similarity index 100%
rename from src/Renci.SshNet.Tests/Data/Key.OPENSSH.ED25519.Encrypted.txt
rename to src/Data/Key.OPENSSH.ED25519.Encrypted.txt
diff --git a/src/Renci.SshNet.Tests/Data/Key.OPENSSH.ED25519.txt b/src/Data/Key.OPENSSH.ED25519.txt
similarity index 100%
rename from src/Renci.SshNet.Tests/Data/Key.OPENSSH.ED25519.txt
rename to src/Data/Key.OPENSSH.ED25519.txt
diff --git a/src/Renci.SshNet.Tests/Data/Key.OPENSSH.RSA.Encrypted.txt b/src/Data/Key.OPENSSH.RSA.Encrypted.txt
similarity index 100%
rename from src/Renci.SshNet.Tests/Data/Key.OPENSSH.RSA.Encrypted.txt
rename to src/Data/Key.OPENSSH.RSA.Encrypted.txt
diff --git a/src/Renci.SshNet.Tests/Data/Key.OPENSSH.RSA.txt b/src/Data/Key.OPENSSH.RSA.txt
similarity index 100%
rename from src/Renci.SshNet.Tests/Data/Key.OPENSSH.RSA.txt
rename to src/Data/Key.OPENSSH.RSA.txt
diff --git a/src/Renci.SshNet.Tests/Data/Key.RSA.Encrypted.Aes.128.CBC.12345.txt b/src/Data/Key.RSA.Encrypted.Aes.128.CBC.12345.txt
similarity index 100%
rename from src/Renci.SshNet.Tests/Data/Key.RSA.Encrypted.Aes.128.CBC.12345.txt
rename to src/Data/Key.RSA.Encrypted.Aes.128.CBC.12345.txt
diff --git a/src/Renci.SshNet.Tests/Data/Key.RSA.Encrypted.Aes.192.CBC.12345.txt b/src/Data/Key.RSA.Encrypted.Aes.192.CBC.12345.txt
similarity index 100%
rename from src/Renci.SshNet.Tests/Data/Key.RSA.Encrypted.Aes.192.CBC.12345.txt
rename to src/Data/Key.RSA.Encrypted.Aes.192.CBC.12345.txt
diff --git a/src/Renci.SshNet.Tests/Data/Key.RSA.Encrypted.Aes.256.CBC.12345.txt b/src/Data/Key.RSA.Encrypted.Aes.256.CBC.12345.txt
similarity index 100%
rename from src/Renci.SshNet.Tests/Data/Key.RSA.Encrypted.Aes.256.CBC.12345.txt
rename to src/Data/Key.RSA.Encrypted.Aes.256.CBC.12345.txt
diff --git a/src/Renci.SshNet.Tests/Data/Key.RSA.Encrypted.Des.CBC.12345.txt b/src/Data/Key.RSA.Encrypted.Des.CBC.12345.txt
similarity index 100%
rename from src/Renci.SshNet.Tests/Data/Key.RSA.Encrypted.Des.CBC.12345.txt
rename to src/Data/Key.RSA.Encrypted.Des.CBC.12345.txt
diff --git a/src/Renci.SshNet.Tests/Data/Key.RSA.Encrypted.Des.Ede3.CBC.12345.txt b/src/Data/Key.RSA.Encrypted.Des.Ede3.CBC.12345.txt
similarity index 100%
rename from src/Renci.SshNet.Tests/Data/Key.RSA.Encrypted.Des.Ede3.CBC.12345.txt
rename to src/Data/Key.RSA.Encrypted.Des.Ede3.CBC.12345.txt
diff --git a/src/Renci.SshNet.Tests/Data/Key.RSA.Encrypted.Des.Ede3.CFB.1234567890.txt b/src/Data/Key.RSA.Encrypted.Des.Ede3.CFB.1234567890.txt
similarity index 100%
rename from src/Renci.SshNet.Tests/Data/Key.RSA.Encrypted.Des.Ede3.CFB.1234567890.txt
rename to src/Data/Key.RSA.Encrypted.Des.Ede3.CFB.1234567890.txt
diff --git a/src/Renci.SshNet.Tests/Data/Key.RSA.txt b/src/Data/Key.RSA.txt
similarity index 100%
rename from src/Renci.SshNet.Tests/Data/Key.RSA.txt
rename to src/Data/Key.RSA.txt
diff --git a/src/Renci.SshNet.Tests/Data/Key.SSH2.DSA.Encrypted.Des.CBC.12345.txt b/src/Data/Key.SSH2.DSA.Encrypted.Des.CBC.12345.txt
similarity index 100%
rename from src/Renci.SshNet.Tests/Data/Key.SSH2.DSA.Encrypted.Des.CBC.12345.txt
rename to src/Data/Key.SSH2.DSA.Encrypted.Des.CBC.12345.txt
diff --git a/src/Renci.SshNet.Tests/Data/Key.SSH2.DSA.txt b/src/Data/Key.SSH2.DSA.txt
similarity index 100%
rename from src/Renci.SshNet.Tests/Data/Key.SSH2.DSA.txt
rename to src/Data/Key.SSH2.DSA.txt
diff --git a/src/Renci.SshNet.Tests/Data/Key.SSH2.RSA.Encrypted.Des.CBC.12345.txt b/src/Data/Key.SSH2.RSA.Encrypted.Des.CBC.12345.txt
similarity index 100%
rename from src/Renci.SshNet.Tests/Data/Key.SSH2.RSA.Encrypted.Des.CBC.12345.txt
rename to src/Data/Key.SSH2.RSA.Encrypted.Des.CBC.12345.txt
diff --git a/src/Renci.SshNet.Tests/Data/Key.SSH2.RSA.txt b/src/Data/Key.SSH2.RSA.txt
similarity index 100%
rename from src/Renci.SshNet.Tests/Data/Key.SSH2.RSA.txt
rename to src/Data/Key.SSH2.RSA.txt
diff --git a/src/Renci.SshNet.Benchmarks/Program.cs b/src/Renci.SshNet.Benchmarks/Program.cs
new file mode 100644
index 000000000..3249baa99
--- /dev/null
+++ b/src/Renci.SshNet.Benchmarks/Program.cs
@@ -0,0 +1,27 @@
+using BenchmarkDotNet.Running;
+
+namespace Renci.SshNet.Benchmarks
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ // Usage examples:
+ // 1. Run all benchmarks:
+ // dotnet run -c Release -- --filter *
+ // 2. List all benchmarks:
+ // dotnet run -c Release -- --list flat
+ // 3. Run a subset of benchmarks based on a filter (of a benchmark method's fully-qualified name,
+ // 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
+ // 3. Print help:
+ // dotnet run -c Release -- --help
+
+ // See also https://benchmarkdotnet.org/articles/guides/console-args.html
+
+ _ = BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
+ }
+ }
+}
diff --git a/src/Renci.SshNet.Benchmarks/Renci.SshNet.Benchmarks.csproj b/src/Renci.SshNet.Benchmarks/Renci.SshNet.Benchmarks.csproj
new file mode 100644
index 000000000..daf3f5095
--- /dev/null
+++ b/src/Renci.SshNet.Benchmarks/Renci.SshNet.Benchmarks.csproj
@@ -0,0 +1,31 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+
+ $(NoWarn);CS1591
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Renci.SshNet.Benchmarks/Security/Cryptography/Ciphers/AesCipherBenchmarks.cs b/src/Renci.SshNet.Benchmarks/Security/Cryptography/Ciphers/AesCipherBenchmarks.cs
new file mode 100644
index 000000000..ff414cc4a
--- /dev/null
+++ b/src/Renci.SshNet.Benchmarks/Security/Cryptography/Ciphers/AesCipherBenchmarks.cs
@@ -0,0 +1,38 @@
+using BenchmarkDotNet.Attributes;
+using Renci.SshNet.Security.Cryptography.Ciphers;
+using Renci.SshNet.Security.Cryptography.Ciphers.Modes;
+
+namespace Renci.SshNet.Benchmarks.Security.Cryptography.Ciphers
+{
+ [MemoryDiagnoser]
+ public class AesCipherBenchmarks
+ {
+ private readonly byte[] _key;
+ private readonly byte[] _iv;
+ private readonly byte[] _data;
+
+ public AesCipherBenchmarks()
+ {
+ _key = new byte[32];
+ _iv = new byte[16];
+ _data = new byte[256];
+
+ Random random = new(Seed: 12345);
+ random.NextBytes(_key);
+ random.NextBytes(_iv);
+ random.NextBytes(_data);
+ }
+
+ [Benchmark]
+ public byte[] Encrypt_CBC()
+ {
+ return new AesCipher(_key, new CbcCipherMode(_iv), null).Encrypt(_data);
+ }
+
+ [Benchmark]
+ public byte[] Decrypt_CBC()
+ {
+ return new AesCipher(_key, new CbcCipherMode(_iv), null).Decrypt(_data);
+ }
+ }
+}
diff --git a/src/Renci.SshNet.Benchmarks/Security/Cryptography/Ciphers/RsaCipherBenchmarks.cs b/src/Renci.SshNet.Benchmarks/Security/Cryptography/Ciphers/RsaCipherBenchmarks.cs
new file mode 100644
index 000000000..f25e6db71
--- /dev/null
+++ b/src/Renci.SshNet.Benchmarks/Security/Cryptography/Ciphers/RsaCipherBenchmarks.cs
@@ -0,0 +1,48 @@
+using BenchmarkDotNet.Attributes;
+
+using Renci.SshNet.Security;
+using Renci.SshNet.Security.Cryptography.Ciphers;
+
+namespace Renci.SshNet.Benchmarks.Security.Cryptography.Ciphers
+{
+ [MemoryDiagnoser]
+ public class RsaCipherBenchmarks
+ {
+ private readonly RsaKey _privateKey;
+ private readonly RsaKey _publicKey;
+ private readonly byte[] _data;
+
+ public RsaCipherBenchmarks()
+ {
+ _data = new byte[128];
+
+ Random random = new(Seed: 12345);
+ random.NextBytes(_data);
+
+ using (var s = typeof(RsaCipherBenchmarks).Assembly.GetManifestResourceStream("Renci.SshNet.Benchmarks.Data.Key.RSA.txt"))
+ {
+ _privateKey = (RsaKey)((KeyHostAlgorithm) new PrivateKeyFile(s).HostKey).Key;
+
+ // The implementations of RsaCipher.Encrypt/Decrypt differ based on whether the supplied RsaKey has private key information
+ // or only public. So we extract out the public key information to a separate variable.
+ _publicKey = new RsaKey()
+ {
+ Public = _privateKey.Public
+ };
+ }
+ }
+
+ [Benchmark]
+ public byte[] Encrypt()
+ {
+ return new RsaCipher(_publicKey).Encrypt(_data);
+ }
+
+ // RSA Decrypt does not work
+ // [Benchmark]
+ // public byte[] Decrypt()
+ // {
+ // return new RsaCipher(_privateKey).Decrypt(_data);
+ // }
+ }
+}
diff --git a/src/Renci.SshNet.Benchmarks/Security/Cryptography/ED25519DigitalSignatureBenchmarks.cs b/src/Renci.SshNet.Benchmarks/Security/Cryptography/ED25519DigitalSignatureBenchmarks.cs
new file mode 100644
index 000000000..2f07a2d3a
--- /dev/null
+++ b/src/Renci.SshNet.Benchmarks/Security/Cryptography/ED25519DigitalSignatureBenchmarks.cs
@@ -0,0 +1,41 @@
+using BenchmarkDotNet.Attributes;
+
+using Renci.SshNet.Security;
+using Renci.SshNet.Security.Cryptography;
+
+namespace Renci.SshNet.Benchmarks.Security.Cryptography
+{
+ [MemoryDiagnoser]
+ public class ED25519DigitalSignatureBenchmarks
+ {
+ private readonly ED25519Key _key;
+ private readonly byte[] _data;
+ private readonly byte[] _signature;
+
+ public ED25519DigitalSignatureBenchmarks()
+ {
+ _data = new byte[128];
+
+ Random random = new(Seed: 12345);
+ random.NextBytes(_data);
+
+ using (var s = typeof(ED25519DigitalSignatureBenchmarks).Assembly.GetManifestResourceStream("Renci.SshNet.Benchmarks.Data.Key.OPENSSH.ED25519.txt"))
+ {
+ _key = (ED25519Key) ((KeyHostAlgorithm) new PrivateKeyFile(s).HostKey).Key;
+ }
+ _signature = new ED25519DigitalSignature(_key).Sign(_data);
+ }
+
+ [Benchmark]
+ public byte[] Sign()
+ {
+ return new ED25519DigitalSignature(_key).Sign(_data);
+ }
+
+ [Benchmark]
+ public bool Verify()
+ {
+ return new ED25519DigitalSignature(_key).Verify(_data, _signature);
+ }
+ }
+}
diff --git a/src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj b/src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj
index 94a4e8015..aa9d79a2a 100644
--- a/src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj
+++ b/src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj
@@ -14,33 +14,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/src/Renci.SshNet.sln b/src/Renci.SshNet.sln
index 34cc0f483..459d129c4 100644
--- a/src/Renci.SshNet.sln
+++ b/src/Renci.SshNet.sln
@@ -42,6 +42,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{D21A4D03-0
..\test\Directory.Build.props = ..\test\Directory.Build.props
EndProjectSection
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Renci.SshNet.Benchmarks", "Renci.SshNet.Benchmarks\Renci.SshNet.Benchmarks.csproj", "{A8C83FF2-B733-4A01-8D4E-D6DA2D420484}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Renci.SshNet.IntegrationTests", "Renci.SshNet.IntegrationTests\Renci.SshNet.IntegrationTests.csproj", "{EEF98046-729C-419E-932D-4E569073C8CC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Renci.SshNet.TestTools.OpenSSH", "Renci.SshNet.TestTools.OpenSSH\Renci.SshNet.TestTools.OpenSSH.csproj", "{78239046-2019-494E-B6EC-240AF787E4D0}"
@@ -88,6 +90,26 @@ Global
{C45379B9-17B1-4E89-BC2E-6D41726413E8}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{C45379B9-17B1-4E89-BC2E-6D41726413E8}.Release|x64.ActiveCfg = Release|Any CPU
{C45379B9-17B1-4E89-BC2E-6D41726413E8}.Release|x86.ActiveCfg = Release|Any CPU
+ {A8C83FF2-B733-4A01-8D4E-D6DA2D420484}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A8C83FF2-B733-4A01-8D4E-D6DA2D420484}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A8C83FF2-B733-4A01-8D4E-D6DA2D420484}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {A8C83FF2-B733-4A01-8D4E-D6DA2D420484}.Debug|ARM.Build.0 = Debug|Any CPU
+ {A8C83FF2-B733-4A01-8D4E-D6DA2D420484}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {A8C83FF2-B733-4A01-8D4E-D6DA2D420484}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {A8C83FF2-B733-4A01-8D4E-D6DA2D420484}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A8C83FF2-B733-4A01-8D4E-D6DA2D420484}.Debug|x64.Build.0 = Debug|Any CPU
+ {A8C83FF2-B733-4A01-8D4E-D6DA2D420484}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A8C83FF2-B733-4A01-8D4E-D6DA2D420484}.Debug|x86.Build.0 = Debug|Any CPU
+ {A8C83FF2-B733-4A01-8D4E-D6DA2D420484}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A8C83FF2-B733-4A01-8D4E-D6DA2D420484}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A8C83FF2-B733-4A01-8D4E-D6DA2D420484}.Release|ARM.ActiveCfg = Release|Any CPU
+ {A8C83FF2-B733-4A01-8D4E-D6DA2D420484}.Release|ARM.Build.0 = Release|Any CPU
+ {A8C83FF2-B733-4A01-8D4E-D6DA2D420484}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {A8C83FF2-B733-4A01-8D4E-D6DA2D420484}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {A8C83FF2-B733-4A01-8D4E-D6DA2D420484}.Release|x64.ActiveCfg = Release|Any CPU
+ {A8C83FF2-B733-4A01-8D4E-D6DA2D420484}.Release|x64.Build.0 = Release|Any CPU
+ {A8C83FF2-B733-4A01-8D4E-D6DA2D420484}.Release|x86.ActiveCfg = Release|Any CPU
+ {A8C83FF2-B733-4A01-8D4E-D6DA2D420484}.Release|x86.Build.0 = Release|Any CPU
{EEF98046-729C-419E-932D-4E569073C8CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EEF98046-729C-419E-932D-4E569073C8CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EEF98046-729C-419E-932D-4E569073C8CC}.Debug|ARM.ActiveCfg = Debug|Any CPU