GlassFish JDBC Security with Salted Passwords on MySQL

Markus Eisele
12
One of the most successful posts on this blog is my post about setting up a JDBC Security Realm with form based authentication on GlassFish. Some comments on this post made me realize that there is more to do to actually make this secure as it should be.

Security out of the box
Picture: TheKenChan (CC BY-NC 2.0)
GlassFish comes with a GlassFish JDBC Realm already. All you have to do is to initialize a database and get the security configuration right and you are done. Among the standard configuration you have the option to define a digest-algorithm (including encoding and charset). The digest-algorithm can be any JDK supported MessageDigest (MD2, MD5, SHA-1, SHA-256, SHA-384, SHA-512). Compare my JDBC Security Realm post for a complete setup.

What is weak or missing?
The out of the box solution goes a very trivial way. It simply hashes the password. There are many ways to recover passwords from plain hashes very quickly. The simplest way to crack a hash is to try to guess the password, hashing each guess, and checking if the guess's hash equals the hash being cracked. If the hashes are equal, the guess is the password. The two most common ways of guessing passwords are dictionary attacks and brute-force attacks. Also very widely know are the Lookup tables. They are an effective method for cracking many hashes of the same type very quickly. The general idea is to pre-compute the hashes of the passwords in a password dictionary and store them, and their corresponding password, in a lookup table data structure. But we are not done now. You also find something called Reverse Lookup Tables. This attack allows an attacker to apply a dictionary or brute-force attack to many hashes at the same time, without having to pre-compute a lookup table. And last but not least the Rainbow Tables attack. They are like lookup tables, except that they sacrifice hash cracking speed to make the lookup tables smaller. Very impressive list of approaches. Clearly this doesn't meet my personal need for securing passwords.

Adding some Salt
The above approaches work because of the fact that each password is hashed in the exact same way. Every time you run a password through the secure hash function it produces the exact same output. One way to prevent this is to add some salt to it. Appending or prepending a random string to the password before hashing it would solve this. This random string is referred to as "salt". Be aware that reusing the salt for all passwords is not secure. You can still use rainbow tables or dictionary attacks to crack them. So you have to randomize the salt for every password and store it beside the hashed password. And it needs to change every time a user updates his password. A short sentence about length. Salts shouldn't be too short. For the most effective length would be the same size as the password hash. If you use a SHA512 (512/8bit=64 bytes) you should choose a salt with at least 64 random bytes long.

