Implementing Host Key Recognition for SSH/SFTP in Chilkat
FileZilla and the Chilkat SFTP library handle host key verification differently because one is a user-facing GUI application and the other is a developer library.
1. How FileZilla Stores Host Keys
When you click "OK" on the "Unknown Host Key" dialog in FileZilla, it saves that key so it won't ask you again. On Windows, FileZilla does not typically store this in a simple text file like known_hosts.
Instead, FileZilla (which uses a modified version of PuTTY's generic SFTP tool) stores these trusted keys in the Windows Registry.
- Location:
HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\SshHostKeys - Format: The registry entry name is formatted like
rsa2@22:hostname, and the value is the public key string.
This registry entry acts as the "cache." Every time FileZilla connects, it checks this registry location. If the key exists and matches, the connection proceeds silently. If it is missing or different, the dialog appears.
2. How to Achieve "Trust on First Use" with Chilkat
Chilkat is a library, not an application, so it intentionally does not touch your file system or registry to "cache" things automatically. It is up to your code to decide if a server is trusted.
To replicate FileZilla's behavior, you must write code to:
- Connect to the server.
- Retrieve the server's fingerprint (
sftp.HostKeyFingerprint). - Check if that fingerprint exists in your own local "cache" file (e.g., a text file named
trusted_hosts.txt). - Decide whether to proceed, disconnect, or update the cache.
Implementation Logic
You can implement a simple text-based cache similar to the OpenSSH known_hosts file.
- Create a storage file (e.g.,
my_known_hosts.txt). - On Connection:
- Call
sftp.Connect(). - Immediately check
sftp.HostKeyFingerprint. - Read your
my_known_hosts.txtfile. - Case A (Found & Match): The fingerprint is in the file. Proceed with authentication.
- Case B (Found & Mismatch): The fingerprint in the file is different from the server's. This is a security risk (Man-in-the-Middle attack). Abort the connection and alert the user.
- Case C (Not Found): This is a first-time connection. Present the fingerprint to the user (via a dialog box).
- If they click Yes: Append the new fingerprint to
my_known_hosts.txtand proceed. - If they click No: Disconnect (
sftp.Disconnect()).
- If they click Yes: Append the new fingerprint to
- Call
Example Code (C#)
This example demonstrates the logic of checking a local file for the fingerprint before allowing the session to continue.
using Chilkat;
using System.IO;
public void ConnectWithHostKeyCheck()
{
SFtp sftp = new SFtp();
bool success = sftp.Connect("sftp.example.com", 22);
if (!success)
{
Console.WriteLine(sftp.LastErrorText);
return;
}
// 1. Get the server's fingerprint
string serverFingerprint = sftp.HostKeyFingerprint;
string cacheFilePath = "trusted_hosts.txt";
string hostId = "sftp.example.com"; // Unique ID for lookup
// 2. Check your local cache
string cachedFingerprint = GetCachedFingerprint(cacheFilePath, hostId);
if (cachedFingerprint == null)
{
// CASE: First time connecting (Key unknown)
// In a real app, pop up a MessageBox here asking the user to trust it.
Console.WriteLine($"The server's host key is unknown: {serverFingerprint}");
Console.WriteLine("Do you trust this host? (y/n)");
var response = Console.ReadLine();
if (response.ToLower() == "y")
{
// Save the key to your cache file
SaveFingerprintToCache(cacheFilePath, hostId, serverFingerprint);
}
else
{
Console.WriteLine("Connection aborted by user.");
sftp.Disconnect();
return;
}
}
else if (cachedFingerprint != serverFingerprint)
{
// CASE: Security Warning! Key has changed!
Console.WriteLine("SECURITY WARNING: Host key has changed!");
Console.WriteLine($"Stored: {cachedFingerprint}");
Console.WriteLine($"Server: {serverFingerprint}");
Console.WriteLine("Connection aborted for security.");
sftp.Disconnect();
return;
}
// 3. If we are here, the key is trusted. Proceed to authenticate.
success = sftp.AuthenticatePw("myUsername", "myPassword");
if (!success)
{
Console.WriteLine(sftp.LastErrorText);
return;
}
Console.WriteLine("Connected and Authenticated!");
}
// Helper: Simple method to read a "hostname=fingerprint" formatted file
private string GetCachedFingerprint(string path, string hostname)
{
if (!File.Exists(path)) return null;
foreach (var line in File.ReadAllLines(path))
{
var parts = line.Split('=');
if (parts.Length == 2 && parts[0].Trim() == hostname)
{
return parts[1].Trim();
}
}
return null;
}
// Helper: Append new trusted host to file
private void SaveFingerprintToCache(string path, string hostname, string fingerprint)
{
File.AppendAllText(path, $"{hostname}={fingerprint}\n");
}