piatok 25. septembra 2009

How to deal with password hash in JBoss Seam

Recently I tried to clarify some points concerning authentication part of Seam security. Here can be found forum thread, where I gave some advice on this subject and which was inspiration for this blog entry: How to persist user?

So what it is all about? Seam provides a standard API for the management of a Seam application's users and roles, called Identity Management. In order to use Seam's Identity Management it must be configured with Identity Store. Out of the box Seam provides Jpa Identity Store which stores users and roles in database.

Apparently developer asking for help discovered identityManager component, which can be used to create new users, however in order to use this component user must be already authenticated and additionally must posses some permissions. (Permissions and authorization in general are not covered in this article.)

But this presents CATCH XXII situation when there is an empty database and there is no possibility to log in. In short, developer can not use identityManager.createUser(String name, String password) method because he can not log in and he can not log in because there are no user accounts in database to log in with.

Well no problem, developers can populate their brand new database with SQL scripts manually or in case of Seam, import-dev.sql file can be employed to do the job. But still there is one catch, Seam can automatically hash passwords in database and it is not trivial to reproduce this manually.

This article explains first shortly how to set up Jpa Identity Store using hashed password with salt and then how gather all information needed to create SQL script, which inserts first user into database.

Firstly Jpa Identity Store must be configured in components.xml descriptor like so:
<security:identity-manager identity-store="#{jpaIdentityStore}"/>
<security:jpa-identity-store 
user-class="com.acme.model.User"
role-class="com.acme.model.Role"/>
Where User class is Entity annotated with seam annotations like so:
(only getters are shown, fields and setters are omitted for clarity)
@Entity
public class User implements Serializable {
...
 @NotNull
 @UserPrincipal
 @Column(unique = true)
 public String getUsername() { return username; }

 @UserPassword
 public String getPasswordHash() { return passwordHash; }

 @PasswordSalt
 public String getSalt() { return salt; }

 @UserEnabled
 public boolean isEnabled() { return enabled; }

 @UserFirstName
 public String getFirstname() { return firstname; }

 @UserLastName
 public String getLastname() { return lastname; }

 @UserRoles
 @ManyToMany
 public List<UserRole> getRoles() { return roles; }
...
}
Most noticeable annotations are @UserPrincipal, @UserPassword and @PasswordSalt.
In field annotated @UserPrincipal username is stored.
In field annotated @PasswordSalt salt is stored.
In field annotated @UserPassword hashed password is stored.
Password hash depends on these parameters:

passwordHash = f(password, algorithm, salt, iterations)

Where:

password - plain password
algorithm - hashing algorithm, in Seam 2.2.0.GA, HmacSHA1 is default
salt - salt value used to produce the password hash, when not specified field annotated with @UserPrincipal is used (username is used as salt for hashing password)
iterations - number of iterations for generating the password hash, default value is 1000

Solution:

I have created small test which does the trick, it uses Seam PasswordHash component and it produces valid password, salt and hash combination which can be used in SQL script. This code was tested only with Seam 2.2.0.GA. (This version uses default hashing algorithm, algorithm parameter can be introduced, but it depends on security provider implementation so it is out of scope for this article.)
public class PasswordHashTest extends SeamTest {

    @Test
    public void testPasswordHash() throws Exception {
        new ComponentTest() {
            @Override
            protected void testComponents() throws Exception {
                Log log = Logging.getLog(PasswordHashTest.class);
                PasswordHash passwordHash = PasswordHash.instance();

                final byte[] salt = passwordHash.generateRandomSalt();
                final int iterations = 1000;
                final String password = "admin";
                final String hash = passwordHash.createPasswordKey(password.toCharArray(), salt, iterations);

                assert hash != null : "Hash not calculated!";

                log.info("Password: " + password);
                log.info("Salt: " + BinTools.bin2hex(salt));
                log.info("Iterations: " + iterations);
                log.info("Hash: " + hash);
            }
        }.run();
    }
}
After running this test, all information needed is logged as info message. This test can be run multiple times in case more user accounts are desired.