6. Hashed Passwords in Database
In this chapter, we create a Java EE 7 JSF application which uses hashed passwords which are stored in the database based on Octopus.
This must give you a very good idea how you can sue such use case in production systems, although we will be using the embedded database facilities of the application server.
6.1 Main scenario
The code for this example can be found in the gitbook/ex6 directory of this Git Repository.
6.1.1 Basic Octopus setup
Instead of repeating all the different steps to define the basic requirements of a Java EE 7 JSF application with the Octopus framework, I refer to them in chapter 1. Just repeat the step 1.1.1 to 1.1.6 (included) but use for example the directory name ex6.
6.1.2 Define User entity and Boundary
We will need to store some information in the database and thus need a JPA entity and Queries using the entityManager.
Create the class User within the package be.atbash.ee.security.octopus.book.ex6 with the contents
@Entity @Table(name = "t_user") public class User implements Serializable { @Id @Column @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column private String userName; @Column private String passwordHash; @Column private String salt; // standard getters and setters. }
Create the boundary for retrieving the User from the database.
@Stateless @PermitAll public class UserBoundary { @PersistenceContext private EntityManager em; public void save(User user) { em.persist(user); } public User findUserByUserName(String userName) { TypedQuery<User> query = em.createQuery("SELECT u FROM User u WHERE u.userName = :userName", User.class); query.setParameter("userName", userName); List<User> resultList = query.getResultList(); if (resultList.isEmpty()) { return null; } else { return resultList.get(0); } } }
6.1.3 Define Octopus data
Octopus asks the authentication and authorization information through a CDI bean implementing the be.c4j.ee.security.realm.SecurityDataProvider interface. In this case, it will use the UserBoundery to retrieve the user information.
Create the be.atbash.ee.security.octopus.book.ex6.ApplicationSecurityData class with the following contents.
@ApplicationScoped public class ApplicationSecurityData implements SecurityDataProvider { @Inject private UserBoundary userBoundary; @Inject private SaltHashingUtil saltHashingUtil; @Override public AuthenticationInfo getAuthenticationInfo(AuthenticationToken authenticationToken) { if (authenticationToken instanceof UsernamePasswordToken) { UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken; User user = userBoundary.findUserByUserName(usernamePasswordToken.getUsername()); AuthenticationInfoBuilder authenticationInfoBuilder = new AuthenticationInfoBuilder(); authenticationInfoBuilder.principalId(1L).name(user.getUserName()); authenticationInfoBuilder.salt(saltHashingUtil.base64Decode(user.getSalt())); authenticationInfoBuilder.password(user.getPasswordHash()); // Just for demo purposes so that we can show it on the screen. Do not use this in production code authenticationInfoBuilder.addUserInfo("salt", user.getSalt()); authenticationInfoBuilder.addUserInfo("hash", user.getPasswordHash()); return authenticationInfoBuilder.build(); } return null; } @Override public AuthorizationInfo getAuthorizationInfo(PrincipalCollection principalCollection) { return null; } }
6.1.4 Create database table and load some data
We are using the embedded database facility so we need some way to populate the database with demo data. We also need to define the JPA configuration so that the database table is created when the application starts.
Define the JPA configuration within the file /src/main/resources/META-INF/persistence.xml
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"> <persistence-unit name="demoPU" transaction-type="JTA"> <properties> <property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/> <property name="javax.persistence.schema-generation.create-source" value="metadata"/> <property name="javax.persistence.schema-generation.drop-source" value="metadata"/> </properties> </persistence-unit> </persistence>
Define the demo data with an EJB which initializes itself at application startup, class be.atbash.ee.security.octopus.book.ex6.PopulateDatabase
@Startup @Singleton public class PopulateDatabase { @Inject private SaltHashingUtil saltHashingUtil; @Inject private UserBoundary userBoundary; @PostConstruct public void init() { User user = new User(); user.setUserName("admin"); byte[] salt = saltHashingUtil.nextSalt(); user.setSalt(saltHashingUtil.base64Encode(salt)); user.setPasswordHash(saltHashingUtil.hash("admin", salt)); userBoundary.save(user); } }
6.1.5 Configure hashing of Octopus framework
We need to configure the Octopus Framework so it will be using hashing.
Create the configuration file /resources/octopusConfig.properties with the following content.
saltLength=32 hashAlgorithmName=SHA-256
6.1.6 Additional info about hashing and salt
We can update The example so that we can see a little bit more information about the hashing and the salt which is being used in this case.
Create a Java class to retrieve user info, create the be.atbash.ee.security.octopus.book.ex6.UserBean class. (it is defined as a CDI named bean)
@Model public class UserBean { @Inject private UserPrincipal userPrincipal; public String getSalt() { return userPrincipal.getUserInfo("salt"); } public String getHash() { return userPrincipal.getUserInfo("hash"); } }
Update the user.xhtml body contents to the following information
Secured area <br/> user = #{loggedInUser} <br/> Database stored information <br/> salt : #{userBean.salt} <br/> hash : #{userBean.hash} <br/>
6.1.6 Test the Application
- Deploy the application on a Java EE 7 compliant application server capable of running on Java 8.
- Open the browser pointing to the following URL : http://
/ex6/index.xhtml - Click on the user page link
- Fill in the username admin and password admin in the login page
- Click on the Login button
- See if you end up on the user page and see your username displayed here and also the hash and salt of the admin user.
6.2 explanation
6.2.1 Basic setup
For an explanation of the steps in the basic setup, I also refer to the explanation which can be found in the sections 1.2.1 through 1.2.6.
6.2.2 User information in Database
In this example, we retrieve the user information from a database table, just as you would do it in a production situation. For each user, we need at least 3 things
- the userName
- the hash of the salted password
- the salt used within the hash
This information is defined by the JPA entity which we have defined in the class User. We used here general JPA functionality by using the annotations @Entity, @Table, @Column and so on.
We also define a boundary class which has methods for saving and retrieving the User entity to and from the database table. These are defined within the class UserBoundary. Also here we used standard EJB functionality to persist an entity and execute an JQL query to retrieve the information of the user identified by the username.
In order to use the JPA entity and boundary, we need to specify the location of the database. This is done by the persistence.xml file. In this demo, we used the special situation where we do not specify any jta_data_source. Since Java EE 7 this links then to the embedded database of the application server. The table (t_user in our example) is created automatically because we have specified some javax.persistence.schema-generation properties.
In real-world situations, we would never use the embedded database and the auto-creation feature of JPA since that would recreate all our tables in case we redeploy or restart the application.
The last thing we need to do is to fill the table with some data so that we can test the application. We have there created an EJB which 'initialize' itself when the application starts. Here we create a single user with username and password equals to admin. The password isn't stored in plain text, but hashed, using a salt. See the next section for an explanation.
The nextSalt() method returns a byte array of the length specified by the configuration parameter saltLength which we have specified within the configuration file octopusConfig.properties. This byte array is used as such for the salting, but to store it in the database fields, we do a BASE64 encoding on it so it contains only regular characters (which make it easier to store)
6.2.3 Salting of the password
When we just store the hash of a password, the password would not be very safe for attackers. If the attacker knows the hashing algorithm, it just could use some lookup tables (called rainbow table) to see which password matches the hash he has found.
When we add the salt bytes at the end of the password, for example, rainbow tables can't be used anymore. And even when the attacker also gets hold of the salt, which should be different for each user, the only option is a brute force attack which is of course not very fast.
When you want to use the password hashing facilities of Octopus, you should make 2 configuration entries
- saltLength defines the length (in bytes) of the salts which can be generated.
- hashAlgorithmName defines the name of hashing algorithm which needs to be used. The supported values for this configuration parameter are the names supported by the java.security.MessageDigest (JVM defaults or provider based). Good candidates are SHA-256, SHA-384, and SHA-512.
Based on that configuration, the following 3 lines give you a (cryptographic safe) secret and can calculate a hash for the password.
byte[] salt = saltHashingUtil.nextSalt();
user.setSalt(saltHashingUtil.base64Encode(salt));
user.setPasswordHash(saltHashingUtil.hash("admin", salt));
6.2.4 Retrieving hash information for authenticationToken
When you have looked at the examples in the previous chapters, you know by know that you need to supply the necessary information to the Octopus framework to validate an authentication attempt by the end user using credentials.
For that purpose, we have now an implementation of SecurityDataProvider which retrieves the database information for that user.
First, we try to find the hashed password and salt for the user (using our UserBoundery we have created
That information is placed in the AuthenticationInfo (through the builder)
authenticationInfoBuilder.salt(saltHashingUtil.base64Decode(user.getSalt()));
authenticationInfoBuilder.password(user.getPasswordHash());
As you can see, the code is demo quality code since it doesn't take into account that the user retrieved by the UserBoundary can be null and thus NullpointerException will be thrown when an unknown user tries to log on.
To show this hashed password and salt information on the user page after authentication, that information is also set as user information.
6.2.5 Authentication process
How works the Authentication process? Since the password matchers defined in Octopus sees the hashAlgorithmName parameter, it will use the algorithm to create the hash value based on the salt and the user-supplied value (value entered within the login screen in the password field). The values are all supplied to Octopus by means of the AuthenticationInfo(Builder). This hash must match then the value also supplied by the Builder.
6.3 Where to go next?
- Try the alternatives (see 6.4) of this example to learn about the more advanced use cases.
- Add remember-me functionality to the authentication process. (See 7)
- Discover the other integrations for authentication (LDAP, OAuth2, ...) (See ???)
- Learn about the core features of Octopus, Authorization. (See ???)
6.4 Alternatives
There are some variations on the main scenario possible, just by changing a configuration parameter.
6.4.1 Key derivation functions as password algorithm
Key derivation functions are not designed upfront as a hashing algorithm. They were designed to generate a cryptographic key from a password or passphrase.
So it is capable of generating a byte array from a password (also a lousy one like 1234) that can be used in, for example, symmetric encryption algorithms like AES.
But since this process is repeatable (when using the same input parameters, see further on) one can use it to create a byte array from a password (just like the hashing algorithms)
One of the most known key derivation functions is PBKDF2.
Since version 0.9.7.1, this is supported by Octopus. By just specifying the function name as algorithm name, Octopus will use this way of creating a hashed password.
hashAlgorithmName=PBKDF2
When this config parameter is defined in the main scenario code, another way of creating the hashed password will be used with the same application code.
6.4.1.1 Some important remarks
- A salt is also required, so don't forget to supply it within the AuthenticationInfoProvider.
- In fact, there are 3 input parameters; salt, iterations and intermediate hashing algorithm which all can be defined.
- Since the Key derivation functions can be implemented efficiently (which is not a good characteristic in cryptography because it makes brute force attacks efficient) the whole process will be performed multiple times using the output from the previous iteration as input o the current iteration. By default, 1024 iterations are performed. You can change this value from the config parameter hashIterations
The intermediate hashing algorithm is determined based on the Java version (SHA1 for Java 7, SHA256 for Java 8). Or you can define the actual hashing to be sued (preferred as it is better when Java version is migrated. However SHA256 is not available on Java 7)
hashAlgorithmName=PBKDF2WithHmacSHA1
There is no provider mechanism foreseen in Java for the key derivation functions so only PBKDF2 is supported.
6.4.2 Hash encoding
As we saw in the main scenario, the hashed password is converted automatically to a string using the BASE64 encoding algorithm.
The result of the hashing is a byte array of a certain length (depending on the algorithm) which is of course not so easy to handle like saving to the database. Therefore the byte array is converted to a character series using the BASE64 algorithm.
Another possibility is using a HEX encoding (which takes slightly more characters). This encoding can be activated by the hashEncoding configuration parameter in the octopusConfig.properties file.
hashEncoding=HEX
The SaltHashingUtil makes use of this parameter, as the internals of Octopus who verifies the hashed password to the supplied Hashed password.
So by just adding the above configuration parameter, the code from the main scenario uses now the HEX encoding as you will see on the user page where the hashed value is shown.