Merge pull request #1 from fourbites/master
Implement Argon2 password hashing and verification in user management
This commit is contained in:
@@ -63,6 +63,12 @@
|
||||
<version>2.5</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/de.mkammerer/argon2-jvm -->
|
||||
<dependency>
|
||||
<groupId>de.mkammerer</groupId>
|
||||
<artifactId>argon2-jvm</artifactId>
|
||||
<version>2.12</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.threerings.user;
|
||||
|
||||
import de.mkammerer.argon2.Argon2;
|
||||
import de.mkammerer.argon2.Argon2Factory;
|
||||
|
||||
public class Crypto {
|
||||
|
||||
protected static final Argon2 argon2 = Argon2Factory.create();
|
||||
|
||||
private static final int HASH_ITERATIONS = 2;
|
||||
private static final int MEMORY_COST_KB = 65536;
|
||||
private static final int PARALLELISM = 1;
|
||||
|
||||
/**
|
||||
* Hashes the given password using Argon2.
|
||||
*
|
||||
* @param password the password to hash
|
||||
* @return the hashed password
|
||||
*/
|
||||
public static String hashPassword(char[] password) {
|
||||
return argon2.hash(HASH_ITERATIONS, MEMORY_COST_KB, PARALLELISM, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the given password against the stored hash.
|
||||
*
|
||||
* @param password the password to verify
|
||||
* @param hash the stored hash
|
||||
* @return true if the password matches the hash, false otherwise
|
||||
*/
|
||||
public static boolean verifyPassword(char[] password, String hash) {
|
||||
return argon2.verify(hash, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given hash needs to be rehashed based on the current
|
||||
* parameters.
|
||||
*
|
||||
* @param hash the hash to check
|
||||
* @return true if the hash needs to be rehashed, false otherwise
|
||||
*/
|
||||
public static boolean needsRehash(String hash) {
|
||||
return argon2.needsRehash(hash, HASH_ITERATIONS, MEMORY_COST_KB, PARALLELISM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given hash is hashed using Argon2.
|
||||
*
|
||||
* @param hash the hash to check
|
||||
* @return true if the hash is hashed with Argon2, false otherwise
|
||||
*/
|
||||
public static boolean isArgon2Hashed(String hash) {
|
||||
return hash != null && hash.startsWith("$argon2");
|
||||
}
|
||||
|
||||
/**
|
||||
* Wipes the contents of the given character array. This should be called
|
||||
* when a plaintext password is no longer needed so it is not kept in memory.
|
||||
*
|
||||
* @param array the array to wipe
|
||||
*/
|
||||
public static void wipeArray(char[] array) {
|
||||
argon2.wipeArray(array);
|
||||
}
|
||||
}
|
||||
@@ -8,11 +8,10 @@ import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import com.samskivert.jdbc.jora.FieldMask;
|
||||
import com.samskivert.servlet.user.Password;
|
||||
import com.samskivert.servlet.user.User;
|
||||
import com.samskivert.util.ArrayIntSet;
|
||||
|
||||
import static com.threerings.user.Log.log;
|
||||
|
||||
/**
|
||||
@@ -341,6 +340,51 @@ public class OOOUser extends User
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPassword(String password) {
|
||||
// we might want to consider disabling this in favor of directly using char[]
|
||||
// because char[] can be wiped when it's done being used for security,
|
||||
// but for now, we'll keep it as is to not break existing functionality
|
||||
setPassword(password.toCharArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user's password from a plaintext string.
|
||||
*
|
||||
* @param password the password to set
|
||||
*/
|
||||
public void setPassword(char[] password) {
|
||||
String encrypted = Crypto.hashPassword(password);
|
||||
setPassword(Password.makeFromCrypto(encrypted));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the user's password against the provided plaintext password.
|
||||
*
|
||||
* @param password the plaintext password to check
|
||||
* @return true if the password is correct, false otherwise
|
||||
*/
|
||||
public boolean checkPassword(char[] password) {
|
||||
return Crypto.verifyPassword(password, this.password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the user's password is hashed using Argon2.
|
||||
*/
|
||||
public boolean isArgon2Hashed() {
|
||||
return Crypto.isArgon2Hashed(password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user's password needs to be rehashed based on the current
|
||||
* parameters.
|
||||
*
|
||||
* @return true if the password needs to be rehashed, false otherwise
|
||||
*/
|
||||
public boolean needsRehash() {
|
||||
return !isArgon2Hashed() || Crypto.needsRehash(this.password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all the spots that this user has recieved.
|
||||
*/
|
||||
|
||||
@@ -1589,7 +1589,7 @@ public class OOOUserRepository extends UserRepository
|
||||
String[] usersSchema = {
|
||||
"userId INTEGER(10) PRIMARY KEY NOT NULL AUTO_INCREMENT",
|
||||
"username VARCHAR(255) NOT NULL",
|
||||
"password VARCHAR(32) NOT NULL",
|
||||
"password VARCHAR(128) NOT NULL",
|
||||
"email VARCHAR(128) NOT NULL",
|
||||
"realname VARCHAR(128) NOT NULL",
|
||||
"created DATE NOT NULL",
|
||||
@@ -1605,6 +1605,12 @@ public class OOOUserRepository extends UserRepository
|
||||
};
|
||||
JDBCUtil.createTableIfMissing(conn, "users", usersSchema, "");
|
||||
|
||||
// give the password a longer length to allow more secure hashing
|
||||
int passwordLength = JDBCUtil.getColumnSize(conn, "users", "password");
|
||||
if (passwordLength < 128) {
|
||||
JDBCUtil.changeColumn(conn, "users", "password", "password VARCHAR(128) NOT NULL");
|
||||
}
|
||||
|
||||
String[] historySchema = {
|
||||
"userId INTEGER(10) PRIMARY KEY NOT NULL",
|
||||
"username VARCHAR(255) NOT NULL",
|
||||
|
||||
Reference in New Issue
Block a user