When developing authentication I followed this guide.
It describes how passwords are hacked and what you need to do to protect user passwords. Provides some good tips on hashing process.
In short:
- store only hash of your password (and salt, see below)
- Use existing hashing functions - they are better than inventing own hashing
- hash with salt - salt is random so even same passwords will have different hashes
- salt has to generated for every password change
So in database you need 2 - 3 columns
- login name
- password hash
- salt (if not combined into password hash)
Authentication will create hash from provided password (and stored salt) and compare it to stored hash (under specified login name). Error message for invalid login name or password should be same (invalid user name or password) so it is not easy to find user names.
Under the hood my authentication method looks like this:
public User authenticate(String login, String password) {
final User user = UserService.getInstance().getUser(login);
if(user != null) {
//check password
byte[] passwordHash = PasswordManipulator.instance.hash(password, user.getPasswordSalt());
if(Arrays.equals(passwordHash, user.getPasswordHash())) {
return user;
}
else {
// invalid password, we return no user
return null;
}
}
return null;
}
returning null means either login or password is invalid. UserService simply reads user table and return it as User object.
PasswordManipulator looks like this:
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
public class PasswordManipulator {
private static final int SaltLength = 32;
private static final int IterationCount = 65536;
private static final int KeyLength = 256;
private final SecureRandom random = new SecureRandom();
SecretKeyFactory factory = null;
public static final PasswordManipulator instance = new PasswordManipulator();
private PasswordManipulator() {
}
public byte[] createSalt() {
byte[] salt = new byte[SaltLength];
random.nextBytes(salt);
return salt;
}
public byte[] hash(String password, byte[] salt) {
init();
if(password == null || password.isEmpty()) {
throw new IllegalArgumentException("Password is null or empty");
}
if(salt == null ) {
throw new IllegalArgumentException("Salt is null!");
}
if(salt.length != SaltLength) {
throw new IllegalArgumentException("Salt length is not valid. Expected length is " + SaltLength + ", but length is " + salt.length);
}
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, IterationCount, KeyLength);
try {
byte[] hash = factory.generateSecret(spec).getEncoded();
return hash;
} catch (InvalidKeySpecException e) {
throw new IllegalArgumentException("Failed to create password hash", e);
}
}
private void init(){
if(factory == null) {
try {
factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Cannot init " + this.getClass(), e);
}
}
}
}
You probably need some database to embed into your application, or have other way of database setup on client. Check this comparison of embedded databases. To connect to database use standard JDBC way.
Question is what will you do if user will forget password. Standard way is to use email for password recovery (and user login to be equal to email or email to be required user information), but that require online connection. Maybe have some password recovery backup questions that user has to answer correctly. Or if you have some form of administrative user, administrative user could reset passwords for standard users.
There are also other ways of authentication like using Active directory, but you need some infrastructure already set up to connect to it.