SSH is one of the best security protocols out there. It is used by anyone remotely logging into servers, as well as for secure connection to Git servers, and for secure file transfers via SFTP. One of the key promises of SSH is protection against active man-in-the-middle attacks. This makes SSH the best choice when connecting to a server over a hostile network, such as over a public hotspot. However, some SSH clients (particularly on mobile phones) void this protection by not caching server keys. Can you do anything about it? Yes, use private-keys instead of passwords for client authentication. Read more (also) for the technical details.
SSH protects against both passive and active attacks without depending on the good behavior of CAs (as TLS does). It simply does not rely on any external accreditation for the public-key provided by the server. Instead, a server cannot be trusted unless you explicitly authorize its public-key once. Once authorized, the attacker cannot introduce another public-key as belonging to that server, and hence he cannot interfere with the confidentiality and the integrity of the tunnel between you and that server. The underlying assumption is that the initial registration of the server public-key was clean, i.e., without an attacker replacing the key. Since this first invocation of the protocol is likely to have occured over a relatively secure network, this assumption is acceptable.
Once you have the safe tunnel established, you can comfortably type in your password to authenticate yourself to the server.
So far, so good; but just as long as the client follows this scheme properly. A problem arises when many clients, especially on mobile phones, do not cache server public-keys. Instead, they automatically accept any public-key the server presents each time, even if this key is suspiciously different than the one used by that server before. The protocol is still secure against passive eavesdropping, but nothing more than that. Many (if not most) mobile SFTP implementations, at least on Android, do not bother to cache server public-keys to assure that a man-in-the-middle attack is not in progress.
What can you do about it? — Use a private-key to authenticate the client (given the client supports this). It is not intuitive why this would solve the problem, but it does.
Client private-key authentication implies that the client authenticates to the server using a private-key rather than using a password. SSH carries out the tunnel establishment and the client authentication at two separate phases in the protocol. First, the server authenticates itself to the client using its public-key, and a tunnel is created; only then does the client authenticate itself to the server using its password or private-key. By this model, it does not seem that having the client authenticate using a key rather than a password would make a difference. The problem we have is of an active attacker impersonating the server by surreptitiously sending a different server public-key. One may thus anticipate that since the seemingly-trusted tunnel with the attacker (instead of the real server) has already been established, the attacker can simply sit between the client and the real server, impersonate the real server to the real client, get the client credentials, and continue to impersonate the client against the real server using those stolen credentials. This is how man-in-the-middle usually works, and this is precisely what could happen if the user authenticates with a password. One would intuitively think that the same trick would work also if the user authenticates with a private key. Once the attacker connects to the real server as a client, he can safely capture the challenge issued by the real server, pass it over to the real client as its own, have the real client solve the puzzle with its private key, and relay the true answer to the real server. Fortunately, this does not work, despite the fact that the tunnel with the attacker is well established, thanks to the sound design of the protocol.
In SSH, the server encrypts the challenge with the client public key, and sends it to the client for decryption. The client decrypts the challenge, but does not send it back as is. Instead, it hashes it along with the session encryption key that the already-existent tunnel uses, and sends the hash back. The server knows the challenge that it created, and knows the session key that it helped establish, and can thus verify that it can reproduce the hash that was sent back by client. What saved the day is the fact that the client will only send a hash of the decrypted challenge along with the session key, rather than the decrypted challenge on its own.
The session key was negotiated between the client and the server using a protocol that resembles Diffie-Hellman. The interesting property of a key that is negotiated this way is that it depends on both parties in a way that it cannot be reproduced by one of them alone. When the man-in-the-middle attacker connected to the real server and negotiated a session key k, this k depends on computation done by the attacker with some ephemeral one-time secret it created, and the real server with its own ephemeral one-time secret. When the attacker connects to the client, while it knows what session key k it uses with the real server, and while it can control its own ephemeral one-time secrets, it cannot control the ephemeral one-time secret that the client will use, and hence cannot force the protocol into deriving the same value of k. The client will use a random ephemeral secret of its choice, and the session key that will result between the client and the attacker will not be k; it will be something else, k’.
When the client answers the server challenge passed to it through the attacker, it will not respond with the decrypted value but with a hash of it along with k’, the only session key that it knows. The attacker has no way to recover the decrypted challenge that the real server produced, and hence will not be able to produce a hash of that challenge with k.
Thanks to careful (i.e., paranoid) protocol design, a client can skip caching server keys, connect and trust the absolutely wrong server, and still not provide that server with any authentication credentials. The fraudulent server can still cause the client to divulge other sensitive information, but nothing that will allow it to impersonate the client thereafter.