A recent question in SOPW got me thinking about secure password access to a CGI program from an HTML form in systems lacking SSL. The solutions seem to fall into these categories:

1. Encrypt the password using a one-way hash and store the result in a file on the server. To gain access, the user types the password into an HTML form. The password is then sent as plaintext to the server. The CGI program (or .htaccess mechanism) encrypts the text received and compares it to the encryption in the file for validation.

Advantage: Password is not stored anywhere as plaintext.
Disadvantage: Password is transmitted in plaintext and could be sniffed in transit.

2. Send a random key to the client in a hidden form field. Submit this key and the typed-in password to a JavaScript one-way hash subroutine included with the form and send the encrypted result back to the host. The host also computes the encryption with the same key, along with the password (stored locally) and compares the two results for validation.

Advantage: No plaintext password (or repeated encryptions thereof) are transmitted.
Disadvantage: Plaintext password must be stored on the server.

3. Use a one-time pad. This works like #1, except that it uses multiple random passwords, and no password is used more than once.

Advantage: Very secure.
Disadvantage: Cumbersome to implement due to a constant need to refresh a large password list.

4. Use a commutative one-way hash. Such a hash has the property that

   hash(hash(password, key1), key2) = hash(hash(password, key2), key1)

Thus you can store hash(password, key1) on the server and send (the random) key2 to the client, which computes hash(password, key2) and returns the result. You then rehash this with key1 and rehash the stored hash with key2, comparing the results for authentication.

Advantage: No plaintext password is stored or transmitted.
Disadvantage: See discussion that follows.

To make #4 work in a browser environment, you almost have to implement the commutative hash as a JavaScript function. An example of such a function is RSA encryption, whose commutative properties make digital signatures and the like possible. But this may be prohibitively complicated to do in JavaScript.

A function that has been implemented in Javascript is MD5. MD5 is a "digest" function, that takes an arbitrarily large string and returns a 128-bit (16-byte) result. It also works with short (password-sized) strings and with its own output. This function is also implemented through a Perl module, Digest::MD5 (for the server CGI side). But I question whether there's a direct way to force commutativity. To begin with, MD5 takes a single (string) argument, instead of an argument and a key. Of course, a single string could be formed from an argument concatenated with a key, but there doesn't seem to be a way to do this and preserve commutativity between two random keys.

What I propose here is an indirect way to get commutativity. Suppose you took your password and recursively applied MD5 to it n times and stored the result. We'll call this new function repMD5(password, n). Now, clearly,

   repMD5(password, n) = repMD5(repMD5(password, n - m), m)

for any m < n.

So here's a way to approach the security of #4 above:

5. Encode the password by recursively applying MD5, say, 1000 times and store the result on the server. Each time you send the password entry form to the user, include a number between 1 and 999 in a hidden field. That's the number of times the included JavaScript MD5 routine must recursively hash the typed-in password before sending it back to the server. The server CGI program then finishes the job by hashing it the remainder of the 1000 times and compares the result to the stored string for authentication. Now, the number you send back to the client cannot be random. For maximum security, it always has to be less than the number you used the prior time. Why? Suppose you used 3 the first time, and someone intercepted the transmission from the client. If that person tried to log in and you sent 9, he could merely take the result he observed from 3 and apply MD5 six more times to get the valid result for 9. By using a smaller "key" each time, this is prevented.

So where does this leave us?

Advantage: This method can be used an arbitrarily large (but finite) number of times before a new password has to be assigned. No plaintext is transmitted or stored.
Disadvantage: The function in MD5.pm computes very quickly, but the JavaScript implementation is probably pretty slow. So recursing up to 1000 times, say, on the client side will doubtlessly try the user's patience. Also the need to reassign the password after n uses is similar to the disadvantage cited for the one-time pad, although here the password "list" is only one item long.

I haven't tried any of this yet, but it might be an interesting experiment. What do the other Monks think?

Replies are listed 'Best First'.
(jptxs)Re: Securing Passwords
by jptxs (Curate) on Apr 01, 2001 at 08:13 UTC
    You could build a small java applet to do the MD5 work on the client side. It would be much faster than Javascript and also keep the code your business so it would be harder to figure out what you were up to even if the transmissions were intercepted...
    "A man's maturity -- consists in having found again the seriousness one had as a child, at play." --Nietzsche
      Here's how I deal with it:
      • password is stored crypted on server
      • user sends password which is crypted and checked
      • if ok, append REMOTE_ADDR environment variable, crypt this and send back as a cookie
      • for future requests, read crypted password from file, append REMOTE_ADDR, crypt this and compare with cookie sent

      This means that:

      • unencrypted password is entered only once
      • cookie only works from that IP address
      • if cookie is sniffed, it's not very useful
      Of course, if security is essential, you'd be using SSL with 128bit encryption, wouldn't you, making most of this conversation moot :)

      cLive ;-)

        two thoughts right off the top of my head: DHCP and mobile users. Basically, IP is not a reliable method of identification in any scheme. What you suggest means floating into keeping a state - which is covered very well by merlyn here, in a thread that's somewhat relevant to this too.
        "A man's maturity -- consists in having found again the seriousness one had as a child, at play." --Nietzsche