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:

  1. Connect to the server.
  2. Retrieve the server's fingerprint (sftp.HostKeyFingerprint).
  3. Check if that fingerprint exists in your own local "cache" file (e.g., a text file named trusted_hosts.txt).
  4. 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.

  1. Create a storage file (e.g., my_known_hosts.txt).
  2. On Connection:
    • Call sftp.Connect().
    • Immediately check sftp.HostKeyFingerprint.
    • Read your my_known_hosts.txt file.
    • 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.txt and proceed.
      • If they click No: Disconnect (sftp.Disconnect()).

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");
}