Security enhancement proposal for FTP protocol authentication (PKFA)

Introduction

Recently the issues of computer security have received much attention from users and software vendors. One of the basic operations involved in the networking environment is user authentication; it is usually performed by exchanging login/password information. In many protocols (including FTP) this information is transmitted over the network in plaintext form. This creates significant security risk as under normal circumstances network packets are not encrypted, and everybody with access to physical line can monitor packet contents and extract password information from them. In fact, use of such network packet monitoring tools is quite common among attackers.

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).

Extension of FTP protocol for secure authentication

The proposed method is based on public key cryptosystem (PKCS). There are several PKCS in use today and any of them can be used for our purpose. When user logs in to FTP server, the server sends its public key and another string called randomizer. Both need not be encrypted. The client concatenates the password with randomizer, then encrypts the result with server's public key and sends it back (the suggested order of concatenation is password-randomizer, with no intervening symbols). Server uses its private key to decrypt the password, discards randomizer part after verification and then performs all necessary authentication checks. In this scheme, password is transmitted in encrypted form and the private key is needed to decrypt it. Since relatively short password is the only data which is encrypted there's virtually no performance hit on both client and server. I have called this method PKFA (public key FTP authentication).

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):

  1. Client connects to server and waits for protocol prompt string.

  2. Server sends its prompt string and waits for commands. To simplify detection of PKFA-capable servers, the server prompt could contain words "PKFA-capable".

  3. Client sends USER command, with login name as specified by RFC959.

  4. Server accepts login name and then waits for either PASS command or CHAL command.

  5. If client decides to try PKFA, it sends CHAL command with single argument specifying supported PKCSs, e.g.
    CHAL RSA1,EG1
    Otherwise client proceeds with usual PASS command and the rest of the process isn't different from RFC959.

  6. After receiving CHAL command, server checks for supported PKCS among specified. If none of PKCSs specified in CHAL command are supported by server, it returns 5xx reply indicating that secure authentication cannot be negotiated. Otherwise it sends 3xx response in the form
    331 Public Key: <space> <PKC-name> <space> <public-key> <space> <randomizer>
    for example,
    331 Public Key: RSA1 1781F:E893 hjbuywueydgwe
    (the example shows very very short key). If server does not support PKFA at all, it replies with 5xx (command not understood) to CHAL command.

  7. If client receives 5xx response it decides that server is either incapable of PKFA at all, or cannot speak PKCSs which are supported by client. The client can then decide whether to repeat authentication process with insecure authentication or refuse to connect at all. Otherwise client will use server's public key and randomizer to encrypt the password and send the result back with
    PKPS <space> <encrypted-password>

  8. Upon receiving PKPS command, server decrypts client's response with its private key and performs authorization as in RFC959.
The negotiation looks as (the example is taken from real experimental implementation):
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.

Sample implementation

I have provided sample implementation of PKFA support in the form of modified version of wu-ftpd 2.6.1. Retrieve patched version of wu-ftpd version 2.6.1 from ftp.ayukov.com/pub/nftp/support unpack, build (./configure; make) and install it as usual (if you system is shipped with preconfigured version of wu-ftpd it is usually sufficient to replace server binary only). You are now ready to try PKFA. This implementation uses ElGamal and configured to use 768 bit keys by default. Patches are also available for FreeBSD server (bsd-ftpd-PKFA-pl2.patch.gz) and command-line ftp client (bsd-ftp-PKFA-pl2.patch.gz). RSA code is commented out; uncomment it on 20 Sep 2000 when patent will expire :-)

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.

Adding PKFA support to FTP clients and servers

It is fairly simple to add PKFA support to FTP client or server when source code is available. I have provided all necessary routines for ElGamal and RSA algorithms conveniently wrapped into easy-to-use library. It is based on Eric Young's SSLeay and therefore falls under its license: use freely, give credit and don't relicense. The library is contained in two files scl.c and scl.h which can be found in wu-ftpd distribution 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);
}

Problems

There are several problems connected with this approach. Some of them are technical and some are legal.

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


return to essays | return to homepage | send comment