Implementing login correctly is a surprisingly complicated bit of coding, particularly in an AJAX application. For example, an HTTP-delivered web page can't use HTTPS to talk to the server to protect the password. You can handle this situation in a klunky-but-easy or elegant-but-hard way, but you have to handle it. This article looks at the various obstacles, describe the solutions in code, and points to some important best practices. Next month, I will look at a full implementation of a generic login system that works on both HTTP-delivered and HTTPS-delivered pages.
Storing Passwords
The first issue is universal, and must be done regardless of how you implement login. The fundamental principle is: Never store an unencrypted password in a database.
As a general rule, you should write your code under the assumption that your server is vulnerable. Passwords are particularly sensitive, because many users use the same password for every website they access, (including their bank). Even if the data associated with your particular app isn't particularly sensitive, don't assume that it doesn't much matter if the passwords are secure on your site. You really don't want to give a hacker the password to your users' bank account. Consequently, not even you should know your users' passwords.
This problem is easy to solve using cryptographic hashing. The basic notion is that a cryptographic hash (sometimes called a message digest) is a longish number (typically 128 bits or more) that uniquely identifies a block of text (anything from a book-length chunk of text to a password). Change one byte of the input, and something like half of the bits in the hash should change. The number of hashes is so large that the odds of two different texts hashing to the same value are astronomically low. It really doesn't matter if this collision does happen, because collisions don't affect security in most protocols. Every time you hash the same input, though, you get the same result. Finally, you can't revert from the hash back to the original value. Given a hash of a password, there's no way to "unhash" it to get the original password.
The basic idea is to store a hashed version of the password in the database (Figure 1). When you validate a password, the server hashes the presented password, then compares that hash to the stored hash. If the two hashes match, then the passwords match (Figure 2).
One way to attack a site is to grab the set of hashed passwords, then create hashes of likely passwords (when I was at college, something like 80% of the passwords in use were characters from The Lord of The Rings). Another attack involves taking an entire dictionary of the English language and hashing every word in it. You then compare the hashed dictionary words to the ones that you've stolen. If the hashes match, then you've found the passwords. Similarly, two identical passwords will hash to the same value, so if you break any one of them, you have all of them.
Both of these problems are solved by something called salt. The basic idea is to add a string (the salt) to the password, typically by XORing the characters in the salt to those in the password in somewhat complicated ways. Because usernames are unique, they work pretty well as salt, but you can also just use a random number (cryptographically superior, but annoying in practice becasue it has to be stored somewhere). You should always salt the password before hashing it.
Several cryptographic hash algorithms exist, but some of them (MD5 and SHA1) have known vulnerabilities. Your best bets are the algorithms called SHA-2 (which come in various flavors with varying levels of security SHA-256 is adequate for our purposes) and BCrypt (which is based on the Blowfish encryption algorithm). I've been using a Java BCrypt implementation that I got from http://www.mindrot.org/projects/jBCrypt/.
Create a hash like this:
String hashed = BCrypt.hashpw( password, BCrypt.genSalt() );
The genSalt() method returns a random number, but you could use the username if you like. Verify the password like this:
if( BCrypt.checkpw(candidatePassword, hashed) ) // candidate matches
The string returned from hashpw() has the salt encoded into it, so it doesn't have to be stored separately.
Just for fun, and to demonstrate why you don't want to use them, here's part of what you'd do to accomplish the same thing with the Java Encryption APIs:
byte[] salt = new byte[]{ (byte)0x3a, (byte)0x44, ... , (byte)0x31 };
int iterations = 1000; // should be >= 1000
char[] password = getPasswordFromUser();
PBEKeySpec spec = new PBEKeySpec( password, salt, iterations );
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = factory.generateSecret( spec );
Cipher c = Cipher.getInstance("PBEWithMD5AndDES");
byte[] hashedValue = c.doFinal( plaintext );


