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:
- It uses 160 bit authentication values with SHA1 hashing.
- The password itself is never exposed (never sent over the
network) nor is even the length of the password exposed.
- 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.)
- If a username does not exist, the server "fakes" the
authentication to prevent determination of valid usernames.
- 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:
- The client sends a username to the server.
- 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.
- 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.
- 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:
This prevents the standard reproducable random sequence, so
the result is more secure.
|