diff --git a/src/Renci.SshNet.Benchmarks/Common/HostKeyEventArgsBenchmarks.cs b/src/Renci.SshNet.Benchmarks/Common/HostKeyEventArgsBenchmarks.cs
new file mode 100644
index 000000000..54900d046
--- /dev/null
+++ b/src/Renci.SshNet.Benchmarks/Common/HostKeyEventArgsBenchmarks.cs
@@ -0,0 +1,66 @@
+using BenchmarkDotNet.Attributes;
+
+using Renci.SshNet.Benchmarks.Security.Cryptography.Ciphers;
+using Renci.SshNet.Common;
+using Renci.SshNet.Security;
+
+namespace Renci.SshNet.Benchmarks.Common
+{
+ [MemoryDiagnoser]
+ [ShortRunJob]
+ public class HostKeyEventArgsBenchmarks
+ {
+ private readonly KeyHostAlgorithm _keyHostAlgorithm;
+
+ public HostKeyEventArgsBenchmarks()
+ {
+ _keyHostAlgorithm = GetKeyHostAlgorithm();
+ }
+ private static KeyHostAlgorithm GetKeyHostAlgorithm()
+ {
+ using (var s = typeof(RsaCipherBenchmarks).Assembly.GetManifestResourceStream("Renci.SshNet.Benchmarks.Data.Key.RSA.txt"))
+ {
+ var privateKey = new PrivateKeyFile(s);
+ return (KeyHostAlgorithm) privateKey.HostKeyAlgorithms.First();
+ }
+ }
+
+ [Benchmark()]
+ public HostKeyEventArgs Constructor()
+ {
+ return new HostKeyEventArgs(_keyHostAlgorithm);
+ }
+
+ [Benchmark()]
+ public (string, string) CalculateFingerPrintSHA256AndMD5()
+ {
+ var test = new HostKeyEventArgs(_keyHostAlgorithm);
+
+ return (test.FingerPrintSHA256, test.FingerPrintMD5);
+ }
+
+ [Benchmark()]
+ public string CalculateFingerPrintSHA256()
+ {
+ var test = new HostKeyEventArgs(_keyHostAlgorithm);
+
+ return test.FingerPrintSHA256;
+ }
+
+ [Benchmark()]
+ public byte[] CalculateFingerPrint()
+ {
+ var test = new HostKeyEventArgs(_keyHostAlgorithm);
+
+ return test.FingerPrint;
+ }
+
+ [Benchmark()]
+ public string CalculateFingerPrintMD5()
+ {
+ var test = new HostKeyEventArgs(_keyHostAlgorithm);
+
+ return test.FingerPrintSHA256;
+ }
+ }
+}
diff --git a/src/Renci.SshNet.IntegrationTests/ConnectivityTests.cs b/src/Renci.SshNet.IntegrationTests/ConnectivityTests.cs
index 5401e6b6b..2f94ba7ff 100644
--- a/src/Renci.SshNet.IntegrationTests/ConnectivityTests.cs
+++ b/src/Renci.SshNet.IntegrationTests/ConnectivityTests.cs
@@ -380,6 +380,53 @@ public void Common_HostKeyValidation_Success()
Assert.IsTrue(hostValidationSuccessful);
}
+ [TestMethod]
+ public void Common_HostKeyValidationSHA256_Success()
+ {
+ var hostValidationSuccessful = false;
+
+ using (var client = new SshClient(_connectionInfoFactory.Create()))
+ {
+ client.HostKeyReceived += (sender, e) =>
+ {
+ if (e.FingerPrintSHA256 == "9fa6vbz64gimzsGZ/xZi3aaYE1o7E96iU2NjcfQNGwI")
+ {
+ hostValidationSuccessful = e.CanTrust;
+ }
+ else
+ {
+ e.CanTrust = false;
+ }
+ };
+ client.Connect();
+ }
+
+ Assert.IsTrue(hostValidationSuccessful);
+ }
+
+ [TestMethod]
+ public void Common_HostKeyValidationMD5_Success()
+ {
+ var hostValidationSuccessful = false;
+
+ using (var client = new SshClient(_connectionInfoFactory.Create()))
+ {
+ client.HostKeyReceived += (sender, e) =>
+ {
+ if (e.FingerPrintMD5 == "3d:90:d8:0d:d5:e0:b6:13:42:7c:78:1e:19:a3:99:2b")
+ {
+ hostValidationSuccessful = e.CanTrust;
+ }
+ else
+ {
+ e.CanTrust = false;
+ }
+ };
+ client.Connect();
+ }
+
+ Assert.IsTrue(hostValidationSuccessful);
+ }
///
/// Verifies whether we handle a disconnect initiated by the SSH server (through a SSH_MSG_DISCONNECT message).
///
diff --git a/src/Renci.SshNet.IntegrationTests/Dockerfile b/src/Renci.SshNet.IntegrationTests/Dockerfile
index 160ea6f29..19ef6e19e 100644
--- a/src/Renci.SshNet.IntegrationTests/Dockerfile
+++ b/src/Renci.SshNet.IntegrationTests/Dockerfile
@@ -14,6 +14,8 @@ RUN apk update && apk upgrade --no-cache && \
chmod 400 /etc/ssh/ssh*key && \
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config && \
sed -i 's/#LogLevel\s*INFO/LogLevel DEBUG3/' /etc/ssh/sshd_config && \
+ # Set the default RSA key
+ echo 'HostKey /etc/ssh/ssh_host_rsa_key' >> /etc/ssh/sshd_config && \
chmod 646 /etc/ssh/sshd_config && \
# install and configure sudo
apk add --no-cache sudo && \
@@ -45,4 +47,4 @@ RUN apk update && apk upgrade --no-cache && \
EXPOSE 22 22
-ENTRYPOINT ["/opt/sshnet/start.sh"]
\ No newline at end of file
+ENTRYPOINT ["/opt/sshnet/start.sh"]
diff --git a/src/Renci.SshNet.Tests/Classes/Common/HostKeyEventArgsTest.cs b/src/Renci.SshNet.Tests/Classes/Common/HostKeyEventArgsTest.cs
index 28001655a..39ff85d7b 100644
--- a/src/Renci.SshNet.Tests/Classes/Common/HostKeyEventArgsTest.cs
+++ b/src/Renci.SshNet.Tests/Classes/Common/HostKeyEventArgsTest.cs
@@ -56,6 +56,8 @@ public void HostKeyEventArgsConstructorTest_VerifyMD5()
Assert.IsTrue(new byte[] {
0x92, 0xea, 0x54, 0xa1, 0x01, 0xf9, 0x95, 0x9c, 0x71, 0xd9, 0xbb, 0x51, 0xb2, 0x55, 0xf8, 0xd9
}.SequenceEqual(target.FingerPrint));
+ Assert.AreEqual("92:ea:54:a1:01:f9:95:9c:71:d9:bb:51:b2:55:f8:d9", target.FingerPrintMD5);
+
}
///
diff --git a/src/Renci.SshNet/Common/HostKeyEventArgs.cs b/src/Renci.SshNet/Common/HostKeyEventArgs.cs
index ddf49d0ac..993b6d645 100644
--- a/src/Renci.SshNet/Common/HostKeyEventArgs.cs
+++ b/src/Renci.SshNet/Common/HostKeyEventArgs.cs
@@ -1,4 +1,5 @@
using System;
+
using Renci.SshNet.Abstractions;
using Renci.SshNet.Security;
@@ -9,6 +10,10 @@ namespace Renci.SshNet.Common
///
public class HostKeyEventArgs : EventArgs
{
+ private readonly Lazy _lazyFingerPrint;
+ private readonly Lazy _lazyFingerPrintSHA256;
+ private readonly Lazy _lazyFingerPrintMD5;
+
///
/// Gets or sets a value indicating whether host key can be trusted.
///
@@ -33,15 +38,42 @@ public class HostKeyEventArgs : EventArgs
///
/// MD5 fingerprint as byte array.
///
- public byte[] FingerPrint { get; private set; }
+ public byte[] FingerPrint
+ {
+ get
+ {
+ return _lazyFingerPrint.Value;
+ }
+ }
///
- /// Gets the SHA256 fingerprint.
+ /// Gets the SHA256 fingerprint of the host key in the same format as the ssh command,
+ /// i.e. non-padded base64, but without the SHA256: prefix.
///
+ /// ohD8VZEXGWo6Ez8GSEJQ9WpafgLFsOfLOtGGQCQo6Og
///
/// Base64 encoded SHA256 fingerprint with padding (equals sign) removed.
///
- public string FingerPrintSHA256 { get; private set; }
+ public string FingerPrintSHA256
+ {
+ get
+ {
+ return _lazyFingerPrintSHA256.Value;
+ }
+ }
+
+ ///
+ /// Gets the MD5 fingerprint of the host key in the same format as the ssh command,
+ /// i.e. hexadecimal bytes separated by colons, but without the MD5: prefix.
+ ///
+ /// 97:70:33:82:fd:29:3a:73:39:af:6a:07:ad:f8:80:49
+ public string FingerPrintMD5
+ {
+ get
+ {
+ return _lazyFingerPrintMD5.Value;
+ }
+ }
///
/// Gets the length of the key in bits.
@@ -61,16 +93,21 @@ public HostKeyEventArgs(KeyHostAlgorithm host)
HostKey = host.Data;
HostKeyName = host.Name;
KeyLength = host.Key.KeyLength;
-
- using (var md5 = CryptoAbstraction.CreateMD5())
+
+ _lazyFingerPrint = new Lazy(() =>
{
- FingerPrint = md5.ComputeHash(host.Data);
- }
+ using var md5 = CryptoAbstraction.CreateMD5();
+ return md5.ComputeHash(HostKey);
+ });
- using (var sha256 = CryptoAbstraction.CreateSHA256())
+ _lazyFingerPrintSHA256 = new Lazy(() =>
{
- FingerPrintSHA256 = Convert.ToBase64String(sha256.ComputeHash(host.Data)).Replace("=", "");
- }
+ using var sha256 = CryptoAbstraction.CreateSHA256();
+ return Convert.ToBase64String(sha256.ComputeHash(HostKey)).Replace("=", "");
+ });
+
+ _lazyFingerPrintMD5 = new Lazy(() =>
+ BitConverter.ToString(FingerPrint).Replace("-", ":").ToLowerInvariant());
}
}
}