There are many protocols and techniques in use today which allow secure communication over insecure channel. However, FTP protocol as specified by RFC959 does not support any of them. RFC2228 (issued in October 1997) specifies authentication mechanisms for FTP protocol but so far it does not seem to gain the momentum, and it mostly talks about high-level interactions.
One of the possible solutions is to use SSL (secure socket layer) which acts independently of higher-level protocol; in fact, client and server tools do exist which implement SSL for FTP, but their use is not widespread by any measure. Also there exists Internet draft which tried to adapt SSL-like mechanism for FTP. We aren't going to discuss why SSL is rarely used for FTP transfers, but some of the most important reasons might be performance hit from encoding/decoding of data stream, necessity to modify both clients and servers and US export restrictions. Moreover, in many cases the data transmitted over network aren't valuable enough to encrypt them; the sensitive part of the FTP protocol lies mostly in transmission of plaintext passwords. Here we propose a method for FTP authentication which avoids sending plaintext password over network, does not encrypt data connection, does not have significant overhead on both server and client and allows to fall back to usual authentication method when client or server do not support secure authentication. It is not based on any new ideas; the corresponding authentication schemes have been in use for years, and several attempts were made to apply them to FTP protocol (e.g., SRA).
Note the use of randomizer to provide necessary level of security. Without it, intruder who has intercepted the communication between client and server will not be able to find the client's password, but if server's public key is the same across sessions, intruder will be able to login to that FTP server as client. Use of randomizer makes such attack impossible. Speaking of two most popular PKCSs, RSA and Diffie-Hellman, one has to comment that Diffie-Hellman scheme (also known as ElGamal) has different session keys even if common modulus is used. Therefore ElGamal-based authentication does not need randomizer but it makes sense to use it anyway to increase the length of the message and provide unified interface with cases when RSA is employed.
Suggested negotiations between client and server can go the following way (italicized text shows what is different from RFC959 specifications):
Looking up 'crydee.sai.msu.ru' Found 'crydee.sai.msu.ru' Connecting to 195.208.220.203: Esc - abort, Space - retry... Connected to port 21 220 crydee.sai.msu.ru FTP server (Version 6.00; PKFA-capable) ready. USER asv 331 Password required for asv. CHAL EG1 331 Public key: EG1 2:59A6223674D7A6115932218C49967D6678F91CEF137C4F2E1... PKPS 6CEB6AB1D165B453893FAF171B577EDEE7A26F0BBDCA346A0CF982D6AD58BD0007... 230 User asv logged in. Successfully logged in as 'asv@crydee.sai.msu.ru' SYST 215 UNIX Type: L8 Version: BSD-199506 TYPE I 200 Type set to I.This is how it could work. Apparently there isn't much sense in doing PKFA on anonymous logins, and clients should avoid it to lower server overhead.
NFTP supports ElGamal- and RSA-based PKFA since version 1.62.b1; support for RSA will appear in late September 2000 after patent expiration. Earlier versions of NFTP used RSA authentication which is no longer supported; 1.61 won't work with servers mentioned above.
Here's the fragment from actual FreeBSD ftp client code implementing PKFA:
if (PKFA_capable == 1 && strcmp(user, "anonymous") && strcmp(user,"ftp"))
{
n = command ("CHAL EG1");
if (n == CONTINUE && strstr (reply_string, "Public key: EG1 ") != NULL)
{
puts ("Using secure authentication (PKFA)");
p = strdup (reply_string);
// kill \r or \n at the end of the reply string
p1 = p + strlen (p) - 1;
while (p1 >= p && (*p1 == '\r' || *p1 == '\n')) *p1-- = '\0';
public_key = strstr (p, ": EG1 ") + 6;
randomizer = strchr (public_key, ' ');
if (public_key != NULL && randomizer != NULL)
{
public_key += 6;
*randomizer = '\0';
randomizer++;
p1 = str_join (pass, randomizer);
p2 = eg_encode (p1, strlen (p1), public_key);
free (p1);
n = command ("PKPS %s", p2);
free (p2);
}
else
PKFA_capable = 0;
free (p);
}
else
PKFA_capable = 0;
}
else
{
n = command("PASS %s", pass);
}
You should have no problems writing similar code for your FTP client.
On the server side the main part of my patch for FreeBSD ftpd looks like that:
| CHAL SP method CRLF
{
challenge($3);
free($3);
}
| PKPS SP password CRLF
{
ch_pass($3);
free($3);
}
.............
char *secret_key = NULL, *randomizer = NULL, *method;
void challenge (char *authmethod)
{
char *public_key, *p1, *p2, *methods[16];
int i, nmethods, rc;
if (logged_in || askpasswd == 0)
{
reply(503, "Login with USER before CHAL.");
return;
}
// extract authentication methods from the CHAL command argument
nmethods = 0;
p1 = authmethod;
do
{
p2 = strchr (p1, ',');
if (p2 != NULL) *p2 = '\0';
methods[nmethods++] = strdup (p1);
if (p2 != NULL) p1 = p2 + 1;
}
while (p2 != NULL && nmethods < 16);
if (nmethods == 0) goto no_chal;
// make randomizer (salt). it does need to be truly random
randomizer = malloc (21);
srandom (time(NULL) * getpid() * getppid());
for (i=0; i<20; i++)
randomizer[i] = 'a' + random()%('z'-'a'-1);
randomizer[20] = '\0';
// first we search for ElGamal
for (i=0; i<nmethods; i++)
{
if (strcmp (methods[i], "EG1") == 0)
{
rc = eg_keypair (2, 0, &public_key, &secret_key);
if (rc) goto no_chal;
method = strdup (methods[i]);
goto ok;
}
// uncomment this on 21 Sep 2000 when RSA patent will expire ;-)
/*
if (strcmp (methods[i], "RSA1") == 0)
{
// we're doing on-the-fly key generation. this could
// be slow on slower machines
// 466MHz Celeron has no problems with 1024 bit keys,
// and 512 bit keys are instantaneous on it
rc = rsa_keypair (512, 65537, &public_key, &secret_key);
if (rc) goto no_chal;
method = strdup (methods[i]);
goto ok;
}
*/
}
no_chal:
reply (504, "%s authentication(s) is not enabled/supported", authmethod);
for (i=0; i<nmethods; i++) free (methods[i]);
return;
ok:
reply (331, "Public key: %s %s %s", methods[i], public_key, randomizer);
for (i=0; i<nmethods; i++) free (methods[i]);
return;
}
void ch_pass (char *passwd)
{
char *ps, *p;
int failed, rc;
if (logged_in || askpasswd == 0 || secret_key == NULL) {
reply(503, "Login with USER first.");
return;
}
if (strcmp (method, "EG1") == 0)
{
rc = eg_decode (passwd, secret_key, &ps);
free (secret_key); secret_key = NULL;
if (rc <= strlen (randomizer))
{
reply (530, "Login incorrect.");
return;
}
}
// uncomment this on 21 Sep 2000 when RSA patent will expire ;-)
/*
if (strcmp (method, "RSA1") == 0)
{
rc = rsa_decode (passwd, secret_key, &ps);
free (secret_key); secret_key = NULL;
if (rc <= strlen (randomizer))
{
reply (530, "Login incorrect.");
return;
}
}
*/
else
{
// could not be...
reply(503, "Login with USER first.");
return;
}
/* check randomizer */
failed = 0;
if (strlen (ps) < strlen (randomizer))
{
failed = 1;
}
else
{
// ps: passwordrandomizer
// strlen (ps)=18, strlen (randomizer)=10
// p = ps + 8 = "randomizer"
p = ps + strlen (ps) - strlen (randomizer);
if (strcmp (p, randomizer)) failed = 1;
*p = '\0';
}
free (randomizer); randomizer = NULL;
if (failed)
{
reply (530, "Login incorrect.");
}
else
{
pass (ps);
}
memset (ps, 0, strlen (ps));
free (ps);
}
The private/public key pair must be either stored on server or generated on-the-fly. Since ElGamal session keys are always generated on the fly, this problem does not exist for ElGamal. With RSA it is unreasonable to compute keys on-the-fly on slow machines and they must be stored. Storing keys, however, is a potential security risk (the file with keys must have at least appropriate access rights). One have to note that even compromising security of this file does not mean that the system's security is compromised; however, if intruder has a copy of this file contents it will be able to decrypt user passwords from network packets. It is recommended to generate new keyset from time to time.
Since PKFA does not encrypt data connection, it will not help when you want to transfer data in secure manner. For that purpose you will probably need another technique such as SSL.
PKFA will not protect you from man-in-the-middle attacks when infiltrator not only monitors your talk with server but also forges server responses (though this is usually much more complex than simple eavesdropping). In this sense PKFA is not really an `authentication'. The real thing (as in SSL) will use server certificates which can help client to verify that server isn't fake, but use of certificates requires large supporting infrastructure.
(updated) Both RSA and Diffie-Hellman (ElGamal) algorithms are patent-free now. RSA used to be intellectual property of RSA Data Security Inc. but they have made smart move and have given up on patent, several months before its expiration.
Exporting software which uses strong cryptography requires permission in US (fortunately, there are other countries in the world). These restrictions have been partially lifted recently, but some issues are still not resolved. I strongly believe however that PKFA does not represent a case of software which employs strong cryptography for secure communication; the encryption is only used for authentication.
Further readings: Cryptography from A to Z, The comp.security.pgp FAQ, PGP DH vs. RSA FAQ (read about Diffie-Hellman and RSA), FTP-related Requests for Comments, FTP-related Internet drafts