diff --git a/authme-core/pom.xml b/authme-core/pom.xml index 5d0f75ef2..8054407fe 100644 --- a/authme-core/pom.xml +++ b/authme-core/pom.xml @@ -62,16 +62,8 @@ postgresql - de.rtner - PBKDF2 - - - de.mkammerer - argon2-jvm-nolibs - - - at.favre.lib - bcrypt + org.bouncycastle + bcprov-jdk18on com.warrenstrange diff --git a/authme-core/src/main/java/fr/xephi/authme/security/crypts/AbstractPbkdf2.java b/authme-core/src/main/java/fr/xephi/authme/security/crypts/AbstractPbkdf2.java index 4f8217b5a..610c96222 100644 --- a/authme-core/src/main/java/fr/xephi/authme/security/crypts/AbstractPbkdf2.java +++ b/authme-core/src/main/java/fr/xephi/authme/security/crypts/AbstractPbkdf2.java @@ -1,9 +1,13 @@ package fr.xephi.authme.security.crypts; -import de.rtner.security.auth.spi.PBKDF2Engine; -import de.rtner.security.auth.spi.PBKDF2Parameters; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.SecuritySettings; +import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator; +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.crypto.params.KeyParameter; + +import java.security.MessageDigest; +import java.nio.charset.StandardCharsets; public abstract class AbstractPbkdf2 extends HexSaltedMethod { @@ -15,12 +19,13 @@ protected AbstractPbkdf2(Settings settings, int defaultRounds) { } protected byte[] deriveKey(String password, byte[] saltBytes, int iterations, int keyLength) { - PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "UTF-8", saltBytes, iterations); - return new PBKDF2Engine(params).deriveKey(password, keyLength); + PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(new SHA256Digest()); + gen.init(password.getBytes(StandardCharsets.UTF_8), saltBytes, iterations); + return ((KeyParameter) gen.generateDerivedMacParameters(keyLength * 8)).getKey(); } protected boolean verifyKey(String password, byte[] saltBytes, int iterations, byte[] expectedKey) { - PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "UTF-8", saltBytes, iterations, expectedKey); - return new PBKDF2Engine(params).verifyKey(password); + byte[] computed = deriveKey(password, saltBytes, iterations, expectedKey.length); + return MessageDigest.isEqual(computed, expectedKey); } } diff --git a/authme-core/src/main/java/fr/xephi/authme/security/crypts/Argon2.java b/authme-core/src/main/java/fr/xephi/authme/security/crypts/Argon2.java index e002886ba..fdaa2f0ce 100644 --- a/authme-core/src/main/java/fr/xephi/authme/security/crypts/Argon2.java +++ b/authme-core/src/main/java/fr/xephi/authme/security/crypts/Argon2.java @@ -1,51 +1,96 @@ package fr.xephi.authme.security.crypts; -import de.mkammerer.argon2.Argon2Constants; -import de.mkammerer.argon2.Argon2Factory; -import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.output.ConsoleLoggerFactory; 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 org.bouncycastle.crypto.generators.Argon2BytesGenerator; +import org.bouncycastle.crypto.params.Argon2Parameters; + +import java.security.MessageDigest; +import java.security.SecureRandom; +import java.util.Base64; @Recommendation(Usage.RECOMMENDED) -@HasSalt(value = SaltType.TEXT, length = Argon2Constants.DEFAULT_SALT_LENGTH) +@HasSalt(value = SaltType.TEXT, length = 16) // Note: Argon2 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 Argon2 extends UnsaltedMethod { - private static ConsoleLogger logger = ConsoleLoggerFactory.get(Argon2.class); + private static final int ITERATIONS = 2; + private static final int MEMORY_KB = 65536; + private static final int PARALLELISM = 1; + private static final int SALT_BYTES = 16; + private static final int HASH_BYTES = 32; - private de.mkammerer.argon2.Argon2 argon2; + private final SecureRandom random = new SecureRandom(); - public Argon2() { - argon2 = Argon2Factory.create(); + @Override + public String computeHash(String password) { + byte[] salt = new byte[SALT_BYTES]; + random.nextBytes(salt); + byte[] hash = derive(password.toCharArray(), salt, ITERATIONS, MEMORY_KB, PARALLELISM, HASH_BYTES); + Base64.Encoder enc = Base64.getEncoder().withoutPadding(); + return "$argon2i$v=19$m=" + MEMORY_KB + ",t=" + ITERATIONS + ",p=" + PARALLELISM + + "$" + enc.encodeToString(salt) + + "$" + enc.encodeToString(hash); } - /** - * Checks if the argon2 library is available in java.library.path. - * - * @return true if the library is present, false otherwise - */ - public static boolean isLibraryLoaded() { + @Override + public boolean comparePassword(String password, HashedPassword hashedPassword, String name) { + String[] parts = hashedPassword.getHash().split("\\$"); + // Expected: ["", "argon2i", "v=19", "m=65536,t=2,p=1", "", ""] + if (parts.length != 6 || !"argon2i".equals(parts[1])) { + return false; + } try { - System.loadLibrary("argon2"); - return true; - } catch (UnsatisfiedLinkError e) { - logger.logException( - "Cannot find argon2 library: https://github.com/AuthMe/AuthMeReloaded/wiki/Argon2-as-Password-Hash", e); + int[] params = parseParams(parts[3]); // m, t, p + byte[] salt = decodeNoPadding(parts[4]); + byte[] expected = decodeNoPadding(parts[5]); + byte[] computed = derive(password.toCharArray(), salt, params[1], params[0], params[2], expected.length); + return MessageDigest.isEqual(computed, expected); + } catch (IllegalArgumentException e) { + return false; } - return false; } - @Override - public String computeHash(String password) { - return argon2.hash(2, 65536, 1, password); + private static byte[] derive(char[] password, byte[] salt, int iterations, int memoryKb, int parallelism, int hashLen) { + Argon2Parameters params = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_i) + .withVersion(Argon2Parameters.ARGON2_VERSION_13) + .withIterations(iterations) + .withMemoryAsKB(memoryKb) + .withParallelism(parallelism) + .withSalt(salt) + .build(); + Argon2BytesGenerator generator = new Argon2BytesGenerator(); + generator.init(params); + byte[] result = new byte[hashLen]; + generator.generateBytes(password, result); + return result; } - @Override - public boolean comparePassword(String password, HashedPassword hashedPassword, String name) { - return argon2.verify(hashedPassword.getHash(), password); + /** Parses "m=65536,t=2,p=1" → [m, t, p]. */ + private static int[] parseParams(String paramStr) { + int[] result = new int[3]; + for (String kv : paramStr.split(",")) { + String[] pair = kv.split("="); + int v = Integer.parseInt(pair[1]); + switch (pair[0]) { + case "m": result[0] = v; break; + case "t": result[1] = v; break; + case "p": result[2] = v; break; + } + } + return result; + } + + /** Decodes base64 without padding (PHC format omits '='). */ + private static byte[] decodeNoPadding(String s) { + switch (s.length() % 4) { + case 2: s += "=="; break; + case 3: s += "="; break; + default: break; + } + return Base64.getDecoder().decode(s); } } diff --git a/authme-core/src/main/java/fr/xephi/authme/security/crypts/BCrypt.java b/authme-core/src/main/java/fr/xephi/authme/security/crypts/BCrypt.java index 5b75c89af..09bef8822 100644 --- a/authme-core/src/main/java/fr/xephi/authme/security/crypts/BCrypt.java +++ b/authme-core/src/main/java/fr/xephi/authme/security/crypts/BCrypt.java @@ -1,6 +1,5 @@ package fr.xephi.authme.security.crypts; -import at.favre.lib.crypto.bcrypt.BCrypt.Version; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.HooksSettings; @@ -18,6 +17,6 @@ public BCrypt(Settings settings) { private static BCryptHasher createHasher(Settings settings) { int bCryptLog2Rounds = settings.getProperty(HooksSettings.BCRYPT_LOG2_ROUND); - return new BCryptHasher(Version.VERSION_2A, bCryptLog2Rounds); + return new BCryptHasher("2a", bCryptLog2Rounds); } } diff --git a/authme-core/src/main/java/fr/xephi/authme/security/crypts/BCrypt2y.java b/authme-core/src/main/java/fr/xephi/authme/security/crypts/BCrypt2y.java index 2558fa98a..8b04bf7fe 100644 --- a/authme-core/src/main/java/fr/xephi/authme/security/crypts/BCrypt2y.java +++ b/authme-core/src/main/java/fr/xephi/authme/security/crypts/BCrypt2y.java @@ -1,7 +1,5 @@ package fr.xephi.authme.security.crypts; -import at.favre.lib.crypto.bcrypt.BCrypt; - import javax.inject.Inject; /** @@ -15,6 +13,6 @@ public BCrypt2y() { } public BCrypt2y(int cost) { - super(new BCryptHasher(BCrypt.Version.VERSION_2Y, cost)); + super(new BCryptHasher("2y", cost)); } } diff --git a/authme-core/src/main/java/fr/xephi/authme/security/crypts/BCryptHasher.java b/authme-core/src/main/java/fr/xephi/authme/security/crypts/BCryptHasher.java index ffa1064dc..55c5413b8 100644 --- a/authme-core/src/main/java/fr/xephi/authme/security/crypts/BCryptHasher.java +++ b/authme-core/src/main/java/fr/xephi/authme/security/crypts/BCryptHasher.java @@ -1,13 +1,13 @@ package fr.xephi.authme.security.crypts; -import at.favre.lib.crypto.bcrypt.BCrypt; -import fr.xephi.authme.security.HashUtils; import fr.xephi.authme.util.RandomStringUtils; +import org.bouncycastle.crypto.generators.OpenBSDBCrypt; -import static java.nio.charset.StandardCharsets.UTF_8; +import java.security.SecureRandom; +import java.util.Arrays; /** - * Wraps a {@link BCrypt.Hasher} instance and provides methods suitable for use in AuthMe. + * Wraps BouncyCastle's {@link OpenBSDBCrypt} and provides methods suitable for use in AuthMe. */ public class BCryptHasher { @@ -16,28 +16,29 @@ public class BCryptHasher { /** Number of characters of the salt in its radix64-encoded form. */ public static final int SALT_LENGTH_ENCODED = 22; - private final BCrypt.Hasher hasher; + private final String version; private final int costFactor; /** * Constructor. * - * @param version the BCrypt version the instance should generate + * @param version the BCrypt version string ("2a" or "2y") * @param costFactor the log2 cost factor to use */ - public BCryptHasher(BCrypt.Version version, int costFactor) { - this.hasher = BCrypt.with(version); + public BCryptHasher(String version, int costFactor) { + this.version = version; this.costFactor = costFactor; } public HashedPassword hash(String password) { - byte[] hash = hasher.hash(costFactor, password.getBytes(UTF_8)); - return new HashedPassword(new String(hash, UTF_8)); + byte[] salt = new byte[BYTES_IN_SALT]; + new SecureRandom().nextBytes(salt); + String hash = OpenBSDBCrypt.generate(version, password.toCharArray(), salt, costFactor); + return new HashedPassword(hash); } public String hashWithRawSalt(String password, byte[] rawSalt) { - byte[] hash = hasher.hash(costFactor, rawSalt, password.getBytes(UTF_8)); - return new String(hash, UTF_8); + return OpenBSDBCrypt.generate(version, password.toCharArray(), rawSalt, costFactor); } /** @@ -48,31 +49,61 @@ public String hashWithRawSalt(String password, byte[] rawSalt) { * @return true if the password matches the hash, false otherwise */ public static boolean comparePassword(String password, String hash) { - if (HashUtils.isValidBcryptHash(hash)) { - BCrypt.Result result = BCrypt.verifyer().verify(password.getBytes(UTF_8), hash.getBytes(UTF_8)); - return result.verified; + if (fr.xephi.authme.security.HashUtils.isValidBcryptHash(hash)) { + return OpenBSDBCrypt.checkPassword(hash, password.toCharArray()); } return false; } /** * Generates a salt for usage in BCrypt. The returned salt is not yet encoded. - *

