Canned Nerd

Now with extra sparkles.

How to use salted secure hashes as passwords

| 0 comments

So the information that storing plaintext passwords is not such a good idea has put you into search mode? Even the quickest of all searches will bring you p.e. to the OWASP-page, which tells you that you should use some good hashing (no, MD5 is not considered good anymore). Even better if it’s salted. Even better if it’s multiply hashed. The page even has a complete example, which makes it much more useful than most other pages, I’ve seen regarding this subject. Sure, there are more pages with examples, but they all share a common problem with the OWASP-Page: their examples try to avoid all dependencies. So instead of using great existing libraries, they stubbornly implement almost everything in there. They use Arrays. And sometimes they even implement the concatenation of two byte-arrays. If you’ve ever wondered if you are the first one to stumble upon this, you’re not. Of course, you do not have to implement Array-copying these days anymore: apache commons to the rescue!

You always store the salt together with the password’s hash in the database. Together, they are checked (in the business layer!) for correctness against the password that’s used to login.
Here’s a full-working example, that uses passwords as suggested by modern security people. Just put it into a LoginExample.java file and there you go.


import java.util.HashMap;
import java.util.Map;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;

/**
 * Contains the login information as it's stored in the database
 */
class LoginDBO {

    private final String      _username;
    private final HashAndSalt _hashSalt;

    LoginDBO(String username, HashAndSalt hashSalt) {
        _username = username;
        _hashSalt = hashSalt;
    }

    public String getUsername() {
        return _username;
    }

    public HashAndSalt getHashSalt() {
        return _hashSalt;
    }

}

/**
 * Hold both hash and salt together.
 */
class HashAndSalt {

    private final String _hash;
    private final String _salt;

    HashAndSalt(String hash, String salt) {
        _hash = hash;
        _salt = salt;
    }

    public String getHash() {
        return _hash;
    }

    public String getSalt() {
        return _salt;
    }
}

/**
 * Simple full working example of business-layer implementation with fake-database.
 */
public class LoginExample {

    /** Hold user-information */
    private final Map _fakeDatabase;

    public LoginExample(Map fakeDatabase) {
        _fakeDatabase = fakeDatabase;
    }

    /** Check whether or not the given username/password-combination is valid*/
    public boolean login( String username, String password) {
        final LoginDBO loginDBO = _fakeDatabase.get(username);
        if (loginDBO == null) {
            return false;
        }

        final String hash = digest(password + loginDBO.getHashSalt().getSalt());

        if (StringUtils.equals(hash, loginDBO.getHashSalt().getHash())) {
            return true;
        }

        return false;
    }

    private static HashAndSalt createPassword(String password) {
        /*THANKS APACHE COMMONS: */
        final String salt = RandomStringUtils.randomAscii(10);
        final String hash = digest(password + salt);
        return new HashAndSalt(hash, salt);
    }

    private static String digest(String input) {
        String output = input;
        for (int i = 0; i < 10; i++ ) {
            /*THANKS APACHE COMMONS: */
            output = DigestUtils.sha256Hex(output);
        }
        return output;
    }

    private static void addUser(final Map fakeDatabase, LoginDBO loginDBO) {
        fakeDatabase.put(loginDBO.getUsername(), loginDBO);
    }

    public static void main(String[] args) {
        final Map fakeDatabase = new HashMap();
        addUser(fakeDatabase, new LoginDBO("testUser1", createPassword("simplePassword")));
        addUser(fakeDatabase, new LoginDBO("testUser2", createPassword("whatever")));
        addUser(fakeDatabase, new LoginDBO("testUser3", createPassword("hey ho")));
        addUser(fakeDatabase, new LoginDBO("testUser4", createPassword("123")));

        final LoginExample example = new LoginExample(fakeDatabase);
        System.out.println("These two logins should work:");
        System.out.println("Existing user /w correct password: " + example.login("testUser1", "simplePassword"));
        System.out.println("Other existing user /w correct password: " + example.login("testUser3", "hey ho"));

        System.out.println("These two logins should fail:");
        System.out.println("Existing user /w wrong password: " + example.login("testUser1", "simplePasswordWithTypo"));
        System.out.println("Inexistent user: " + example.login("inexistantUser", "doesntMatter"));
    }
}

That’s all folks.