使用SSH.NET实现SFTP传输时,字符串转字节数组异常及密钥验证求助
问题背景
使用SSH.NET通过SFTP传输文件时,需验证主机密钥指纹提升安全性。将从known-hosts获取的主机密钥(格式如ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBP4cDyvxP/kbZ1XrPxxyMshbTtrY605xu5VL37uZt9mv7d2ZqD3TSWoEcUgHgtzGuRzkGTfDXdT9J=)以字符串存储在XML配置中,编写的字符串转字节数组方法抛出异常:
<![LOG[Failed: Exception occurred - Additional non-parsable characters are at the end of the string.]LOG]!>
错误原因
当前StringToByteArray方法是处理十六进制格式字符串的,但传入的是带算法前缀的Base64编码完整公钥,两者格式不匹配导致转换失败。另外,e.FingerPrint是主机密钥的哈希指纹字节数组,和直接解码Base64公钥得到的字节数组并非同一数据。
解决方案
提供两种验证方式,按需选择:
方案一:直接验证完整主机公钥(推荐,安全性更高)
直接使用known-hosts中的Base64公钥进行验证,无需计算指纹,避免哈希碰撞风险:
修改FtpHandler类的UploadFileSftp方法:
using System; using System.IO; using System.Linq; using FsLogger; using Renci.SshNet; namespace FsUtils.FileOperations { public class FtpHandler { public string User { get; set; } public string Password { get; set; } public string Server { get; set; } public void UploadFileSftp(string sourceFile, string targetDir, string sshHostKey) { // 拆分算法标识和Base64公钥部分 var keyParts = sshHostKey.Split(new[] { ' ' }, 2); if (keyParts.Length != 2) throw new ArgumentException("Invalid host key format. Expected format: [algorithm] [base64-public-key]"); string expectedAlgorithm = keyParts[0]; byte[] expectedPublicKey = Convert.FromBase64String(keyParts[1]); using (var client = new SftpClient(Server, User, Password)) { client.HostKeyReceived += (sender, e) => { // 对比算法和公钥字节数组 e.CanTrust = e.HostKey.Algorithm == expectedAlgorithm && e.HostKey.Data.SequenceEqual(expectedPublicKey); }; try { client.Connect(); if (client.IsConnected) { // 确保目标路径包含文件名,避免上传到目录时出错 string targetPath = Path.Combine(targetDir, Path.GetFileName(sourceFile)); using (var fileStream = File.OpenRead(sourceFile)) { client.UploadFile(fileStream, targetPath); } } Logging.Info("File uploaded successfully"); } catch (Exception ex) { Logging.Error($"Exception occurred - {ex.Message}"); } finally { if (client.IsConnected) client.Disconnect(); } } } } }
方案二:验证十六进制格式的指纹
如果坚持使用指纹验证,需先获取正确的十六进制指纹(可通过ssh-keyscan -t ecdsa-sha2-nistp256 <server> | ssh-keygen -lf -生成,格式如SHA256:abcdef1234...或AA:BB:CC:...),然后修改转换方法处理指纹格式:
修改FtpHandler类中的StringToByteArray方法及事件处理:
public void UploadFileSftp(string sourceFile, string targetDir, string sshHostKeyFingerprint) { using (var client = new SftpClient(Server, User, Password)) { client.HostKeyReceived += (sender, e) => { byte[] receivedFingerprint = e.FingerPrint; byte[] providedFingerprint = StringToByteArray(sshHostKeyFingerprint); e.CanTrust = receivedFingerprint.SequenceEqual(providedFingerprint); }; // 后续连接、上传逻辑同原代码... } } private byte[] StringToByteArray(string fingerprint) { // 移除指纹中的分隔符(冒号或空格) string cleanHex = fingerprint.Replace(":", "").Replace(" ", "").Trim(); // 移除可能的前缀(如SHA256:) if (cleanHex.Contains("SHA256:")) cleanHex = cleanHex.Substring(cleanHex.IndexOf(":") + 1); return Enumerable.Range(0, cleanHex.Length) .Where(x => x % 2 == 0) .Select(x => Convert.ToByte(cleanHex.Substring(x, 2), 16)) .ToArray(); }
注意事项
- 方案一无需额外转换公钥,直接验证原始密钥,安全性更高;
- 方案二需确保指纹与目标服务器的主机密钥完全对应,避免因算法不一致导致验证失败;
- 无论哪种方案,都要确保配置中的密钥/指纹来源可信,避免中间人攻击。
内容的提问来源于stack exchange,提问作者Nafisian Castle