- * Internally, the BCrypt library in {@link BCrypt.Hasher#hash(int, byte[])} uses the following: - * {@code Bytes.random(16, secureRandom).encodeUtf8();} - *

- * Because our {@link EncryptionMethod} interface works with {@code String} types we need to make sure that the - * generated bytes in the salt are suitable for conversion into a String, such that calling String#getBytes will - * yield the same number of bytes again. Thus, we are forced to limit the range of characters we use. Ideally we'd - * only have to pass the salt in its encoded form so that we could make use of the entire "spectrum" of values, - * which proves difficult to achieve with the underlying BCrypt library. However, the salt needs to be generated - * manually only for testing purposes; production code should always hash passwords using - * {@link EncryptionMethod#computeHash(String, String)}, which internally may represent salts in more suitable - * formats. * * @return the salt for a BCrypt hash */ public static String generateSalt() { return RandomStringUtils.generateLowerUpper(BYTES_IN_SALT); } + + // BCrypt modified-base64 alphabet (OpenBSD variant) + private static final byte[] B64_INDEX; + static { + B64_INDEX = new byte[128]; + Arrays.fill(B64_INDEX, (byte) -1); + String table = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + for (int i = 0; i < table.length(); i++) { + B64_INDEX[table.charAt(i)] = (byte) i; + } + } + + /** + * Decodes a BCrypt-modified-base64 encoded salt (22 chars) into raw bytes (16 bytes). + * The BCrypt alphabet differs from standard base64 and uses a slightly different character set. + * + * @param saltB64 the 22-character BCrypt-base64 encoded salt + * @return 16 raw salt bytes + */ + public static byte[] decodeSalt(String saltB64) { + byte[] out = new byte[BYTES_IN_SALT]; + for (int i = 0, j = 0; i < saltB64.length() - 1 && j < BYTES_IN_SALT; ) { + int c0 = b64Char(saltB64, i++); + int c1 = b64Char(saltB64, i++); + out[j++] = (byte) ((c0 << 2) | (c1 >> 4)); + if (j >= BYTES_IN_SALT || i >= saltB64.length()) break; + int c2 = b64Char(saltB64, i++); + out[j++] = (byte) (((c1 & 0x0f) << 4) | (c2 >> 2)); + if (j >= BYTES_IN_SALT || i >= saltB64.length()) break; + int c3 = b64Char(saltB64, i++); + out[j++] = (byte) (((c2 & 0x03) << 6) | c3); + } + return out; + } + + private static int b64Char(String s, int pos) { + char c = s.charAt(pos); + int v = (c < 128) ? B64_INDEX[c] : -1; + if (v == -1) { + throw new IllegalArgumentException("Invalid BCrypt base64 character '" + c + "'"); + } + return v; + } } diff --git a/authme-core/src/main/java/fr/xephi/authme/security/crypts/Ipb4.java b/authme-core/src/main/java/fr/xephi/authme/security/crypts/Ipb4.java index e176d0aab..41ab05f36 100644 --- a/authme-core/src/main/java/fr/xephi/authme/security/crypts/Ipb4.java +++ b/authme-core/src/main/java/fr/xephi/authme/security/crypts/Ipb4.java @@ -1,15 +1,11 @@ package fr.xephi.authme.security.crypts; -import at.favre.lib.crypto.bcrypt.BCrypt; -import at.favre.lib.crypto.bcrypt.IllegalBCryptFormatException; 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.util.RandomStringUtils; -import static java.nio.charset.StandardCharsets.UTF_8; - /** * Implementation for Ipb4 (Invision Power Board 4). @@ -22,21 +18,16 @@ @HasSalt(value = SaltType.TEXT, length = BCryptHasher.SALT_LENGTH_ENCODED) public class Ipb4 implements EncryptionMethod { - private BCryptHasher bCryptHasher = new BCryptHasher(BCrypt.Version.VERSION_2A, 13); + private BCryptHasher bCryptHasher = new BCryptHasher("2a", 13); @Override public String computeHash(String password, String salt, String name) { - // Since the radix64-encoded salt is necessary to be stored separately as well, the incoming salt here is - // radix64-encoded (see #generateSalt()). This means we first need to decode it before passing into the - // bcrypt hasher... We cheat by inserting the encoded salt into a dummy bcrypt hash so that we can parse it - // with the BCrypt utilities. - // This method (with specific salt) is only used for testing purposes, so this approach should be OK. - - String dummyHash = "$2a$10$" + salt + "3Cfb5GnwvKhJ20r.hMjmcNkIT9.Uh9K"; + // The salt here is the 22-char BCrypt-modified-base64 encoded salt (see #generateSalt). + // This method (with specific salt) is only used for testing purposes. try { - BCrypt.HashData parseResult = BCrypt.Version.VERSION_2A.parser.parse(dummyHash.getBytes(UTF_8)); - return bCryptHasher.hashWithRawSalt(password, parseResult.rawSalt); - } catch (IllegalBCryptFormatException |IllegalArgumentException e) { + byte[] rawSalt = BCryptHasher.decodeSalt(salt); + return bCryptHasher.hashWithRawSalt(password, rawSalt); + } catch (IllegalArgumentException e) { throw new IllegalStateException("Cannot parse hash with salt '" + salt + "'", e); } } @@ -45,7 +36,7 @@ public String computeHash(String password, String salt, String name) { public HashedPassword computeHash(String password, String name) { HashedPassword hash = bCryptHasher.hash(password); - // 7 chars prefix, then 22 chars which is the encoded salt, which we need again + // 7 chars prefix ($2a$XX$), then 22 chars which is the encoded salt, which we need again String salt = hash.getHash().substring(7, 29); return new HashedPassword(hash.getHash(), salt); } diff --git a/authme-core/src/main/java/fr/xephi/authme/security/crypts/Pbkdf2.java b/authme-core/src/main/java/fr/xephi/authme/security/crypts/Pbkdf2.java index 2be6ad156..03745542b 100644 --- a/authme-core/src/main/java/fr/xephi/authme/security/crypts/Pbkdf2.java +++ b/authme-core/src/main/java/fr/xephi/authme/security/crypts/Pbkdf2.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; import com.google.common.primitives.Ints; -import de.rtner.misc.BinTools; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.Usage; import fr.xephi.authme.settings.Settings; +import org.bouncycastle.util.encoders.Hex; import javax.inject.Inject; @@ -24,7 +24,7 @@ public class Pbkdf2 extends AbstractPbkdf2 { @Override public String computeHash(String password, String salt, String name) { return "pbkdf2_sha256$" + numberOfRounds + "$" + salt + "$" - + BinTools.bin2hex(deriveKey(password, salt.getBytes(), numberOfRounds, 64)); + + Hex.toHexString(deriveKey(password, salt.getBytes(), numberOfRounds, 64)).toUpperCase(); } @Override @@ -38,7 +38,7 @@ public boolean comparePassword(String password, HashedPassword hashedPassword, S logger.warning("Cannot read number of rounds for Pbkdf2: '" + line[1] + "'"); return false; } - return verifyKey(password, line[2].getBytes(), iterations, BinTools.hex2bin(line[3])); + return verifyKey(password, line[2].getBytes(), iterations, Hex.decode(line[3])); } @Override diff --git a/authme-core/src/main/java/fr/xephi/authme/security/crypts/Pbkdf2Django.java b/authme-core/src/main/java/fr/xephi/authme/security/crypts/Pbkdf2Django.java index a0877cd4b..8dda3de15 100644 --- a/authme-core/src/main/java/fr/xephi/authme/security/crypts/Pbkdf2Django.java +++ b/authme-core/src/main/java/fr/xephi/authme/security/crypts/Pbkdf2Django.java @@ -1,27 +1,29 @@ package fr.xephi.authme.security.crypts; import com.google.common.primitives.Ints; -import de.rtner.security.auth.spi.PBKDF2Engine; -import de.rtner.security.auth.spi.PBKDF2Parameters; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.security.crypts.description.AsciiRestricted; +import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator; +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.crypto.params.KeyParameter; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; import java.util.Base64; @AsciiRestricted public class Pbkdf2Django extends HexSaltedMethod { private static final int DEFAULT_ITERATIONS = 24000; + private static final int HASH_BYTES = 32; private final ConsoleLogger logger = ConsoleLoggerFactory.get(Pbkdf2Django.class); @Override public String computeHash(String password, String salt, String name) { - String result = "pbkdf2_sha256$" + DEFAULT_ITERATIONS + "$" + salt + "$"; - PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "ASCII", salt.getBytes(), DEFAULT_ITERATIONS); - PBKDF2Engine engine = new PBKDF2Engine(params); - - return result + Base64.getEncoder().encodeToString(engine.deriveKey(password, 32)); + byte[] derived = derive(password, salt.getBytes(StandardCharsets.US_ASCII), DEFAULT_ITERATIONS); + return "pbkdf2_sha256$" + DEFAULT_ITERATIONS + "$" + salt + "$" + + Base64.getEncoder().encodeToString(derived); } @Override @@ -35,12 +37,10 @@ public boolean comparePassword(String password, HashedPassword hashedPassword, S logger.warning("Cannot read number of rounds for Pbkdf2Django: '" + line[1] + "'"); return false; } - String salt = line[2]; - byte[] derivedKey = Base64.getDecoder().decode(line[3]); - PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "ASCII", salt.getBytes(), iterations, derivedKey); - PBKDF2Engine engine = new PBKDF2Engine(params); - return engine.verifyKey(password); + byte[] expected = Base64.getDecoder().decode(line[3]); + byte[] computed = derive(password, salt.getBytes(StandardCharsets.US_ASCII), iterations); + return MessageDigest.isEqual(computed, expected); } @Override @@ -48,4 +48,9 @@ public int getSaltLength() { return 12; } + private static byte[] derive(String password, byte[] saltBytes, int iterations) { + PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(new SHA256Digest()); + gen.init(password.getBytes(StandardCharsets.US_ASCII), saltBytes, iterations); + return ((KeyParameter) gen.generateDerivedMacParameters(HASH_BYTES * 8)).getKey(); + } } diff --git a/authme-core/src/main/java/fr/xephi/authme/security/crypts/Wbb4.java b/authme-core/src/main/java/fr/xephi/authme/security/crypts/Wbb4.java index a55a2d48b..ab11c7a7a 100644 --- a/authme-core/src/main/java/fr/xephi/authme/security/crypts/Wbb4.java +++ b/authme-core/src/main/java/fr/xephi/authme/security/crypts/Wbb4.java @@ -1,7 +1,5 @@ package fr.xephi.authme.security.crypts; -import at.favre.lib.crypto.bcrypt.BCrypt; -import at.favre.lib.crypto.bcrypt.IllegalBCryptFormatException; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.security.crypts.description.HasSalt; @@ -9,19 +7,19 @@ import fr.xephi.authme.security.crypts.description.SaltType; import fr.xephi.authme.security.crypts.description.Usage; +import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import static fr.xephi.authme.security.HashUtils.isEqual; import static fr.xephi.authme.security.crypts.BCryptHasher.BYTES_IN_SALT; import static fr.xephi.authme.security.crypts.BCryptHasher.SALT_LENGTH_ENCODED; -import static java.nio.charset.StandardCharsets.UTF_8; @Recommendation(Usage.RECOMMENDED) @HasSalt(value = SaltType.TEXT, length = SALT_LENGTH_ENCODED) public class Wbb4 implements EncryptionMethod { private final ConsoleLogger logger = ConsoleLoggerFactory.get(Wbb4.class); - private BCryptHasher bCryptHasher = new BCryptHasher(BCrypt.Version.VERSION_2A, 8); + private BCryptHasher bCryptHasher = new BCryptHasher("2a", 8); private SecureRandom random = new SecureRandom(); @Override @@ -35,17 +33,22 @@ public HashedPassword computeHash(String password, String name) { @Override public String computeHash(String password, String salt, String name) { - return hashInternal(password, salt.getBytes(UTF_8)); + return hashInternal(password, salt.getBytes(StandardCharsets.UTF_8)); } @Override public boolean comparePassword(String password, HashedPassword hashedPassword, String name) { + String hash = hashedPassword.getHash(); + // BCrypt hash format: $2a$<2-digit-cost>$<22-char-salt><31-char-hash> + // Salt starts at index 7 ($2a$08$ = 7 chars), is 22 chars long + if (hash.length() < 29) { + return false; + } try { - BCrypt.HashData hashData = BCrypt.Version.VERSION_2A.parser.parse(hashedPassword.getHash().getBytes(UTF_8)); - byte[] salt = hashData.rawSalt; + byte[] salt = BCryptHasher.decodeSalt(hash.substring(7, 29)); String computedHash = hashInternal(password, salt); - return isEqual(hashedPassword.getHash(), computedHash); - } catch (IllegalBCryptFormatException | IllegalArgumentException e) { + return isEqual(hash, computedHash); + } catch (Exception e) { logger.logException("Invalid WBB4 hash:", e); } return false; diff --git a/authme-core/src/main/java/fr/xephi/authme/security/crypts/XfBCrypt.java b/authme-core/src/main/java/fr/xephi/authme/security/crypts/XfBCrypt.java index 749b5f57f..109b3d118 100644 --- a/authme-core/src/main/java/fr/xephi/authme/security/crypts/XfBCrypt.java +++ b/authme-core/src/main/java/fr/xephi/authme/security/crypts/XfBCrypt.java @@ -1,7 +1,5 @@ package fr.xephi.authme.security.crypts; -import at.favre.lib.crypto.bcrypt.BCrypt; - import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -11,7 +9,7 @@ public class XfBCrypt extends BCryptBasedHash { private static final Pattern HASH_PATTERN = Pattern.compile("\"hash\";s.*\"(.*)?\""); XfBCrypt() { - super(new BCryptHasher(BCrypt.Version.VERSION_2A, 10)); + super(new BCryptHasher("2a", 10)); } /** diff --git a/authme-core/src/main/java/fr/xephi/authme/settings/SettingsWarner.java b/authme-core/src/main/java/fr/xephi/authme/settings/SettingsWarner.java index c1141d388..b1419f153 100644 --- a/authme-core/src/main/java/fr/xephi/authme/settings/SettingsWarner.java +++ b/authme-core/src/main/java/fr/xephi/authme/settings/SettingsWarner.java @@ -3,12 +3,9 @@ import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.output.ConsoleLoggerFactory; -import fr.xephi.authme.security.HashAlgorithm; -import fr.xephi.authme.security.crypts.Argon2; import fr.xephi.authme.settings.properties.EmailSettings; import fr.xephi.authme.settings.properties.PluginSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; -import fr.xephi.authme.settings.properties.SecuritySettings; import javax.inject.Inject; @@ -53,13 +50,6 @@ public void logWarningsForMisconfigurations() { logger.warning("Warning: Session timeout needs to be positive in order to work!"); } - // Check if argon2 library is present and can be loaded - if (settings.getProperty(SecuritySettings.PASSWORD_HASH).equals(HashAlgorithm.ARGON2) - && !Argon2.isLibraryLoaded()) { - logger.warning("WARNING!!! You use Argon2 Hash Algorithm method but we can't find the Argon2 " - + "library on your system! See https://github.com/AuthMe/AuthMeReloaded/wiki/Argon2-as-Password-Hash"); - authMe.stopOrUnload(); - } } } diff --git a/authme-core/src/main/resources/plugin.yml b/authme-core/src/main/resources/plugin.yml index 53f391a32..caa3a3b42 100644 --- a/authme-core/src/main/resources/plugin.yml +++ b/authme-core/src/main/resources/plugin.yml @@ -27,9 +27,7 @@ libraries: - com.mysql:mysql-connector-j:${dependencies.mysql-connector-j.version} - org.mariadb.jdbc:mariadb-java-client:${dependencies.mariadb-java-client.version} - org.postgresql:postgresql:${dependencies.postgresql.version} - - de.rtner:PBKDF2:${dependencies.pbkdf2.version} - - de.mkammerer:argon2-jvm-nolibs:${dependencies.argon2-jvm-nolibs.version} - - at.favre.lib:bcrypt:${dependencies.bcrypt.version} + - org.bouncycastle:bcprov-jdk18on:${dependencies.bcprov.version} - com.warrenstrange:googleauth:${dependencies.googleauth.version} - ch.jalu:configme:${dependencies.configme.version} - org.bstats:bstats-bukkit:${dependencies.bstats.version} diff --git a/authme-core/src/test/java/fr/xephi/authme/security/HashAlgorithmIntegrationTest.java b/authme-core/src/test/java/fr/xephi/authme/security/HashAlgorithmIntegrationTest.java index 13147dcb8..fa94ef48a 100644 --- a/authme-core/src/test/java/fr/xephi/authme/security/HashAlgorithmIntegrationTest.java +++ b/authme-core/src/test/java/fr/xephi/authme/security/HashAlgorithmIntegrationTest.java @@ -3,7 +3,6 @@ import ch.jalu.injector.Injector; import ch.jalu.injector.InjectorBuilder; import fr.xephi.authme.TestHelper; -import fr.xephi.authme.security.crypts.Argon2; import fr.xephi.authme.security.crypts.EncryptionMethod; import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.security.crypts.description.Recommendation; @@ -64,10 +63,6 @@ public void shouldBeAbleToInstantiateEncryptionAlgorithms() { // given / when / then for (HashAlgorithm algorithm : HashAlgorithm.values()) { if (!HashAlgorithm.CUSTOM.equals(algorithm) && !HashAlgorithm.PLAINTEXT.equals(algorithm)) { - if (HashAlgorithm.ARGON2.equals(algorithm) && !Argon2.isLibraryLoaded()) { - System.out.println("[WARNING] Cannot find argon2 library, skipping integration test"); - continue; - } EncryptionMethod method = injector.createIfHasDependencies(algorithm.getClazz()); if (method == null) { fail("Could not create '" + algorithm.getClazz() + "' - forgot to provide some class?"); diff --git a/authme-core/src/test/java/fr/xephi/authme/security/crypts/Argon2Test.java b/authme-core/src/test/java/fr/xephi/authme/security/crypts/Argon2Test.java index a1736148f..0a46003c7 100644 --- a/authme-core/src/test/java/fr/xephi/authme/security/crypts/Argon2Test.java +++ b/authme-core/src/test/java/fr/xephi/authme/security/crypts/Argon2Test.java @@ -1,22 +1,16 @@ package fr.xephi.authme.security.crypts; -import static org.junit.jupiter.api.Assumptions.assumeTrue; - /** * Test for {@link Argon2}. */ public class Argon2Test extends AbstractEncryptionMethodTest { - private static final boolean IS_LIBRARY_LOADED = Argon2.isLibraryLoaded(); - public Argon2Test() { super(new Argon2(), "$argon2i$v=19$m=65536,t=2,p=1$dOP8NiXsPTcMgzI4Z8Rbew$ShdowtoTEWTL5UTFz1UgQOigb9JOlm4ZxWPA6WbIeUw", // password "$argon2i$v=19$m=65536,t=2,p=1$amZHbPfgc5peKd/4w1AI1g$Q2PUiOVw47TACijP57U0xf7QfiZ00HV4eFzMDA6yKRE", // PassWord1 "$argon2i$v=19$m=65536,t=2,p=1$58v7dWNn9/bpD00QLzSebw$7cMC7p0qceE3Mgf2yQp4X7c+UkO9oyJwQ7S6XTBubNs", // &^%te$t?Pw@_ "$argon2i$v=19$m=65536,t=2,p=1$93OSU71DgBOzpmhti7+6rQ$sSSI6QQQdoG9DlGwLjYz576kTek89nwr9CyNpy6bsL0"); // âË_3(íù* - - assumeTrue(IS_LIBRARY_LOADED, "Argon2 library is not loaded - skipping test"); } @Override diff --git a/authme-folia/src/main/resources/plugin.yml b/authme-folia/src/main/resources/plugin.yml index 47cf2235a..e206119f6 100644 --- a/authme-folia/src/main/resources/plugin.yml +++ b/authme-folia/src/main/resources/plugin.yml @@ -28,9 +28,7 @@ libraries: - com.mysql:mysql-connector-j:${dependencies.mysql-connector-j.version} - org.mariadb.jdbc:mariadb-java-client:${dependencies.mariadb-java-client.version} - org.postgresql:postgresql:${dependencies.postgresql.version} - - de.rtner:PBKDF2:${dependencies.pbkdf2.version} - - de.mkammerer:argon2-jvm-nolibs:${dependencies.argon2-jvm-nolibs.version} - - at.favre.lib:bcrypt:${dependencies.bcrypt.version} + - org.bouncycastle:bcprov-jdk18on:${dependencies.bcprov.version} - com.warrenstrange:googleauth:${dependencies.googleauth.version} - ch.jalu:configme:${dependencies.configme.version} - org.bstats:bstats-bukkit:${dependencies.bstats.version} diff --git a/authme-paper/src/main/resources/plugin.yml b/authme-paper/src/main/resources/plugin.yml index dd864cb84..a001d537f 100644 --- a/authme-paper/src/main/resources/plugin.yml +++ b/authme-paper/src/main/resources/plugin.yml @@ -27,9 +27,7 @@ libraries: - com.mysql:mysql-connector-j:${dependencies.mysql-connector-j.version} - org.mariadb.jdbc:mariadb-java-client:${dependencies.mariadb-java-client.version} - org.postgresql:postgresql:${dependencies.postgresql.version} - - de.rtner:PBKDF2:${dependencies.pbkdf2.version} - - de.mkammerer:argon2-jvm-nolibs:${dependencies.argon2-jvm-nolibs.version} - - at.favre.lib:bcrypt:${dependencies.bcrypt.version} + - org.bouncycastle:bcprov-jdk18on:${dependencies.bcprov.version} - com.warrenstrange:googleauth:${dependencies.googleauth.version} - ch.jalu:configme:${dependencies.configme.version} - org.bstats:bstats-bukkit:${dependencies.bstats.version} diff --git a/authme-spigot-1.21/src/main/resources/plugin.yml b/authme-spigot-1.21/src/main/resources/plugin.yml index dd864cb84..a001d537f 100644 --- a/authme-spigot-1.21/src/main/resources/plugin.yml +++ b/authme-spigot-1.21/src/main/resources/plugin.yml @@ -27,9 +27,7 @@ libraries: - com.mysql:mysql-connector-j:${dependencies.mysql-connector-j.version} - org.mariadb.jdbc:mariadb-java-client:${dependencies.mariadb-java-client.version} - org.postgresql:postgresql:${dependencies.postgresql.version} - - de.rtner:PBKDF2:${dependencies.pbkdf2.version} - - de.mkammerer:argon2-jvm-nolibs:${dependencies.argon2-jvm-nolibs.version} - - at.favre.lib:bcrypt:${dependencies.bcrypt.version} + - org.bouncycastle:bcprov-jdk18on:${dependencies.bcprov.version} - com.warrenstrange:googleauth:${dependencies.googleauth.version} - ch.jalu:configme:${dependencies.configme.version} - org.bstats:bstats-bukkit:${dependencies.bstats.version} diff --git a/authme-spigot-legacy/src/main/resources/plugin.yml b/authme-spigot-legacy/src/main/resources/plugin.yml index dd864cb84..a001d537f 100644 --- a/authme-spigot-legacy/src/main/resources/plugin.yml +++ b/authme-spigot-legacy/src/main/resources/plugin.yml @@ -27,9 +27,7 @@ libraries: - com.mysql:mysql-connector-j:${dependencies.mysql-connector-j.version} - org.mariadb.jdbc:mariadb-java-client:${dependencies.mariadb-java-client.version} - org.postgresql:postgresql:${dependencies.postgresql.version} - - de.rtner:PBKDF2:${dependencies.pbkdf2.version} - - de.mkammerer:argon2-jvm-nolibs:${dependencies.argon2-jvm-nolibs.version} - - at.favre.lib:bcrypt:${dependencies.bcrypt.version} + - org.bouncycastle:bcprov-jdk18on:${dependencies.bcprov.version} - com.warrenstrange:googleauth:${dependencies.googleauth.version} - ch.jalu:configme:${dependencies.configme.version} - org.bstats:bstats-bukkit:${dependencies.bstats.version} diff --git a/authme-tools/pom.xml b/authme-tools/pom.xml index e86fc708d..d2919c8ef 100644 --- a/authme-tools/pom.xml +++ b/authme-tools/pom.xml @@ -74,18 +74,8 @@ test - de.rtner - PBKDF2 - test - - - de.mkammerer - argon2-jvm-nolibs - test - - - at.favre.lib - bcrypt + org.bouncycastle + bcprov-jdk18on test diff --git a/authme-tools/src/test/java/tools/docs/hashmethods/EncryptionMethodInfoGatherer.java b/authme-tools/src/test/java/tools/docs/hashmethods/EncryptionMethodInfoGatherer.java index 5972ba749..3a615bcf2 100644 --- a/authme-tools/src/test/java/tools/docs/hashmethods/EncryptionMethodInfoGatherer.java +++ b/authme-tools/src/test/java/tools/docs/hashmethods/EncryptionMethodInfoGatherer.java @@ -2,12 +2,10 @@ import ch.jalu.injector.Injector; import ch.jalu.injector.InjectorBuilder; -import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.TestHelper; import fr.xephi.authme.security.HashAlgorithm; -import fr.xephi.authme.security.crypts.Argon2; import fr.xephi.authme.security.crypts.EncryptionMethod; import fr.xephi.authme.security.crypts.HexSaltedMethod; import fr.xephi.authme.security.crypts.description.AsciiRestricted; @@ -38,7 +36,7 @@ public class EncryptionMethodInfoGatherer { private Map descriptions; public EncryptionMethodInfoGatherer() { - ConsoleLogger.initialize(Logger.getAnonymousLogger(), null); // set logger because of Argon2.isLibraryLoaded() + ConsoleLogger.initialize(Logger.getAnonymousLogger(), null); descriptions = new LinkedHashMap<>(); constructDescriptions(); } @@ -83,11 +81,6 @@ private static MethodDescription createDescription(HashAlgorithm algorithm) { } private static EncryptionMethod createEncryptionMethod(Class clazz) { - if (clazz == Argon2.class && !Argon2.isLibraryLoaded()) { - // The library for Argon2 isn't installed, so override the hash implementation to avoid using the library - return new Argon2DummyExtension(); - } - EncryptionMethod method = injector.createIfHasDependencies(clazz); if (method == null) { throw new NullPointerException("Failed to instantiate '" + clazz + "'. Is a dependency missing?"); @@ -167,11 +160,4 @@ private static Injector createInitializer() { return injector; } - private static final class Argon2DummyExtension extends Argon2 { - @Override - public String computeHash(String password) { - // Argon2 produces hashes of 96 characters -> return dummy value with this length - return Strings.repeat(".", 96); - } - } } diff --git a/docs/build.md b/docs/build.md index 351e175c4..fa332c285 100644 --- a/docs/build.md +++ b/docs/build.md @@ -82,12 +82,10 @@ The tooling lives in `authme-tools`, but you can run it directly from the reposi mvn -q -pl authme-tools -am -P run-tools process-test-classes # Regenerate repository docs -mvn -q -pl authme-tools -am -P run-tools process-test-classes \ - -Dexec.args=updateDocs +mvn -q -pl authme-tools -am -P run-tools process-test-classes "-Dexec.args=updateDocs" # Regenerate generated command/plugin manifests -mvn -q -pl authme-tools -am -P run-tools process-test-classes \ - "-Dexec.args=generateCommandsYml generatePluginYml" +mvn -q -pl authme-tools -am -P run-tools process-test-classes "-Dexec.args=generateCommandsYml generatePluginYml" ``` ## Generated files diff --git a/pom.xml b/pom.xml index 2e0f5bacf..a6dd20a34 100644 --- a/pom.xml +++ b/pom.xml @@ -95,9 +95,7 @@ 9.3.0 3.5.7 42.7.11 - 1.1.4 - 2.12 - 0.10.2 + 1.79 1.5.0 1.3.1 6.0.3 @@ -577,27 +575,11 @@ - + - de.rtner - PBKDF2 - ${dependencies.pbkdf2.version} - provided - - - - - de.mkammerer - argon2-jvm-nolibs - ${dependencies.argon2-jvm-nolibs.version} - provided - - - - - at.favre.lib - bcrypt - ${dependencies.bcrypt.version} + org.bouncycastle + bcprov-jdk18on + ${dependencies.bcprov.version} provided