Strong Authentication Method

Author: Carl Sassenrath
Return to REBOL Cookbook

Here is a strong authentication method that you can use for networking (or other things like application password files). This method has several benefits:

  1. It uses 160 bit authentication values with SHA1 hashing.
  2. The password itself is never exposed (never sent over the network) nor is even the length of the password exposed.
  3. The authenticator (the server) does not actually know the password, which keeps the actual password string safe even if the server is cracked. (People tend to reuse the same passwords for many things, so this feature is very important.)
  4. If a username does not exist, the server "fakes" the authentication to prevent determination of valid usernames.
  5. It's extremely fast. It takes advantage of the highly secure random number and checksum functions of REBOL.

The basic technique used here is called a password challenge. Here are the three main functions that make it work:

    make-salt: does [
        checksum/secure form random/secure to-integer #{7fffffff}

    encode-pass: func [pass salt] [
        checksum/secure append to-binary pass salt

    encode-challenge: func [pass challenge] [
        checksum/secure encode-pass pass challenge

The first function creates a highly secure 160 bit random number. The second function combines a password with a random number to create a highly encoded 160 bit password value. This is not reversible. That is, you cannot get the password back from the resulting number. The third function computes a challenge value based on an encoded password. (See below.)

On the server, there is a table of users that includes not only user names, but also an encoded password and the random number that was used to encode it. Here is the function to make a new user on the server:

    make-user: func [user pass /local salt users] [
        users: load %user-db.r
        if find users user [print "User already exists" exit]
        salt: make-salt
        pass: encode-pass pass salt
        repend users [user reduce [salt pass]]
        save %user-db.r users

Notice that the password is not saved in the user-db file, only the encoded password is saved, and it is not reversible. Also, note that you'll need to create an empty user-db.r file to start.

Now, to authenticate a user, you will follow these steps:

  1. The client sends a username to the server.
  2. The server looks up the username, generates a new random number, and sends the number back to the client along with the number that was used to encode the password. This is called the challenge.
  3. The client encodes the users password (in the same way the server did), then again encodes it using the challenge. It then sends the result back to the server.
  4. The server now takes the client's result and compares it to what it gets by encoding the password with the challenge. If the two values match, then the client password is authentic.

Here's the first step, a function that runs on the server:

    make-challenge: func [user /local users auth] [
        users: load %user-db.r
        if not auth: select users user [
            auth: reduce [make-salt make-salt] ; invalid user, fake it
        reduce [auth/1 make-salt] ; user-salt and challenge

This function returns a block that contains the user salt value (that encoded the password) and the challenge. The client must now take the original user password string (as was entered by the user) along with the server challenge and generate a result that will be sent back to the server:

    answer-challenge: func [pass challenge] [
        pass: encode-pass pass challenge/1
        encode-challenge pass challenge/2

Now when the server gets this response, the server must verify it:

    verify-challenge: func [user challenge response] [
        users: load %user-db.r
        if not auth: select users user [return false]
        response = encode-challenge auth/2 challenge

This function returns true if it got a valid response (it is authenticated).

To test the above code, here is an example that runs them:

    ;-- Server does this:
    random/seed now
    save %user-db.r [] ; create new user-db
    make-user "carl" "lober"

    ;-- Client asks to authenticate "carl" by sending the user
    ;   name to the server. The server then does this:
    user: "carl" ; (received from client)
    challenge: make-challenge user ; sent back to client

    ;-- Client must now answer the challenge and send it back to server:
    response: answer-challenge "lober" challenge

    ;-- Server then verifies the response:
    either verify-challenge user challenge/2 response [
        print "You may enter."
        print "You may not enter."

To try it yourself, just copy all the above functions and test code, paste it to a file, add a REBOL header, and run it.

Note that before it begins, it randomizes REBOL's random number generator with the line:

    random/seed now

This prevents the standard reproducable random sequence, so the result is more secure.

2006 REBOL Technologies REBOL.com REBOL.net