mirror of
https://github.com/AuthMe/AuthMeReloaded.git
synced 2024-11-24 11:15:19 +01:00
#358 Create encryption method supertypes, add new methods
This commit is contained in:
parent
31730699ac
commit
48d0a65724
@ -198,7 +198,7 @@ public class PasswordSecurity {
|
|||||||
: algorithm.getClazz().newInstance();
|
: algorithm.getClazz().newInstance();
|
||||||
} catch (InstantiationException | IllegalAccessException e) {
|
} catch (InstantiationException | IllegalAccessException e) {
|
||||||
throw new IllegalStateException("Constructor for '" + algorithm.getClazz()
|
throw new IllegalStateException("Constructor for '" + algorithm.getClazz()
|
||||||
+ "' could not be invoked. (Is it public with no arguments?)", e);
|
+ "' could not be invoked. (Is there no default constructor?)", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
PasswordEncryptionEvent event = new PasswordEncryptionEvent(method, playerName);
|
PasswordEncryptionEvent event = new PasswordEncryptionEvent(method, playerName);
|
||||||
|
@ -17,6 +17,7 @@ import fr.xephi.authme.security.crypts.description.HasSalt;
|
|||||||
import fr.xephi.authme.security.crypts.description.Usage;
|
import fr.xephi.authme.security.crypts.description.Usage;
|
||||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||||
import fr.xephi.authme.security.crypts.description.SaltType;
|
import fr.xephi.authme.security.crypts.description.SaltType;
|
||||||
|
import fr.xephi.authme.settings.Settings;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
@ -63,9 +64,9 @@ import java.security.SecureRandom;
|
|||||||
* @author Damien Miller
|
* @author Damien Miller
|
||||||
* @version 0.2
|
* @version 0.2
|
||||||
*/
|
*/
|
||||||
@Recommendation(Usage.RECOMMENDED)
|
@Recommendation(Usage.RECOMMENDED) // provided the salt length is >= 8
|
||||||
@HasSalt(value = SaltType.TEXT, length = BCRYPT.BCRYPT_SALT_LEN)
|
@HasSalt(value = SaltType.TEXT) // length depends on Settings.bCryptLog2Rounds
|
||||||
public class BCRYPT implements EncryptionMethod {
|
public class BCRYPT implements NewEncrMethod {
|
||||||
|
|
||||||
// BCrypt parameters
|
// BCrypt parameters
|
||||||
private static final int GENSALT_DEFAULT_LOG2_ROUNDS = 10;
|
private static final int GENSALT_DEFAULT_LOG2_ROUNDS = 10;
|
||||||
@ -518,8 +519,10 @@ public class BCRYPT implements EncryptionMethod {
|
|||||||
return hashpw(password, salt);
|
return hashpw(password, salt);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String computeHash(String password, String name) {
|
@Override
|
||||||
return hashpw(password, generateSalt());
|
public HashResult computeHash(String password, String name) {
|
||||||
|
String salt = generateSalt();
|
||||||
|
return new HashResult(hashpw(password, salt), salt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -527,7 +530,18 @@ public class BCRYPT implements EncryptionMethod {
|
|||||||
return checkpw(password, hash);
|
return checkpw(password, hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean comparePassword(String hash, String password, String salt, String name) {
|
||||||
|
return comparePassword(hash, password, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String generateSalt() {
|
public String generateSalt() {
|
||||||
return BCRYPT.gensalt();
|
return BCRYPT.gensalt(Settings.bCryptLog2Rounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasSeparateSalt() {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import java.security.MessageDigest;
|
|||||||
|
|
||||||
@Recommendation(Usage.DO_NOT_USE)
|
@Recommendation(Usage.DO_NOT_USE)
|
||||||
@HasSalt(SaltType.USERNAME)
|
@HasSalt(SaltType.USERNAME)
|
||||||
public class CRAZYCRYPT1 implements EncryptionMethod {
|
public class CRAZYCRYPT1 extends UsernameSaltMethod {
|
||||||
|
|
||||||
private static final char[] CRYPTCHARS =
|
private static final char[] CRYPTCHARS =
|
||||||
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
|
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
|
||||||
@ -28,23 +28,11 @@ public class CRAZYCRYPT1 implements EncryptionMethod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String computeHash(String password, String salt, String name) {
|
public HashResult computeHash(String password, String name) {
|
||||||
return computeHash(password, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String computeHash(String password, String name) {
|
|
||||||
final String text = "ÜÄaeut//&/=I " + password + "7421€547" + name + "__+IÄIH§%NK " + password;
|
final String text = "ÜÄaeut//&/=I " + password + "7421€547" + name + "__+IÄIH§%NK " + password;
|
||||||
final MessageDigest md = HashUtils.getDigest(MessageDigestAlgorithm.SHA512);
|
final MessageDigest md = HashUtils.getDigest(MessageDigestAlgorithm.SHA512);
|
||||||
md.update(text.getBytes(charset), 0, text.length());
|
md.update(text.getBytes(charset), 0, text.length());
|
||||||
return byteArrayToHexString(md.digest());
|
return new HashResult(byteArrayToHexString(md.digest()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean comparePassword(String hash, String password, String playerName) {
|
|
||||||
return hash.equals(computeHash(password, playerName));
|
|
||||||
}
|
|
||||||
|
|
||||||
public String generateSalt() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -12,8 +12,7 @@ import java.util.Arrays;
|
|||||||
public class CryptPBKDF2 implements EncryptionMethod {
|
public class CryptPBKDF2 implements EncryptionMethod {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String computeHash(String password, String salt, String name)
|
public String computeHash(String password, String salt, String name) {
|
||||||
throws NoSuchAlgorithmException {
|
|
||||||
String result = "pbkdf2_sha256$10000$" + salt + "$";
|
String result = "pbkdf2_sha256$10000$" + salt + "$";
|
||||||
PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "ASCII", salt.getBytes(), 10000);
|
PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "ASCII", salt.getBytes(), 10000);
|
||||||
PBKDF2Engine engine = new PBKDF2Engine(params);
|
PBKDF2Engine engine = new PBKDF2Engine(params);
|
||||||
@ -22,8 +21,7 @@ public class CryptPBKDF2 implements EncryptionMethod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean comparePassword(String hash, String password,
|
public boolean comparePassword(String hash, String password, String playerName) {
|
||||||
String playerName) throws NoSuchAlgorithmException {
|
|
||||||
String[] line = hash.split("\\$");
|
String[] line = hash.split("\\$");
|
||||||
String salt = line[2];
|
String salt = line[2];
|
||||||
String derivedKey = line[3];
|
String derivedKey = line[3];
|
||||||
|
@ -1,18 +1,11 @@
|
|||||||
package fr.xephi.authme.security.crypts;
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
import fr.xephi.authme.security.RandomString;
|
|
||||||
import fr.xephi.authme.security.crypts.description.HasSalt;
|
|
||||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
|
||||||
import fr.xephi.authme.security.crypts.description.SaltType;
|
|
||||||
import fr.xephi.authme.security.crypts.description.Usage;
|
|
||||||
import fr.xephi.authme.security.pbkdf2.PBKDF2Engine;
|
import fr.xephi.authme.security.pbkdf2.PBKDF2Engine;
|
||||||
import fr.xephi.authme.security.pbkdf2.PBKDF2Parameters;
|
import fr.xephi.authme.security.pbkdf2.PBKDF2Parameters;
|
||||||
|
|
||||||
import javax.xml.bind.DatatypeConverter;
|
import javax.xml.bind.DatatypeConverter;
|
||||||
|
|
||||||
@Recommendation(Usage.ACCEPTABLE)
|
public class CryptPBKDF2Django extends HexSaltedMethod {
|
||||||
@HasSalt(value = SaltType.TEXT, length = 12)
|
|
||||||
public class CryptPBKDF2Django implements EncryptionMethod {
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String computeHash(String password, String salt, String name) {
|
public String computeHash(String password, String salt, String name) {
|
||||||
@ -23,12 +16,8 @@ public class CryptPBKDF2Django implements EncryptionMethod {
|
|||||||
return result + String.valueOf(DatatypeConverter.printBase64Binary(engine.deriveKey(password, 32)));
|
return result + String.valueOf(DatatypeConverter.printBase64Binary(engine.deriveKey(password, 32)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public String computeHash(String password, String name) {
|
|
||||||
return computeHash(password, generateSalt(), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean comparePassword(String hash, String password, String playerName) {
|
public boolean comparePassword(String hash, String password, String unusedSalt, String unusedName) {
|
||||||
String[] line = hash.split("\\$");
|
String[] line = hash.split("\\$");
|
||||||
String salt = line[2];
|
String salt = line[2];
|
||||||
byte[] derivedKey = DatatypeConverter.parseBase64Binary(line[3]);
|
byte[] derivedKey = DatatypeConverter.parseBase64Binary(line[3]);
|
||||||
@ -37,8 +26,9 @@ public class CryptPBKDF2Django implements EncryptionMethod {
|
|||||||
return engine.verifyKey(password);
|
return engine.verifyKey(password);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String generateSalt() {
|
@Override
|
||||||
return RandomString.generateHex(12);
|
public int getSaltLength() {
|
||||||
|
return 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,12 @@
|
|||||||
package fr.xephi.authme.security.crypts;
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
import fr.xephi.authme.security.HashUtils;
|
import static fr.xephi.authme.security.HashUtils.md5;
|
||||||
import fr.xephi.authme.security.crypts.description.HasSalt;
|
|
||||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
|
||||||
import fr.xephi.authme.security.crypts.description.SaltType;
|
|
||||||
import fr.xephi.authme.security.crypts.description.Usage;
|
|
||||||
|
|
||||||
@Recommendation(Usage.DO_NOT_USE)
|
public class DOUBLEMD5 extends UnsaltedMethod {
|
||||||
@HasSalt(SaltType.NONE)
|
|
||||||
public class DOUBLEMD5 implements EncryptionMethod {
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String computeHash(String password, String salt, String name) {
|
public String computeHash(String password) {
|
||||||
return computeHash(password, null);
|
return md5(md5(password));
|
||||||
}
|
|
||||||
|
|
||||||
public String computeHash(String password, String name) {
|
|
||||||
return HashUtils.md5(HashUtils.md5(password));
|
|
||||||
}
|
|
||||||
|
|
||||||
public String generateSalt() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean comparePassword(String hash, String password, String playerName) {
|
|
||||||
return hash.equals(computeHash(password, null, null));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package fr.xephi.authme.security.crypts;
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Public interface for custom password encryption methods.
|
* Public interface for custom password encryption methods.
|
||||||
*/
|
*/
|
||||||
@ -16,8 +14,7 @@ public interface EncryptionMethod {
|
|||||||
*
|
*
|
||||||
* @return The hashed password
|
* @return The hashed password
|
||||||
*/
|
*/
|
||||||
String computeHash(String password, String salt, String name)
|
String computeHash(String password, String salt, String name);
|
||||||
throws NoSuchAlgorithmException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether a given hash matches the clear-text password.
|
* Check whether a given hash matches the clear-text password.
|
||||||
@ -30,7 +27,6 @@ public interface EncryptionMethod {
|
|||||||
* @return True if the password matches, false otherwise
|
* @return True if the password matches, false otherwise
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
boolean comparePassword(String hash, String password, String playerName)
|
boolean comparePassword(String hash, String password, String playerName);
|
||||||
throws NoSuchAlgorithmException;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,10 @@ public class HashResult {
|
|||||||
this.salt = salt;
|
this.salt = salt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HashResult(String hash) {
|
||||||
|
this(hash, null);
|
||||||
|
}
|
||||||
|
|
||||||
public String getHash() {
|
public String getHash() {
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
|
import fr.xephi.authme.security.RandomString;
|
||||||
|
import fr.xephi.authme.security.crypts.description.HasSalt;
|
||||||
|
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||||
|
import fr.xephi.authme.security.crypts.description.SaltType;
|
||||||
|
import fr.xephi.authme.security.crypts.description.Usage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common type for encryption methods which use a random String of hexadecimal characters
|
||||||
|
* and store the salt with the hash itself.
|
||||||
|
*/
|
||||||
|
@Recommendation(Usage.ACCEPTABLE)
|
||||||
|
@HasSalt(SaltType.TEXT) // See saltLength() for length
|
||||||
|
public abstract class HexSaltedMethod implements NewEncrMethod {
|
||||||
|
|
||||||
|
public abstract int getSaltLength();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract String computeHash(String password, String salt, String name);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HashResult computeHash(String password, String name) {
|
||||||
|
String salt = generateSalt();
|
||||||
|
return new HashResult(computeHash(password, salt, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract boolean comparePassword(String hash, String password, String salt, String name);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public boolean comparePassword(String hash, String password, String playerName) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String generateSalt() {
|
||||||
|
return RandomString.generateHex(getSaltLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasSeparateSalt() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -1,34 +1,52 @@
|
|||||||
package fr.xephi.authme.security.crypts;
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
import fr.xephi.authme.AuthMe;
|
import fr.xephi.authme.AuthMe;
|
||||||
|
import fr.xephi.authme.security.HashUtils;
|
||||||
|
import fr.xephi.authme.security.RandomString;
|
||||||
|
import fr.xephi.authme.security.crypts.description.HasSalt;
|
||||||
|
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||||
|
import fr.xephi.authme.security.crypts.description.SaltType;
|
||||||
|
import fr.xephi.authme.security.crypts.description.Usage;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
/**
|
import static fr.xephi.authme.security.HashUtils.md5;
|
||||||
*/
|
|
||||||
public class IPB3 implements EncryptionMethod {
|
|
||||||
|
|
||||||
private static String getMD5(String message)
|
@Recommendation(Usage.DO_NOT_USE)
|
||||||
throws NoSuchAlgorithmException {
|
@HasSalt(value = SaltType.TEXT, length = 5)
|
||||||
MessageDigest md5 = MessageDigest.getInstance("MD5");
|
public class IPB3 implements NewEncrMethod {
|
||||||
md5.reset();
|
|
||||||
md5.update(message.getBytes());
|
@Override
|
||||||
byte[] digest = md5.digest();
|
public String computeHash(String password, String salt, String name) {
|
||||||
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
|
return md5(md5(salt) + md5(password));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String computeHash(String password, String salt, String name)
|
public HashResult computeHash(String password, String name) {
|
||||||
throws NoSuchAlgorithmException {
|
String salt = generateSalt();
|
||||||
return getMD5(getMD5(salt) + getMD5(password));
|
return new HashResult(computeHash(password, salt, name), salt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean comparePassword(String hash, String password,
|
public boolean comparePassword(String hash, String password, String playerName) {
|
||||||
String playerName) throws NoSuchAlgorithmException {
|
|
||||||
String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt();
|
String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt();
|
||||||
return hash.equals(computeHash(password, salt, playerName));
|
return hash.equals(computeHash(password, salt, playerName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean comparePassword(String hash, String password, String salt, String name) {
|
||||||
|
return hash.equals(computeHash(password, salt, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String generateSalt() {
|
||||||
|
return RandomString.generateHex(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasSeparateSalt() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,36 +1,26 @@
|
|||||||
package fr.xephi.authme.security.crypts;
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
import fr.xephi.authme.security.HashUtils;
|
import fr.xephi.authme.security.HashUtils;
|
||||||
import fr.xephi.authme.security.RandomString;
|
|
||||||
import fr.xephi.authme.security.crypts.description.HasSalt;
|
|
||||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||||
import fr.xephi.authme.security.crypts.description.SaltType;
|
|
||||||
import fr.xephi.authme.security.crypts.description.Usage;
|
import fr.xephi.authme.security.crypts.description.Usage;
|
||||||
|
|
||||||
@Recommendation(Usage.ACCEPTABLE)
|
@Recommendation(Usage.RECOMMENDED)
|
||||||
@HasSalt(value = SaltType.TEXT, length = 32)
|
public class JOOMLA extends HexSaltedMethod {
|
||||||
public class JOOMLA implements EncryptionMethod {
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String computeHash(String password, String salt, String name) {
|
public String computeHash(String password, String salt, String name) {
|
||||||
return HashUtils.md5(password + salt) + ":" + salt;
|
return HashUtils.md5(password + salt) + ":" + salt;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String computeHash(String password, String name) {
|
@Override
|
||||||
return computeHash(password, generateSalt(), null);
|
public boolean comparePassword(String hash, String password, String unusedSalt, String unusedName) {
|
||||||
}
|
String[] hashParts = hash.split(":");
|
||||||
|
return hashParts.length == 2 && hash.equals(computeHash(password, hashParts[1], null));
|
||||||
public String generateSalt() {
|
|
||||||
return RandomString.generateHex(32);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean comparePassword(String hash, String password, String playerName) {
|
public int getSaltLength() {
|
||||||
String[] hashParts = hash.split(":");
|
return 32;
|
||||||
if (hashParts.length != 2) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
String salt = hashParts[1];
|
|
||||||
return hash.equals(computeHash(password, salt, null));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,21 +6,11 @@ import fr.xephi.authme.security.crypts.description.Recommendation;
|
|||||||
import fr.xephi.authme.security.crypts.description.SaltType;
|
import fr.xephi.authme.security.crypts.description.SaltType;
|
||||||
import fr.xephi.authme.security.crypts.description.Usage;
|
import fr.xephi.authme.security.crypts.description.Usage;
|
||||||
|
|
||||||
@Recommendation(Usage.DO_NOT_USE)
|
public class MD5 extends UnsaltedMethod {
|
||||||
@HasSalt(SaltType.NONE)
|
|
||||||
public class MD5 implements EncryptionMethod {
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String computeHash(String password, String salt, String name) {
|
public String computeHash(String password) {
|
||||||
return computeHash(password, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String computeHash(String password, String name) {
|
|
||||||
return HashUtils.md5(password);
|
return HashUtils.md5(password);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean comparePassword(String hash, String password, String playerName) {
|
|
||||||
return hash.equals(computeHash(password, null));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,34 +1,23 @@
|
|||||||
package fr.xephi.authme.security.crypts;
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
import fr.xephi.authme.security.RandomString;
|
|
||||||
import fr.xephi.authme.security.crypts.description.HasSalt;
|
|
||||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
|
||||||
import fr.xephi.authme.security.crypts.description.SaltType;
|
|
||||||
import fr.xephi.authme.security.crypts.description.Usage;
|
|
||||||
|
|
||||||
import static fr.xephi.authme.security.HashUtils.md5;
|
import static fr.xephi.authme.security.HashUtils.md5;
|
||||||
|
|
||||||
@Recommendation(Usage.ACCEPTABLE)
|
public class MD5VB extends HexSaltedMethod {
|
||||||
@HasSalt(value = SaltType.TEXT, length = 16)
|
|
||||||
public class MD5VB implements EncryptionMethod {
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String computeHash(String password, String salt, String name) {
|
public String computeHash(String password, String salt, String name) {
|
||||||
return "$MD5vb$" + salt + "$" + md5(md5(password) + salt);
|
return "$MD5vb$" + salt + "$" + md5(md5(password) + salt);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String computeHash(String password, String name) {
|
@Override
|
||||||
return computeHash(password, generateSalt(), null);
|
public boolean comparePassword(String hash, String password, String salt, String name) {
|
||||||
|
String[] line = hash.split("\\$");
|
||||||
|
return line.length == 4 && hash.equals(computeHash(password, line[2], name));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean comparePassword(String hash, String password, String playerName) {
|
public int getSaltLength() {
|
||||||
String[] line = hash.split("\\$");
|
return 16;
|
||||||
return line.length == 4 && hash.equals(computeHash(password, line[2], ""));
|
|
||||||
}
|
|
||||||
|
|
||||||
public String generateSalt() {
|
|
||||||
return RandomString.generateHex(16);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,34 +1,46 @@
|
|||||||
package fr.xephi.authme.security.crypts;
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
import fr.xephi.authme.AuthMe;
|
import fr.xephi.authme.AuthMe;
|
||||||
|
import fr.xephi.authme.security.HashUtils;
|
||||||
|
import fr.xephi.authme.security.RandomString;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
/**
|
import static fr.xephi.authme.security.HashUtils.md5;
|
||||||
*/
|
|
||||||
public class MYBB implements EncryptionMethod {
|
|
||||||
|
|
||||||
private static String getMD5(String message)
|
public class MYBB implements NewEncrMethod {
|
||||||
throws NoSuchAlgorithmException {
|
|
||||||
MessageDigest md5 = MessageDigest.getInstance("MD5");
|
@Override
|
||||||
md5.reset();
|
public String computeHash(String password, String salt, String name) {
|
||||||
md5.update(message.getBytes());
|
return md5(md5(salt) + md5(password));
|
||||||
byte[] digest = md5.digest();
|
|
||||||
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String computeHash(String password, String salt, String name)
|
public HashResult computeHash(String password, String name) {
|
||||||
throws NoSuchAlgorithmException {
|
String salt = generateSalt();
|
||||||
return getMD5(getMD5(salt) + getMD5(password));
|
return new HashResult(computeHash(password, salt, name), salt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean comparePassword(String hash, String password,
|
public boolean comparePassword(String hash, String password, String playerName) {
|
||||||
String playerName) throws NoSuchAlgorithmException {
|
|
||||||
String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt();
|
String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt();
|
||||||
return hash.equals(computeHash(password, salt, playerName));
|
return hash.equals(computeHash(password, salt, playerName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean comparePassword(String hash, String password, String salt, String name) {
|
||||||
|
return hash.equals(computeHash(password, salt, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String generateSalt() {
|
||||||
|
return RandomString.generateHex(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasSeparateSalt() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,19 +4,20 @@
|
|||||||
*/
|
*/
|
||||||
package fr.xephi.authme.security.crypts;
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
|
import fr.xephi.authme.security.RandomString;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author stefano
|
* @author stefano
|
||||||
*/
|
*/
|
||||||
public class PHPBB implements EncryptionMethod {
|
public class PHPBB extends HexSaltedMethod {
|
||||||
|
|
||||||
private static final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
private static final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||||
|
|
||||||
public static String md5(String data) {
|
private static String md5(String data) {
|
||||||
try {
|
try {
|
||||||
byte[] bytes = data.getBytes("ISO-8859-1");
|
byte[] bytes = data.getBytes("ISO-8859-1");
|
||||||
MessageDigest md5er = MessageDigest.getInstance("MD5");
|
MessageDigest md5er = MessageDigest.getInstance("MD5");
|
||||||
@ -58,7 +59,7 @@ public class PHPBB implements EncryptionMethod {
|
|||||||
return buf.toString();
|
return buf.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String phpbb_hash(String password, String salt) {
|
private String phpbb_hash(String password, String salt) {
|
||||||
String random_state = salt;
|
String random_state = salt;
|
||||||
StringBuilder random = new StringBuilder();
|
StringBuilder random = new StringBuilder();
|
||||||
int count = 6;
|
int count = 6;
|
||||||
@ -109,7 +110,7 @@ public class PHPBB implements EncryptionMethod {
|
|||||||
return output.toString();
|
return output.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
String _hash_crypt_private(String password, String setting) {
|
private String _hash_crypt_private(String password, String setting) {
|
||||||
String output = "*";
|
String output = "*";
|
||||||
if (!setting.substring(0, 3).equals("$H$"))
|
if (!setting.substring(0, 3).equals("$H$"))
|
||||||
return output;
|
return output;
|
||||||
@ -130,21 +131,25 @@ public class PHPBB implements EncryptionMethod {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean phpbb_check_hash(String password, String hash) {
|
private boolean phpbb_check_hash(String password, String hash) {
|
||||||
if (hash.length() == 34)
|
if (hash.length() == 34)
|
||||||
return _hash_crypt_private(password, hash).equals(hash);
|
return _hash_crypt_private(password, hash).equals(hash);
|
||||||
else return md5(password).equals(hash);
|
else return md5(password).equals(hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String computeHash(String password, String salt, String name)
|
public String computeHash(String password, String salt, String name) {
|
||||||
throws NoSuchAlgorithmException {
|
|
||||||
return phpbb_hash(password, salt);
|
return phpbb_hash(password, salt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean comparePassword(String hash, String password,
|
public boolean comparePassword(String hash, String password, String salt, String name) {
|
||||||
String playerName) throws NoSuchAlgorithmException {
|
|
||||||
return phpbb_check_hash(password, hash);
|
return phpbb_check_hash(password, hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSaltLength() {
|
||||||
|
return 16;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
package fr.xephi.authme.security.crypts;
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
import fr.xephi.authme.AuthMe;
|
import fr.xephi.authme.AuthMe;
|
||||||
|
import fr.xephi.authme.security.HashUtils;
|
||||||
|
import fr.xephi.authme.security.RandomString;
|
||||||
|
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||||
|
import fr.xephi.authme.security.crypts.description.Usage;
|
||||||
|
|
||||||
import javax.crypto.Mac;
|
import javax.crypto.Mac;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
@ -10,27 +14,15 @@ import java.security.InvalidKeyException;
|
|||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
/**
|
@Recommendation(Usage.DO_NOT_USE)
|
||||||
*/
|
public class PHPFUSION implements NewEncrMethod {
|
||||||
public class PHPFUSION implements EncryptionMethod {
|
|
||||||
|
|
||||||
private static String getSHA1(String message)
|
|
||||||
throws NoSuchAlgorithmException {
|
|
||||||
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
|
|
||||||
sha1.reset();
|
|
||||||
sha1.update(message.getBytes());
|
|
||||||
byte[] digest = sha1.digest();
|
|
||||||
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String computeHash(String password, String salt, String name)
|
public String computeHash(String password, String salt, String name) {
|
||||||
throws NoSuchAlgorithmException {
|
|
||||||
String digest = null;
|
|
||||||
String algo = "HmacSHA256";
|
String algo = "HmacSHA256";
|
||||||
String keyString = getSHA1(salt);
|
String keyString = HashUtils.sha1(salt);
|
||||||
try {
|
try {
|
||||||
SecretKeySpec key = new SecretKeySpec((keyString).getBytes("UTF-8"), algo);
|
SecretKeySpec key = new SecretKeySpec(keyString.getBytes("UTF-8"), algo);
|
||||||
Mac mac = Mac.getInstance(algo);
|
Mac mac = Mac.getInstance(algo);
|
||||||
mac.init(key);
|
mac.init(key);
|
||||||
byte[] bytes = mac.doFinal(password.getBytes("ASCII"));
|
byte[] bytes = mac.doFinal(password.getBytes("ASCII"));
|
||||||
@ -42,19 +34,38 @@ public class PHPFUSION implements EncryptionMethod {
|
|||||||
}
|
}
|
||||||
hash.append(hex);
|
hash.append(hex);
|
||||||
}
|
}
|
||||||
digest = hash.toString();
|
return hash.toString();
|
||||||
} catch (UnsupportedEncodingException | InvalidKeyException | NoSuchAlgorithmException e) {
|
} catch (UnsupportedEncodingException | InvalidKeyException | NoSuchAlgorithmException e) {
|
||||||
//ingore
|
throw new UnsupportedOperationException("Cannot create PHPFUSION hash for " + name, e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return digest;
|
@Override
|
||||||
|
public HashResult computeHash(String password, String name) {
|
||||||
|
String salt = generateSalt();
|
||||||
|
return new HashResult(computeHash(password, salt, name), salt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean comparePassword(String hash, String password,
|
public boolean comparePassword(String hash, String password,
|
||||||
String playerName) throws NoSuchAlgorithmException {
|
String playerName) {
|
||||||
String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt();
|
String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt();
|
||||||
return hash.equals(computeHash(password, salt, ""));
|
return hash.equals(computeHash(password, salt, ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean comparePassword(String hash, String password, String salt, String name) {
|
||||||
|
return hash.equals(computeHash(password, salt, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String generateSalt() {
|
||||||
|
return RandomString.generateHex(12);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasSeparateSalt() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,11 @@
|
|||||||
package fr.xephi.authme.security.crypts;
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
@Deprecated
|
||||||
|
public class PLAINTEXT extends UnsaltedMethod {
|
||||||
/**
|
|
||||||
*/
|
|
||||||
public class PLAINTEXT implements EncryptionMethod {
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String computeHash(String password, String salt, String name)
|
public String computeHash(String password) {
|
||||||
throws NoSuchAlgorithmException {
|
|
||||||
return password;
|
return password;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean comparePassword(String hash, String password,
|
|
||||||
String playerName) throws NoSuchAlgorithmException {
|
|
||||||
return hash.equals(password);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,35 +1,16 @@
|
|||||||
package fr.xephi.authme.security.crypts;
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
import java.security.MessageDigest;
|
import fr.xephi.authme.security.HashUtils;
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
|
|
||||||
/**
|
public class ROYALAUTH extends UnsaltedMethod {
|
||||||
*/
|
|
||||||
public class ROYALAUTH implements EncryptionMethod {
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String computeHash(String password, String salt, String name)
|
public String computeHash(String password) {
|
||||||
throws NoSuchAlgorithmException {
|
for (int i = 0; i < 25; i++) {
|
||||||
for (int i = 0; i < 25; i++)
|
// TODO ljacqu 20151228: HashUtils#sha512 gets a new message digest each time...
|
||||||
password = hash(password, salt);
|
password = HashUtils.sha512(password);
|
||||||
|
}
|
||||||
return password;
|
return password;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String hash(String password, String salt)
|
|
||||||
throws NoSuchAlgorithmException {
|
|
||||||
MessageDigest md = MessageDigest.getInstance("SHA-512");
|
|
||||||
md.update(password.getBytes());
|
|
||||||
byte byteData[] = md.digest();
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
for (byte aByteData : byteData)
|
|
||||||
sb.append(Integer.toString((aByteData & 0xff) + 0x100, 16).substring(1));
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean comparePassword(String hash, String password,
|
|
||||||
String playerName) throws NoSuchAlgorithmException {
|
|
||||||
return hash.equalsIgnoreCase(computeHash(password, "", ""));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package fr.xephi.authme.security.crypts;
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
import fr.xephi.authme.AuthMe;
|
import fr.xephi.authme.AuthMe;
|
||||||
|
import fr.xephi.authme.security.HashUtils;
|
||||||
import fr.xephi.authme.security.RandomString;
|
import fr.xephi.authme.security.RandomString;
|
||||||
import fr.xephi.authme.security.crypts.description.HasSalt;
|
import fr.xephi.authme.security.crypts.description.HasSalt;
|
||||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||||
@ -12,50 +13,15 @@ import java.math.BigInteger;
|
|||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
import static fr.xephi.authme.security.HashUtils.md5;
|
||||||
|
|
||||||
@Recommendation(Usage.ACCEPTABLE) // presuming that length is something sensible (>= 8)
|
@Recommendation(Usage.ACCEPTABLE) // presuming that length is something sensible (>= 8)
|
||||||
@HasSalt(value = SaltType.TEXT) // length defined by Settings.saltLength
|
@HasSalt(value = SaltType.TEXT) // length defined by Settings.saltLength
|
||||||
public class SALTED2MD5 implements NewEncrMethod {
|
public class SALTED2MD5 extends SeparateSaltMethod {
|
||||||
|
|
||||||
private static String getMD5(String message)
|
|
||||||
throws NoSuchAlgorithmException {
|
|
||||||
MessageDigest md5 = MessageDigest.getInstance("MD5");
|
|
||||||
md5.reset();
|
|
||||||
md5.update(message.getBytes());
|
|
||||||
byte[] digest = md5.digest();
|
|
||||||
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String computeHash(String password, String salt, String name)
|
public String computeHash(String password, String salt, String name) {
|
||||||
throws NoSuchAlgorithmException {
|
return md5(md5(password) + salt);
|
||||||
return getMD5(getMD5(password) + salt);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HashResult computeHash(String password, String name) {
|
|
||||||
try {
|
|
||||||
String salt = generateSalt();
|
|
||||||
return new HashResult(computeHash(password, salt, name), salt);
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new RuntimeException(e); // TODO #358: Remove try-catch clause
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean comparePassword(String hash, String password,
|
|
||||||
String playerName) throws NoSuchAlgorithmException {
|
|
||||||
String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt();
|
|
||||||
return hash.equals(getMD5(getMD5(password) + salt));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean comparePassword(String hash, String password, String salt, String name) {
|
|
||||||
try {
|
|
||||||
return hash.equals(computeHash(password, salt, name));
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
// TODO #358: Remove try-catch
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -63,9 +29,4 @@ public class SALTED2MD5 implements NewEncrMethod {
|
|||||||
return RandomString.generateHex(Settings.saltLength);
|
return RandomString.generateHex(Settings.saltLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasSeparateSalt() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,34 +1,20 @@
|
|||||||
package fr.xephi.authme.security.crypts;
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
import fr.xephi.authme.AuthMe;
|
import fr.xephi.authme.security.HashUtils;
|
||||||
|
import fr.xephi.authme.security.RandomString;
|
||||||
|
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||||
|
import fr.xephi.authme.security.crypts.description.Usage;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
@Recommendation(Usage.RECOMMENDED)
|
||||||
import java.security.MessageDigest;
|
public class SALTEDSHA512 extends SeparateSaltMethod {
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
*/
|
public String computeHash(String password, String salt, String name) {
|
||||||
public class SALTEDSHA512 implements EncryptionMethod {
|
return HashUtils.sha512(password + salt);
|
||||||
|
|
||||||
private static String getSHA512(String message)
|
|
||||||
throws NoSuchAlgorithmException {
|
|
||||||
MessageDigest sha512 = MessageDigest.getInstance("SHA-512");
|
|
||||||
sha512.reset();
|
|
||||||
sha512.update(message.getBytes());
|
|
||||||
byte[] digest = sha512.digest();
|
|
||||||
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String computeHash(String password, String salt, String name)
|
public String generateSalt() {
|
||||||
throws NoSuchAlgorithmException {
|
return RandomString.generateHex(32);
|
||||||
return getSHA512(password + salt);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean comparePassword(String hash, String password,
|
|
||||||
String playerName) throws NoSuchAlgorithmException {
|
|
||||||
String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt();
|
|
||||||
return hash.equals(computeHash(password, salt, ""));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,12 @@
|
|||||||
package fr.xephi.authme.security.crypts;
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
import fr.xephi.authme.security.HashUtils;
|
import fr.xephi.authme.security.HashUtils;
|
||||||
import fr.xephi.authme.security.crypts.description.HasSalt;
|
|
||||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
|
||||||
import fr.xephi.authme.security.crypts.description.SaltType;
|
|
||||||
import fr.xephi.authme.security.crypts.description.Usage;
|
|
||||||
|
|
||||||
@Recommendation(Usage.DO_NOT_USE)
|
public class SHA1 extends UnsaltedMethod {
|
||||||
@HasSalt(SaltType.NONE)
|
|
||||||
public class SHA1 implements EncryptionMethod {
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String computeHash(String password, String salt, String name) {
|
public String computeHash(String password) {
|
||||||
return computeHash(password, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String computeHash(String password, String name) {
|
|
||||||
return HashUtils.sha1(password);
|
return HashUtils.sha1(password);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean comparePassword(String hash, String password, String playerName) {
|
|
||||||
return hash.equals(computeHash(password, null));
|
|
||||||
}
|
|
||||||
|
|
||||||
public String generateSalt() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,34 +1,27 @@
|
|||||||
package fr.xephi.authme.security.crypts;
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
import fr.xephi.authme.security.RandomString;
|
|
||||||
import fr.xephi.authme.security.crypts.description.HasSalt;
|
|
||||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||||
import fr.xephi.authme.security.crypts.description.SaltType;
|
|
||||||
import fr.xephi.authme.security.crypts.description.Usage;
|
import fr.xephi.authme.security.crypts.description.Usage;
|
||||||
|
|
||||||
import static fr.xephi.authme.security.HashUtils.sha256;
|
import static fr.xephi.authme.security.HashUtils.sha256;
|
||||||
|
|
||||||
@Recommendation(Usage.RECOMMENDED)
|
@Recommendation(Usage.RECOMMENDED)
|
||||||
@HasSalt(value = SaltType.TEXT, length = 16)
|
public class SHA256 extends HexSaltedMethod {
|
||||||
public class SHA256 implements EncryptionMethod {
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String computeHash(String password, String salt, String name) {
|
public String computeHash(String password, String salt, String name) {
|
||||||
return "$SHA$" + salt + "$" + sha256(sha256(password) + salt);
|
return "$SHA$" + salt + "$" + sha256(sha256(password) + salt);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String computeHash(String password, String name) {
|
|
||||||
return computeHash(password, generateSalt(), name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean comparePassword(String hash, String password, String playerName) {
|
public boolean comparePassword(String hash, String password, String salt, String playerName) {
|
||||||
String[] line = hash.split("\\$");
|
String[] line = hash.split("\\$");
|
||||||
return line.length == 4 && hash.equals(computeHash(password, line[2], ""));
|
return line.length == 4 && hash.equals(computeHash(password, line[2], ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
public String generateSalt() {
|
@Override
|
||||||
return RandomString.generateHex(16);
|
public int getSaltLength() {
|
||||||
|
return 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,12 @@
|
|||||||
package fr.xephi.authme.security.crypts;
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
import fr.xephi.authme.security.HashUtils;
|
import fr.xephi.authme.security.HashUtils;
|
||||||
import fr.xephi.authme.security.crypts.description.HasSalt;
|
|
||||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
|
||||||
import fr.xephi.authme.security.crypts.description.SaltType;
|
|
||||||
import fr.xephi.authme.security.crypts.description.Usage;
|
|
||||||
|
|
||||||
@Recommendation(Usage.DO_NOT_USE)
|
public class SHA512 extends UnsaltedMethod {
|
||||||
@HasSalt(SaltType.NONE)
|
|
||||||
public class SHA512 implements EncryptionMethod {
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String computeHash(String password, String salt, String name) {
|
public String computeHash(String password) {
|
||||||
return computeHash(password, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String computeHash(String password, String name) {
|
|
||||||
return HashUtils.sha512(password);
|
return HashUtils.sha512(password);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean comparePassword(String hash, String password, String playerName) {
|
|
||||||
return hash.equals(computeHash(password, "", ""));
|
|
||||||
}
|
|
||||||
|
|
||||||
public String generateSalt() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,11 @@
|
|||||||
package fr.xephi.authme.security.crypts;
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
import fr.xephi.authme.security.HashUtils;
|
import fr.xephi.authme.security.HashUtils;
|
||||||
import fr.xephi.authme.security.crypts.description.HasSalt;
|
|
||||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
|
||||||
import fr.xephi.authme.security.crypts.description.SaltType;
|
|
||||||
import fr.xephi.authme.security.crypts.description.Usage;
|
|
||||||
|
|
||||||
@Recommendation(Usage.DO_NOT_USE)
|
public class SMF extends UsernameSaltMethod {
|
||||||
@HasSalt(SaltType.USERNAME)
|
|
||||||
public class SMF implements EncryptionMethod {
|
|
||||||
|
|
||||||
@Override
|
public HashResult computeHash(String password, String name) {
|
||||||
public String computeHash(String password, String salt, String name) {
|
return new HashResult(HashUtils.sha1(name.toLowerCase() + password));
|
||||||
return computeHash(password, name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String computeHash(String password, String name) {
|
|
||||||
return HashUtils.sha1(name.toLowerCase() + password);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean comparePassword(String hash, String password, String playerName) {
|
|
||||||
return hash.equals(computeHash(password, playerName));
|
|
||||||
}
|
|
||||||
|
|
||||||
public String generateSalt() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common supertype for encryption methods which store their salt separately from the hash.
|
||||||
|
*/
|
||||||
|
public abstract class SeparateSaltMethod implements NewEncrMethod {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract String computeHash(String password, String salt, String name);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract String generateSalt();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HashResult computeHash(String password, String name) {
|
||||||
|
String salt = generateSalt();
|
||||||
|
return new HashResult(computeHash(password, salt, name), salt);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean comparePassword(String hash, String password, String salt, String name) {
|
||||||
|
return hash.equals(computeHash(password, salt, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasSeparateSalt() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public boolean comparePassword(String hash, String password, String playerName) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
|
import fr.xephi.authme.security.crypts.description.HasSalt;
|
||||||
|
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||||
|
import fr.xephi.authme.security.crypts.description.SaltType;
|
||||||
|
import fr.xephi.authme.security.crypts.description.Usage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common type for encryption methods which do not use any salt whatsoever.
|
||||||
|
*/
|
||||||
|
@Recommendation(Usage.DO_NOT_USE)
|
||||||
|
@HasSalt(SaltType.NONE)
|
||||||
|
public abstract class UnsaltedMethod implements NewEncrMethod {
|
||||||
|
|
||||||
|
public abstract String computeHash(String password);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HashResult computeHash(String password, String name) {
|
||||||
|
return new HashResult(computeHash(password));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String computeHash(String password, String salt, String name) {
|
||||||
|
return computeHash(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public boolean comparePassword(String hash, String password, String playerName) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean comparePassword(String hash, String password, String salt, String name) {
|
||||||
|
return hash.equals(computeHash(password));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String generateSalt() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasSeparateSalt() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
|
import fr.xephi.authme.security.crypts.description.HasSalt;
|
||||||
|
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||||
|
import fr.xephi.authme.security.crypts.description.SaltType;
|
||||||
|
import fr.xephi.authme.security.crypts.description.Usage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common supertype of encryption methods that use a player's username
|
||||||
|
* (or something based on it) as salt.
|
||||||
|
*/
|
||||||
|
@Recommendation(Usage.DO_NOT_USE)
|
||||||
|
@HasSalt(SaltType.USERNAME)
|
||||||
|
public abstract class UsernameSaltMethod implements NewEncrMethod {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract HashResult computeHash(String password, String name);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean comparePassword(String hash, String password, String salt, String name) {
|
||||||
|
return hash.equals(computeHash(password, name).getHash());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public boolean comparePassword(String hash, String password, String playerName) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String computeHash(String password, String salt, String name) {
|
||||||
|
return computeHash(password, name).getHash();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String generateSalt() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasSeparateSalt() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,34 +1,19 @@
|
|||||||
package fr.xephi.authme.security.crypts;
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
import fr.xephi.authme.AuthMe;
|
import fr.xephi.authme.security.RandomString;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import static fr.xephi.authme.security.HashUtils.sha1;
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
|
|
||||||
/**
|
public class WBB3 extends SeparateSaltMethod {
|
||||||
*/
|
|
||||||
public class WBB3 implements EncryptionMethod {
|
|
||||||
|
|
||||||
private static String getSHA1(String message)
|
@Override
|
||||||
throws NoSuchAlgorithmException {
|
public String computeHash(String password, String salt, String name) {
|
||||||
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
|
return sha1(salt.concat(sha1(salt.concat(sha1(password)))));
|
||||||
sha1.reset();
|
|
||||||
sha1.update(message.getBytes());
|
|
||||||
byte[] digest = sha1.digest();
|
|
||||||
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String computeHash(String password, String salt, String name)
|
public String generateSalt() {
|
||||||
throws NoSuchAlgorithmException {
|
return RandomString.generateHex(40);
|
||||||
return getSHA1(salt.concat(getSHA1(salt.concat(getSHA1(password)))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean comparePassword(String hash, String password,
|
|
||||||
String playerName) throws NoSuchAlgorithmException {
|
|
||||||
String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt();
|
|
||||||
return hash.equals(computeHash(password, salt, ""));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -7,14 +7,12 @@ import java.security.NoSuchAlgorithmException;
|
|||||||
public class WBB4 implements EncryptionMethod {
|
public class WBB4 implements EncryptionMethod {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String computeHash(String password, String salt, String name)
|
public String computeHash(String password, String salt, String name) {
|
||||||
throws NoSuchAlgorithmException {
|
|
||||||
return BCRYPT.getDoubleHash(password, salt);
|
return BCRYPT.getDoubleHash(password, salt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean comparePassword(String hash, String password,
|
public boolean comparePassword(String hash, String password, String playerName) {
|
||||||
String playerName) throws NoSuchAlgorithmException {
|
|
||||||
return BCRYPT.checkpw(password, hash, 2);
|
return BCRYPT.checkpw(password, hash, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,16 +59,9 @@ package fr.xephi.authme.security.crypts;
|
|||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import fr.xephi.authme.security.crypts.description.HasSalt;
|
|
||||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
|
||||||
import fr.xephi.authme.security.crypts.description.SaltType;
|
|
||||||
import fr.xephi.authme.security.crypts.description.Usage;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
@Recommendation(Usage.DO_NOT_USE)
|
public class WHIRLPOOL extends UnsaltedMethod {
|
||||||
@HasSalt(SaltType.NONE)
|
|
||||||
public class WHIRLPOOL implements EncryptionMethod {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The message digest size (in bits)
|
* The message digest size (in bits)
|
||||||
@ -386,12 +379,7 @@ public class WHIRLPOOL implements EncryptionMethod {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public String computeHash(String password) {
|
||||||
public String computeHash(String password, String salt, String name) {
|
|
||||||
return computeHash(password, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String computeHash(String password, String name) {
|
|
||||||
byte[] digest = new byte[DIGESTBYTES];
|
byte[] digest = new byte[DIGESTBYTES];
|
||||||
NESSIEinit();
|
NESSIEinit();
|
||||||
NESSIEadd(password);
|
NESSIEadd(password);
|
||||||
@ -399,9 +387,4 @@ public class WHIRLPOOL implements EncryptionMethod {
|
|||||||
return display(digest);
|
return display(digest);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean comparePassword(String hash, String password, String playerName) {
|
|
||||||
return hash.equals(computeHash(password, null, null));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,9 @@ import java.util.Arrays;
|
|||||||
|
|
||||||
@Recommendation(Usage.ACCEPTABLE)
|
@Recommendation(Usage.ACCEPTABLE)
|
||||||
@HasSalt(value = SaltType.TEXT, length = 9)
|
@HasSalt(value = SaltType.TEXT, length = 9)
|
||||||
public class WORDPRESS implements EncryptionMethod {
|
// Note ljacqu 20151228: Wordpress is actually a salted algorithm but salt generation is handled internally
|
||||||
|
// and isn't exposed to the outside, so we treat it as an unsalted implementation
|
||||||
|
public class WORDPRESS extends UnsaltedMethod {
|
||||||
|
|
||||||
private static final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
private static final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||||
private final SecureRandom randomGen = new SecureRandom();
|
private final SecureRandom randomGen = new SecureRandom();
|
||||||
@ -107,25 +109,16 @@ public class WORDPRESS implements EncryptionMethod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String computeHash(String password, String salt, String name) {
|
public String computeHash(String password) {
|
||||||
byte random[] = new byte[6];
|
byte random[] = new byte[6];
|
||||||
randomGen.nextBytes(random);
|
randomGen.nextBytes(random);
|
||||||
return crypt(password, gensaltPrivate(stringToUtf8(new String(random))));
|
return crypt(password, gensaltPrivate(stringToUtf8(new String(random))));
|
||||||
}
|
}
|
||||||
|
|
||||||
public String computeHash(String password, String name) {
|
|
||||||
return computeHash(password, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean comparePassword(String hash, String password, String playerName) {
|
public boolean comparePassword(String hash, String password, String salt, String name) {
|
||||||
String comparedHash = crypt(password, hash);
|
String comparedHash = crypt(password, hash);
|
||||||
return comparedHash.equals(hash);
|
return comparedHash.equals(hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String generateSalt() {
|
|
||||||
// This hash uses a salt, but it is not exposed to the outside
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
package fr.xephi.authme.security.crypts;
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
import fr.xephi.authme.security.RandomString;
|
|
||||||
import fr.xephi.authme.security.crypts.description.HasSalt;
|
|
||||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||||
import fr.xephi.authme.security.crypts.description.SaltType;
|
|
||||||
import fr.xephi.authme.security.crypts.description.Usage;
|
import fr.xephi.authme.security.crypts.description.Usage;
|
||||||
|
|
||||||
@Recommendation(Usage.RECOMMENDED)
|
@Recommendation(Usage.RECOMMENDED)
|
||||||
@HasSalt(value = SaltType.TEXT, length = 12)
|
public class XAUTH extends HexSaltedMethod {
|
||||||
public class XAUTH implements EncryptionMethod {
|
|
||||||
|
|
||||||
public static String getWhirlpool(String message) {
|
private static String getWhirlpool(String message) {
|
||||||
WHIRLPOOL w = new WHIRLPOOL();
|
WHIRLPOOL w = new WHIRLPOOL();
|
||||||
byte[] digest = new byte[WHIRLPOOL.DIGESTBYTES];
|
byte[] digest = new byte[WHIRLPOOL.DIGESTBYTES];
|
||||||
w.NESSIEinit();
|
w.NESSIEinit();
|
||||||
@ -26,19 +22,16 @@ public class XAUTH implements EncryptionMethod {
|
|||||||
return hash.substring(0, saltPos) + salt + hash.substring(saltPos);
|
return hash.substring(0, saltPos) + salt + hash.substring(saltPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String computeHash(String password, String name) {
|
@Override
|
||||||
return computeHash(password, generateSalt(), null);
|
public boolean comparePassword(String hash, String password, String salt, String playerName) {
|
||||||
|
int saltPos = (password.length() >= hash.length() ? hash.length() - 1 : password.length());
|
||||||
|
String saltFromHash = hash.substring(saltPos, saltPos + 12);
|
||||||
|
return hash.equals(computeHash(password, saltFromHash, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean comparePassword(String hash, String password, String playerName) {
|
public int getSaltLength() {
|
||||||
int saltPos = (password.length() >= hash.length() ? hash.length() - 1 : password.length());
|
return 12;
|
||||||
String salt = hash.substring(saltPos, saltPos + 12);
|
|
||||||
return hash.equals(computeHash(password, salt, ""));
|
|
||||||
}
|
|
||||||
|
|
||||||
public String generateSalt() {
|
|
||||||
return RandomString.generateHex(12);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -11,23 +11,37 @@ import java.util.regex.Pattern;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
public class XF implements EncryptionMethod {
|
public class XF implements NewEncrMethod {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String computeHash(String password, String salt, String name)
|
public String computeHash(String password, String salt, String name) {
|
||||||
throws NoSuchAlgorithmException {
|
|
||||||
return getSha256(getSha256(password) + regmatch("\"salt\";.:..:\"(.*)\";.:.:\"hashFunc\"", salt));
|
return getSha256(getSha256(password) + regmatch("\"salt\";.:..:\"(.*)\";.:.:\"hashFunc\"", salt));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean comparePassword(String hash, String password,
|
public HashResult computeHash(String password, String name) {
|
||||||
String playerName) throws NoSuchAlgorithmException {
|
String salt = generateSalt();
|
||||||
|
return new HashResult(computeHash(password, salt, null), salt);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean comparePassword(String hash, String password, String playerName) {
|
||||||
String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt();
|
String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt();
|
||||||
return hash.equals(regmatch("\"hash\";.:..:\"(.*)\";.:.:\"salt\"", salt));
|
return hash.equals(regmatch("\"hash\";.:..:\"(.*)\";.:.:\"salt\"", salt));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getSha256(String password) throws NoSuchAlgorithmException {
|
public boolean comparePassword(String hash, String password, String salt, String name) {
|
||||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
return hash.equals(regmatch("\"hash\";.:..:\"(.*)\";.:.:\"salt\"", salt));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getSha256(String password) {
|
||||||
|
MessageDigest md = null;
|
||||||
|
try {
|
||||||
|
md = MessageDigest.getInstance("SHA-256");
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
// TODO #358: Handle exception properly
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
md.update(password.getBytes());
|
md.update(password.getBytes());
|
||||||
byte byteData[] = md.digest();
|
byte byteData[] = md.digest();
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
@ -45,6 +59,17 @@ public class XF implements EncryptionMethod {
|
|||||||
return hexString.toString();
|
return hexString.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String generateSalt() {
|
||||||
|
// TODO #369: Find out what kind of salt format XF uses
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasSeparateSalt() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private String regmatch(String pattern, String line) {
|
private String regmatch(String pattern, String line) {
|
||||||
List<String> allMatches = new ArrayList<>();
|
List<String> allMatches = new ArrayList<>();
|
||||||
Matcher m = Pattern.compile(pattern).matcher(line);
|
Matcher m = Pattern.compile(pattern).matcher(line);
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
package fr.xephi.authme.security.crypts;
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
import fr.xephi.authme.security.PasswordSecurity;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@ -11,6 +9,7 @@ import static org.hamcrest.Matchers.equalTo;
|
|||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test for implementations of {@link EncryptionMethod}.
|
* Test for implementations of {@link EncryptionMethod}.
|
||||||
@ -98,15 +97,13 @@ public abstract class AbstractEncryptionMethodTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPasswordEquality() throws NoSuchAlgorithmException {
|
public void testPasswordEquality() {
|
||||||
// TODO #358: Remove "throws NoSuchAlgorithmException" on method declaration
|
|
||||||
// TODO #358: Remove instanceof and use this code always
|
// TODO #358: Remove instanceof and use this code always
|
||||||
if (method instanceof NewEncrMethod) {
|
if (method instanceof NewEncrMethod) {
|
||||||
NewEncrMethod method1 = (NewEncrMethod) method;
|
NewEncrMethod method1 = (NewEncrMethod) method;
|
||||||
for (String password : INTERNAL_PASSWORDS) {
|
for (String password : INTERNAL_PASSWORDS) {
|
||||||
HashResult result = method1.computeHash(password, USERNAME);
|
final String salt = method1.generateSalt();
|
||||||
final String hash = result.getHash();
|
final String hash = method1.computeHash(password, salt, USERNAME);
|
||||||
final String salt = result.getSalt();
|
|
||||||
|
|
||||||
// Check that the computeHash(password, salt, name) method has the same output for the returned salt
|
// Check that the computeHash(password, salt, name) method has the same output for the returned salt
|
||||||
assertThat(hash, equalTo(method1.computeHash(password, salt, USERNAME)));
|
assertThat(hash, equalTo(method1.computeHash(password, salt, USERNAME)));
|
||||||
@ -125,23 +122,7 @@ public abstract class AbstractEncryptionMethodTest {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (String password : INTERNAL_PASSWORDS) {
|
fail("No longer supporting old EncryptionMethod implementations");
|
||||||
try {
|
|
||||||
String hash = method.computeHash(password, getSalt(method), USERNAME);
|
|
||||||
assertTrue("Generated hash for '" + password + "' should match password (hash = '" + hash + "')",
|
|
||||||
method.comparePassword(hash, password, USERNAME));
|
|
||||||
if (!password.equals(password.toLowerCase())) {
|
|
||||||
assertFalse("Lower-case of '" + password + "' should not match generated hash '" + hash + "'",
|
|
||||||
method.comparePassword(hash, password.toLowerCase(), USERNAME));
|
|
||||||
}
|
|
||||||
if (!password.equals(password.toUpperCase())) {
|
|
||||||
assertFalse("Upper-case of '" + password + "' should not match generated hash '" + hash + "'",
|
|
||||||
method.comparePassword(hash, password.toUpperCase(), USERNAME));
|
|
||||||
}
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new IllegalStateException("EncryptionMethod '" + method + "' threw exception", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean doesGivenHashMatch(String password, EncryptionMethod method) {
|
private boolean doesGivenHashMatch(String password, EncryptionMethod method) {
|
||||||
@ -154,11 +135,8 @@ public abstract class AbstractEncryptionMethodTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
try {
|
// TODO #358: Remove line below
|
||||||
return method.comparePassword(hashes.get(password), password, USERNAME);
|
return method.comparePassword(hashes.get(password), password, USERNAME);
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new IllegalStateException("EncryptionMethod '" + method + "' threw exception", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// @org.junit.Test public void a() { AbstractEncryptionMethodTest.generateTest(); }
|
// @org.junit.Test public void a() { AbstractEncryptionMethodTest.generateTest(); }
|
||||||
@ -170,45 +148,29 @@ public abstract class AbstractEncryptionMethodTest {
|
|||||||
System.out.println("\n\tpublic " + className + "Test() {");
|
System.out.println("\n\tpublic " + className + "Test() {");
|
||||||
System.out.println("\t\tsuper(new " + className + "(),");
|
System.out.println("\t\tsuper(new " + className + "(),");
|
||||||
|
|
||||||
|
NewEncrMethod method1 = null;
|
||||||
|
if (method instanceof NewEncrMethod) {
|
||||||
|
method1 = (NewEncrMethod) method;
|
||||||
|
if (!method1.hasSeparateSalt()) method1 = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
String delim = ", ";
|
String delim = ", ";
|
||||||
for (String password : GIVEN_PASSWORDS) {
|
for (String password : GIVEN_PASSWORDS) {
|
||||||
if (password.equals(GIVEN_PASSWORDS[GIVEN_PASSWORDS.length - 1])) {
|
if (password.equals(GIVEN_PASSWORDS[GIVEN_PASSWORDS.length - 1])) {
|
||||||
delim = "); ";
|
delim = "); ";
|
||||||
}
|
}
|
||||||
try {
|
if (method1 != null) {
|
||||||
System.out.println("\t\t\"" + method.computeHash(password, getSalt(method), USERNAME)
|
HashResult hashResult = method1.computeHash(password, USERNAME);
|
||||||
|
System.out.println(String.format("\t\tnew HashResult(\"%s\", \"%s\")%s// %s",
|
||||||
|
hashResult.getHash(), hashResult.getSalt(), delim, password));
|
||||||
|
} else {
|
||||||
|
System.out.println("\t\t\"" + method.computeHash(password, null, USERNAME)
|
||||||
+ "\"" + delim + "// " + password);
|
+ "\"" + delim + "// " + password);
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new IllegalStateException("Could not generate hash", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
System.out.println("\t}");
|
System.out.println("\t}");
|
||||||
System.out.println("\n}");
|
System.out.println("\n}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO #358: Remove this method and use the new salt method on the interface
|
|
||||||
private static String getSalt(EncryptionMethod method) {
|
|
||||||
if (method instanceof BCRYPT) {
|
|
||||||
return BCRYPT.gensalt();
|
|
||||||
} else if (method instanceof MD5 || method instanceof WORDPRESS || method instanceof SMF
|
|
||||||
|| method instanceof SHA512 || method instanceof SHA1 || method instanceof ROYALAUTH
|
|
||||||
|| method instanceof DOUBLEMD5 || method instanceof CRAZYCRYPT1) {
|
|
||||||
return "";
|
|
||||||
} else if (method instanceof JOOMLA || method instanceof SALTEDSHA512) {
|
|
||||||
return PasswordSecurity.createSalt(32);
|
|
||||||
} else if (method instanceof SHA256 || method instanceof PHPBB || method instanceof WHIRLPOOL
|
|
||||||
|| method instanceof MD5VB || method instanceof BCRYPT2Y) {
|
|
||||||
return PasswordSecurity.createSalt(16);
|
|
||||||
} else if (method instanceof WBB3) {
|
|
||||||
return PasswordSecurity.createSalt(40);
|
|
||||||
} else if (method instanceof XAUTH || method instanceof CryptPBKDF2Django
|
|
||||||
|| method instanceof CryptPBKDF2) {
|
|
||||||
return PasswordSecurity.createSalt(12);
|
|
||||||
} else if (method instanceof WBB4) {
|
|
||||||
return BCRYPT.gensalt(8);
|
|
||||||
}
|
|
||||||
System.out.println("Note: Cannot generate salt for unknown encryption method '" + method + "'");
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,20 @@
|
|||||||
package fr.xephi.authme.security.crypts;
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
|
import fr.xephi.authme.settings.Settings;
|
||||||
|
import fr.xephi.authme.util.WrapperMock;
|
||||||
|
import org.junit.Before;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test for {@link BCRYPT}.
|
* Test for {@link BCRYPT}.
|
||||||
*/
|
*/
|
||||||
public class BcryptTest extends AbstractEncryptionMethodTest {
|
public class BcryptTest extends AbstractEncryptionMethodTest {
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUpSettings() {
|
||||||
|
WrapperMock.createInstance();
|
||||||
|
Settings.bCryptLog2Rounds = 8;
|
||||||
|
}
|
||||||
|
|
||||||
public BcryptTest() {
|
public BcryptTest() {
|
||||||
super(new BCRYPT(),
|
super(new BCRYPT(),
|
||||||
"$2a$10$6iATmYgwJVc3YONhVcZFve3Cfb5GnwvKhJ20r.hMjmcNkIT9.Uh9K", // password
|
"$2a$10$6iATmYgwJVc3YONhVcZFve3Cfb5GnwvKhJ20r.hMjmcNkIT9.Uh9K", // password
|
||||||
|
16
src/test/java/fr/xephi/authme/security/crypts/IPB3Test.java
Normal file
16
src/test/java/fr/xephi/authme/security/crypts/IPB3Test.java
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link IPB3}.
|
||||||
|
*/
|
||||||
|
public class IPB3Test extends AbstractEncryptionMethodTest {
|
||||||
|
|
||||||
|
public IPB3Test() {
|
||||||
|
super(new IPB3(),
|
||||||
|
new HashResult("f8ecea1ce42b5babef369ff7692dbe3f", "1715b"), //password
|
||||||
|
new HashResult("40a93731a931352e0619cdf09b975040", "ba91c"), //PassWord1
|
||||||
|
new HashResult("a77ca982373946d5800430bd2947ba11", "a7725"), //&^%te$t?Pw@_
|
||||||
|
new HashResult("383d7b9e2b707d6e894ec7b30e3032c3", "fa9fd")); //âË_3(íù*
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
16
src/test/java/fr/xephi/authme/security/crypts/MYBBTest.java
Normal file
16
src/test/java/fr/xephi/authme/security/crypts/MYBBTest.java
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link MYBB}.
|
||||||
|
*/
|
||||||
|
public class MYBBTest extends AbstractEncryptionMethodTest {
|
||||||
|
|
||||||
|
public MYBBTest() {
|
||||||
|
super(new MYBB(),
|
||||||
|
new HashResult("57c7a16d860833db5030738f5a465d2b", "acdc14e6"), //password
|
||||||
|
new HashResult("08fbdf721f2c42d9780b7d66df0ba830", "792fd7fb"), //PassWord1
|
||||||
|
new HashResult("d602f38fb59ad9e185d5604f5d4ddb36", "4b5534a4"), //&^%te$t?Pw@_
|
||||||
|
new HashResult("b3c39410d0ab8ae2a65c257820797fad", "e5a6cb14")); //âË_3(íù*
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -10,8 +10,7 @@ public class Md5Test extends AbstractEncryptionMethodTest {
|
|||||||
"5f4dcc3b5aa765d61d8327deb882cf99", // password
|
"5f4dcc3b5aa765d61d8327deb882cf99", // password
|
||||||
"f2126d405f46ed603ff5b2950f062c96", // PassWord1
|
"f2126d405f46ed603ff5b2950f062c96", // PassWord1
|
||||||
"0833dcd2bc741f90c46bbac5498fd08f", // &^%te$t?Pw@_
|
"0833dcd2bc741f90c46bbac5498fd08f", // &^%te$t?Pw@_
|
||||||
"d1accd961cb7b688c87278191c1dfed3" // âË_3(íù*
|
"d1accd961cb7b688c87278191c1dfed3"); // âË_3(íù*
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
|
import org.junit.Ignore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link PHPFUSION}.
|
||||||
|
*/
|
||||||
|
@Ignore
|
||||||
|
// TODO #364: Need to skip lowercase/uppercase password test for the non-ASCII one
|
||||||
|
public class PHPFUSIONTest extends AbstractEncryptionMethodTest {
|
||||||
|
|
||||||
|
public PHPFUSIONTest() {
|
||||||
|
super(new PHPFUSION(),
|
||||||
|
new HashResult("f7a606c4eb3fcfbc382906476e05b06f21234a77d1a4eacc0f93f503deb69e70", "6cd1c97c55cb"), // password
|
||||||
|
new HashResult("8a9b7bb706a3347e5f684a7cb905bfb26b9a0d099358064139ab3ed1a66aeb2b", "d6012370b73f"), // PassWord1
|
||||||
|
new HashResult("43f2f23f44c8f89e2dbf06050bc8c77dbcdf71a7b5d28c87ec657d474e63d62d", "f75400a209a4"), // &^%te$t?Pw@_
|
||||||
|
new HashResult("4e7f4eb7e3653d7460f1cf590def4153c6fcdf8b8e16fb95538fdf9e54a95245", "d552e0f5b23a")); // âË_3(íù*
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,20 +1,16 @@
|
|||||||
package fr.xephi.authme.security.crypts;
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
import org.junit.Ignore;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test for {@link SALTEDSHA512}.
|
* Test for {@link SALTEDSHA512}.
|
||||||
*/
|
*/
|
||||||
@Ignore
|
|
||||||
// TODO ljacqu 20151220: Currently cannot test because of closely coupled database call inside of class
|
|
||||||
public class SALTEDSHA512Test extends AbstractEncryptionMethodTest {
|
public class SALTEDSHA512Test extends AbstractEncryptionMethodTest {
|
||||||
|
|
||||||
public SALTEDSHA512Test() {
|
public SALTEDSHA512Test() {
|
||||||
super(new SALTEDSHA512(),
|
super(new SALTEDSHA512(),
|
||||||
"c8efe95e1ab02d9a0e7c7d11d4ac3cc068a8405b5810aac3a1b8b01927ab059563438131dc995156739daf74db40ffdc79b78f6aec9b2a468fe106b88c66c204", // password
|
new HashResult("dea7a37cecf5384ae8e347fd1411efb51364b6ba1b328695de3b354612c1d7010807e8b7051c40f740e498490e1f133e2c2408327d13fbdd68e1b1f6d548e624", "29f8a3c52147f987fee7ba3e0fb311bd"), // password
|
||||||
"74c61af1bcbb3293cdc0959c7323d50be28c167eddc7a1b7eb029e38263c2cfb6eb090f41370a65249752aa316fa851091c2bd8420302e87d383529beea735b4", // PassWord1
|
new HashResult("7c06225aac574d2dc7c81a2ed306637adf025715f52083e05bdab014faaa234e24a97d0e69ea0108dfa77cc9228e58be319ee677e679b5d1ad168d40e50a42f6", "8ea37b85d020b98f60c0fe9b8ec9296c"), // PassWord1
|
||||||
"08eefcca4a17876441ebe61a02e8bc62cab7502dd87f8ec3b7f82edb2adace791b8dad31e74c5513cf99be502b732f5c5efffb239f4590d5c600d066a7037908", // &^%te$t?Pw@_
|
new HashResult("55711adbe03c9616f3505f0d57077fdd528c32243eb6f9840c1a6ff9e553940d6b89790750ebd52ebda63ca793fbe9980d54057af40836820c648750fe22d49c", "9f58079631ef21d32b4710694f1f461b"), // &^%te$t?Pw@_
|
||||||
"a122490c4c7c18ad665b5ac9617c948741468a787a2ba42c6fd2530ea1d7874681b8575ee9a8907c42ff65dac69e4ada2852789759c17d51865ca915b259a65a"); // âË_3(íù*
|
new HashResult("29dc5be8702975ea4563ed3de5b145e2d2f1c37ae661bbe0d3e94d964402cf09d539d65f3b90ff6921ea3d40727f76fb38fb34d1e5c2d62238c4e0203efc372f", "048bb76168265d906f1fd1f81d0616a9")); // âË_3(íù*
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,16 @@
|
|||||||
package fr.xephi.authme.security.crypts;
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
import org.junit.Ignore;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test for {@link WBB3}.
|
* Test for {@link WBB3}.
|
||||||
*/
|
*/
|
||||||
@Ignore
|
|
||||||
// TODO #364 ljacqu 20151220: Unignore test after fixing closely coupled DB dependency
|
|
||||||
public class WBB3Test extends AbstractEncryptionMethodTest {
|
public class WBB3Test extends AbstractEncryptionMethodTest {
|
||||||
|
|
||||||
public WBB3Test() {
|
public WBB3Test() {
|
||||||
super(new WBB3(),
|
super(new WBB3(),
|
||||||
"ca426c4d20a82cd24c7bb07d94d69f3757e3d07d", // password
|
new HashResult("8df818ef7d56075ab2744f74b98ad68a375ccac4", "b7415b355492ea60314f259a35733a3092c03e3f"), // password
|
||||||
"72d59d27674a3cace2600ff152ba8b46274e27e9", // PassWord1
|
new HashResult("106da5cf5df92cb845e12cf62cbdb5235b6dc693", "6110f19b2b52910dccf592a19c59126873f42e69"), // PassWord1
|
||||||
"23daf26602e52591156968a14c2a6592b5be4743", // &^%te$t?Pw@_
|
new HashResult("940a9fb7acec0178c6691e8b3c14bd7d789078b1", "f9dd501ff3d1bf74904f9e89649e378429af56e7"), // &^%te$t?Pw@_
|
||||||
"d3908efe4a15314066391dd8572883c70b16fd8a"); // âË_3(íù*
|
new HashResult("0fa12e8d96c9e95f73aa91f3b76f8cdc815ec8a5", "736be8669f6159ddb2d5b47a3e6428cdb8b324de")); // âË_3(íù*
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
package fr.xephi.authme.security.crypts;
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
|
import org.junit.Ignore;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test for {@link WORDPRESS}.
|
* Test for {@link WORDPRESS}.
|
||||||
*/
|
*/
|
||||||
|
@Ignore
|
||||||
|
// TODO #364: Need to skip an assertion due to the "internal salt" of Wordpress
|
||||||
public class WORDPRESSTest extends AbstractEncryptionMethodTest {
|
public class WORDPRESSTest extends AbstractEncryptionMethodTest {
|
||||||
|
|
||||||
public WORDPRESSTest() {
|
public WORDPRESSTest() {
|
||||||
|
17
src/test/java/fr/xephi/authme/security/crypts/XFTest.java
Normal file
17
src/test/java/fr/xephi/authme/security/crypts/XFTest.java
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package fr.xephi.authme.security.crypts;
|
||||||
|
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link XF}.
|
||||||
|
*/
|
||||||
|
@Ignore
|
||||||
|
// TODO #369: XF needs to generate a salt it is expecting
|
||||||
|
public class XFTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldComputeHash() {
|
||||||
|
System.out.println(new XF().computeHash("Test", "name"));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user