Preparations
We are clearly leaving the standard JDBCRealm features now. Which means we have to implement our own security realm. Let's call it UserRealm from now on. Let's start with the same setup we have for the JDBCRealm. A MySQL database with a "jdbcrealmdb" schema. Only difference here, we prepare to save the salt with every password.
USE jdbcrealmdb;
CREATE TABLE `jdbcrealmdb`.`users` (
`username` varchar(255) NOT NULL,
`salt` varchar(255) NOT NULL,
`password` varchar(255) DEFAULT NULL,
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE `jdbcrealmdb`.`groups` (
`username` varchar(255) DEFAULT NULL,
`groupname` varchar(255) DEFAULT NULL)
ENGINE=InnoDB DEFAULT CHARSET=utf8; 
CREATE INDEX groups_users_FK1 ON groups(username ASC);

Now we implement the basic realm. The following code simply shows the mandatory members. I am going to make the source available during the next days. Until today this post is anything that is available for you.  on github.com.

public class UserRealm extends AppservRealm {
/**
* Init realm from properties
*/
public void init(Properties props) 
/**
* Get JAASContext
*/
public String getJAASContext() 
/**
* Get AuthType
*/
public String getAuthType() 
/**
* Get DB Connection
*/
private Connection getConnection()
/**
* Close Connection
*/
private void closeConnection(Connection cn)
/** 
* Close prepared statement
*/
private void closeStatement(PreparedStatement st)
/** 
* Make the compiler happy.
*/
public Enumeration getGroupNames(String string)
/** 
* Authenticate the user
*/
public String[] authenticate(String userId, String password) 

But the most important part is missing here.

Setting up some tests
I'm not exactly the kind of test driven guy but in this case it actually makes sense. Because the realm I am going to implement here doesn't support user-management via the GlassFish admin console. So the basic requirement is to have a prepared database with all the users, passwords and salts in place. Let's go. Add the sql-maven-plugin and let it create the tables during test-compile phase.
 <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>sql-maven-plugin</artifactId>
                <version>1.3</version>
                <dependencies>
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>5.1.18</version>
                    </dependency>
                </dependencies>
                <configuration>
                    <driver>${driver}</driver>
                    <url>${url}</url>
                    <username>${username}</username>
                    <password>${password}</password>
                    <skip>${maven.test.skip}</skip>
                    <srcFiles>
                        <srcFile>src/test/data/drop-and-create-table.sql</srcFile>
                    </srcFiles>
                </configuration>
                <executions>
                    <execution>
                        <id>create-table</id>
                        <phase>test-compile</phase>
                        <goals>
                            <goal>execute</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
You can either use some db-unit magic to insert the test-data into your database or do this within your test-cases. I decided to go this way. First let us put all the relevant JDBC stuff to a separate place called SecurityStore. We basically need three methods. Add a user, get the salt for a user and validate the user.

 private final static String ADD_USER = "INSERT INTO users VALUES(?,?,?);";
    private final static String SALT_FOR_USER = "SELECT salt FROM users u WHERE username = ?;";
    private final static String VERIFY_USER = "SELECT username FROM users u WHERE username = ? AND password = ?;";
//...
public void addUser(String name, String salt, String password) {
        try {
            PreparedStatement pstm = con.prepareStatement(ADD_USER);
            pstm.setString(1, name);
            pstm.setString(2, salt);
            pstm.setString(3, password);
            pstm.executeUpdate();
        } catch (SQLException ex) {
            LOGGER.log(Level.SEVERE, "Create User failed!", ex);
        }
    }

    public String getSaltForUser(String name) {
        String salt = null;
        try {
            PreparedStatement pstm = con.prepareStatement(SALT_FOR_USER);
            pstm.setString(1, name);
            ResultSet rs = pstm.executeQuery();

            if (rs.next()) {
                salt = rs.getString(1);
            }

        } catch (SQLException ex) {
            LOGGER.log(Level.SEVERE, "User not found!", ex);
        }
        return salt;
    }

    public boolean validateUser(String name, String password) {
        try {
            PreparedStatement pstm = con.prepareStatement(VERIFY_USER);
            pstm.setString(1, name);
            pstm.setString(2, password);
            ResultSet rs = pstm.executeQuery();
            if (rs.next()) {
                return true;
            }
        } catch (SQLException ex) {
            LOGGER.log(Level.SEVERE, "User validation failed!", ex);
        }
        return false;
    }
In order to not implement too much here I decided to have two separate constructors:
public SecurityStore(String dataSource) 
public SecurityStore(String user, String passwd)
So this will work with both, the app-server and my local tests. Next is the actual password and salt logic.

Working with Passwords, Hashes and Salts
Here is what I came up with:
public class Password {

    private SecureRandom random;
    private static final String CHARSET = "UTF-8";
    private static final String ENCRYPTION_ALGORITHM = "SHA-512";
    private BASE64Decoder decoder = new BASE64Decoder();
    private BASE64Encoder encoder = new BASE64Encoder();

    public byte[] getSalt(int length) {
        random = new SecureRandom();
        byte bytes[] = new byte[length];
        random.nextBytes(bytes);
        return bytes;
    }

    public byte[] hashWithSalt(String password, byte[] salt) {
        byte[] hash = null;
        try {
            byte[] bytesOfMessage = password.getBytes(CHARSET);
            MessageDigest md;
            md = MessageDigest.getInstance(ENCRYPTION_ALGORITHM);
            md.reset();
            md.update(salt);
            md.update(bytesOfMessage);
            hash = md.digest();

        } catch (UnsupportedEncodingException | NoSuchAlgorithmException ex) {
            Logger.getLogger(Password.class.getName()).log(Level.SEVERE, "Encoding Problem", ex);
        }
        return hash;
    }

    public String base64FromBytes(byte[] text) {
        return encoder.encode(text);
    }

    public byte[] bytesFrombase64(String text) {
        byte[] textBytes = null;
        try {
            textBytes = decoder.decodeBuffer(text);
        } catch (IOException ex) {
            Logger.getLogger(Password.class.getName()).log(Level.SEVERE, "Encoding failed!", ex);
        }
        return textBytes;
    }
}
Pretty easy, right? To be honest: Working with the byte[] could be hidden better, but I thought you will easier understand what is happening here. The salt() method returns a secure random salt of the configured length. The hashWithSalt() method puts everything into one SHA-512 hashed password.

A word about endcodings
I decided to Base64 encode it and I am using the proprietary API (sun.misc.BASE64Decoder, Encoder). You should think about using apache commons here. But it was the easiest way to do it. Another approach is to simply HEX encode (zero-pad) everything. The difference between Base64 and  HEX  is really just how bytes are represented.  HEX  is another way of saying "Base16".  HEX  will take two characters for each byte - Base64 takes 4 characters for every 3 bytes, so it's more efficient than hex. Assuming you're using UTF-8 to encode the XML document, a 100K file will take 200K to encode in hex, or 133K in Base64.

And finally the missing method in the UserRealm
The very final part of this lengthy post is the authenticate method in the UserRealm class.
    /**
     * Authenticates a user against GlassFish
     *
     * @param name The user name
     * @param givenPwd The password to check
     * @return String[] of the groups a user belongs to.
     * @throws Exception
     */
    public String[] authenticate(String name, String givenPwd) throws Exception {
        SecurityStore store = new SecurityStore(dataSource);
        // attempting to read the users-salt
        String salt = store.getSaltForUser(name);

        // Defaulting to a failed login by setting null
        String[] result = null;

        if (salt != null) {
            Password pwd = new Password();
            // get the byte[] from the salt
            byte[] saltBytes = pwd.bytesFrombase64(salt);
            // hash password and salt
            byte[] passwordBytes = pwd.hashWithSalt(givenPwd, saltBytes);
            // Base64 encode to String
            String password = pwd.base64FromBytes(passwordBytes);
            _logger.log(Level.FINE, "PWD Generated {0}", password);
            // validate password with the db
            if (store.validateUser(name, password)) {
                result[0] = "ValidUser";
            }
        }
        return result;
    }
That is all left to do here. If we have a salt for a given user-name we generate a hashed password which we are going to check against the one we have in the database. The getSaltForUser() also is our implicit check for the existence of the user.

Making password cracks even harder: Slow Hash Functions
Security wouldn't be called security if they wouldn't add more to it. So, salted passwords are way better than simply hashed ones but still probably not enough because they still allow for brute-force or dictionary attacks on any individual hash. But you can add more protection. The keyword is key-stretching. Also known as slow hash functions. The idea here is to make computation slow enough to no longer allow for CPU/GPU driven attacks. It is implemented using a special CPU-intensive hash function. PBKDF2 (Password-Based Key Derivation Function 2) is one of them. You can use it in different ways but one warning: never try to do this at your own. Use one of the tested and provided implementations like the PBKDF2WithHmacSHA1 from the JDK or the PKCS5S2ParametersGenerator from the Bouncycastle library. An example could look like this:
    public byte[] hashWithSlowsalt(String password, byte[] salt) {
        SecretKeyFactory factory;
        Key key = null;
        try {
            factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
            KeySpec keyspec = new PBEKeySpec(password.toCharArray(), salt, 1000, 512);
            key = factory.generateSecret(keyspec);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException ex) {
            Logger.getLogger(Password.class.getName()).log(Level.SEVERE, null, ex);
        }
        return key.getEncoded();
    }

Why all that?
We hear about password and user database leaks a lot. Every day. Some big sites have been hit and it basically is up to the implementer to provide suitable security for his users. Knowing where and how to tweak can be difficult and honestly using the provided features left you behind with a wrong comfortable feeling. Don't stop learning about security features and keep an eye open for possible problems. I personally wish GlassFish would provide a more comprehensive set of default realms for users to work with. But as long as this isn't the case my blog is the only way to guide you into the right direction. Hope you enjoyed it!

[UPDATE 31.07.2012]
The source is on github.com

Post a Comment

12Comments

  1. Hi,

    May I ask u to give us some start to deploy the custom realM inisde GlassFish?

    Cheers

    ReplyDelete
    Replies
    1. Hi Nico,

      that's easy :)
      1. copy realm.jar /lib
      2. Start Glassfish (login as admin)
      3. Add your Realm to the list of known Glassfish realms by navigating into the
      Configuration->Security->Realms

      Also compare the official documentation:
      http://docs.oracle.com/cd/E18930_01/html/821-2418/beabo.html

      Have fun!
      - m

      Delete
    2. Markus,

      I try to do as u said... I got an LoginModule missing expection . I start to implement it as describe here: http://docs.oracle.com/cd/E19798-01/821-1752/beabs/index.html. Does I missing something? Do u agree a Custom loginModule is mandatory in order to use you pretty good RealM?

      Cheers,

      Delete
    3. Hi Nico,

      of course. That is mandatory. You need both, the login.conf entry which points to the LoginModule and the LoginModule which calls the UserRealm. You can keep this simple:
      - Do some checks for empty username
      - set final UserRealm userRealm = (UserRealm) _currentRealm;
      - call
      String[] grpList = userRealm .authenticate(_username, getPasswordChar());

      Compare AppservPasswordLoginModule

      - M

      Delete
    4. This comment has been removed by a blog administrator.

      Delete
    5. This comment has been removed by the author.

      Delete
  2. Hi Markus,

    At end it work. Thank you for getting back to me, in spite of your busy schedule ;).

    I will stay tune on your blog...

    Have a Nice day

    ReplyDelete
    Replies
    1. Hi Markus,

      I start to learn "How to logout". Do u have any advice?

      kd

      Delete
  3. Hi Markus!

    i'm quite new in Java EE, but i'm interested in doing things well

    Do you any book that cover this theme in more detail?
    Any recommendation?

    Because i couldn't figure out how to implement the code you posted here in a project.

    Thanks a lot!

    ReplyDelete
    Replies
    1. Hi Caleb, thanks for your response. The code is on github so you should be able to run this.
      Beside this, there are some good books around. Search my blog for masoud kalalis security book!

      -m

      Delete
    2. Thanks a lot Markus

      Delete
  4. This comment has been removed by a blog administrator.

    ReplyDelete
Post a Comment