Implement Argon2 password hashing and verification in user management

This commit is contained in:
fourbites
2025-08-18 23:52:41 +02:00
parent f0c392c331
commit 802ec201fa
4 changed files with 124 additions and 3 deletions
+6
View File
@@ -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);
}
}
+46 -2
View File
@@ -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 and wipes the plaintext password from memory.
*
* @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",