Sync repo files to b22
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
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;
|
||||
|
||||
@Recommendation(Usage.RECOMMENDED)
|
||||
@HasSalt(value = SaltType.TEXT, length = Argon2Constants.DEFAULT_SALT_LENGTH)
|
||||
// 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 de.mkammerer.argon2.Argon2 argon2;
|
||||
|
||||
public Argon2() {
|
||||
argon2 = Argon2Factory.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the argon2 library is available in java.library.path.
|
||||
*
|
||||
* @return true if the library is present, false otherwise
|
||||
*/
|
||||
public static boolean isLibraryLoaded() {
|
||||
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);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String computeHash(String password) {
|
||||
return argon2.hash(2, 65536, 1, password);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
|
||||
return argon2.verify(hashedPassword.getHash(), password);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
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;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* BCrypt hash algorithm with configurable cost factor.
|
||||
*/
|
||||
public class BCrypt extends BCryptBasedHash {
|
||||
|
||||
@Inject
|
||||
public BCrypt(Settings settings) {
|
||||
super(createHasher(settings));
|
||||
}
|
||||
|
||||
private static BCryptHasher createHasher(Settings settings) {
|
||||
int bCryptLog2Rounds = settings.getProperty(HooksSettings.BCRYPT_LOG2_ROUND);
|
||||
return new BCryptHasher(Version.VERSION_2A, bCryptLog2Rounds);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package fr.xephi.authme.security.crypts;
|
||||
|
||||
import at.favre.lib.crypto.bcrypt.BCrypt;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Hash for BCrypt in the $2y$ variant. Uses a fixed cost factor of 10.
|
||||
*/
|
||||
public class BCrypt2y extends BCryptBasedHash {
|
||||
|
||||
@Inject
|
||||
public BCrypt2y() {
|
||||
this(10);
|
||||
}
|
||||
|
||||
public BCrypt2y(int cost) {
|
||||
super(new BCryptHasher(BCrypt.Version.VERSION_2Y, cost));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
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;
|
||||
|
||||
import static fr.xephi.authme.security.crypts.BCryptHasher.SALT_LENGTH_ENCODED;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
* Abstract parent for BCrypt-based hash algorithms.
|
||||
*/
|
||||
@Recommendation(Usage.RECOMMENDED)
|
||||
@HasSalt(value = SaltType.TEXT, length = SALT_LENGTH_ENCODED)
|
||||
public abstract class BCryptBasedHash implements EncryptionMethod {
|
||||
|
||||
private final BCryptHasher bCryptHasher;
|
||||
|
||||
public BCryptBasedHash(BCryptHasher bCryptHasher) {
|
||||
this.bCryptHasher = bCryptHasher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashedPassword computeHash(String password, String name) {
|
||||
return bCryptHasher.hash(password);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String computeHash(String password, String salt, String name) {
|
||||
return bCryptHasher.hashWithRawSalt(password, salt.getBytes(UTF_8));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
|
||||
return BCryptHasher.comparePassword(password, hashedPassword.getHash());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateSalt() {
|
||||
return BCryptHasher.generateSalt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSeparateSalt() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
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 static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
* Wraps a {@link BCrypt.Hasher} instance and provides methods suitable for use in AuthMe.
|
||||
*/
|
||||
public class BCryptHasher {
|
||||
|
||||
/** Number of bytes in a BCrypt salt (not encoded). */
|
||||
public static final int BYTES_IN_SALT = 16;
|
||||
/** 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 int costFactor;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param version the BCrypt version the instance should generate
|
||||
* @param costFactor the log2 cost factor to use
|
||||
*/
|
||||
public BCryptHasher(BCrypt.Version version, int costFactor) {
|
||||
this.hasher = BCrypt.with(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));
|
||||
}
|
||||
|
||||
public String hashWithRawSalt(String password, byte[] rawSalt) {
|
||||
byte[] hash = hasher.hash(costFactor, rawSalt, password.getBytes(UTF_8));
|
||||
return new String(hash, UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the given password is correct for the provided BCrypt hash.
|
||||
*
|
||||
* @param password the password to check with
|
||||
* @param hash the hash to check against
|
||||
* @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;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a salt for usage in BCrypt. The returned salt is not yet encoded.
|
||||
* <p>
|
||||
* Internally, the BCrypt library in {@link BCrypt.Hasher#hash(int, byte[])} uses the following:
|
||||
* {@code Bytes.random(16, secureRandom).encodeUtf8();}
|
||||
* <p>
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package fr.xephi.authme.security.crypts;
|
||||
|
||||
import fr.xephi.authme.security.HashUtils;
|
||||
|
||||
/**
|
||||
* Hash algorithm to hook into the CMS <a href="http://craftmywebsite.fr/">Craft My Website</a>.
|
||||
*/
|
||||
public class CmwCrypt extends UnsaltedMethod {
|
||||
|
||||
@Override
|
||||
public String computeHash(String password) {
|
||||
return HashUtils.md5(HashUtils.sha1(password));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package fr.xephi.authme.security.crypts;
|
||||
|
||||
import fr.xephi.authme.security.HashUtils;
|
||||
import fr.xephi.authme.security.MessageDigestAlgorithm;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
public class CrazyCrypt1 extends UsernameSaltMethod {
|
||||
|
||||
private static final char[] CRYPTCHARS =
|
||||
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
|
||||
|
||||
private static String byteArrayToHexString(final byte... args) {
|
||||
final char[] chars = new char[args.length * 2];
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
chars[i * 2] = CRYPTCHARS[(args[i] >> 4) & 0xF];
|
||||
chars[i * 2 + 1] = CRYPTCHARS[(args[i]) & 0xF];
|
||||
}
|
||||
return new String(chars);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashedPassword computeHash(String password, String name) {
|
||||
final String text = "ÜÄaeut//&/=I " + password + "7421€547" + name + "__+IÄIH§%NK " + password;
|
||||
final MessageDigest md = HashUtils.getDigest(MessageDigestAlgorithm.SHA512);
|
||||
md.update(text.getBytes(StandardCharsets.UTF_8), 0, text.length());
|
||||
return new HashedPassword(byteArrayToHexString(md.digest()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package fr.xephi.authme.security.crypts;
|
||||
|
||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||
import fr.xephi.authme.security.crypts.description.Usage;
|
||||
|
||||
import static fr.xephi.authme.security.HashUtils.md5;
|
||||
|
||||
@Deprecated
|
||||
@Recommendation(Usage.DEPRECATED)
|
||||
public class DoubleMd5 extends UnsaltedMethod {
|
||||
|
||||
@Override
|
||||
public String computeHash(String password) {
|
||||
return md5(md5(password));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package fr.xephi.authme.security.crypts;
|
||||
|
||||
/**
|
||||
* Public interface for custom password encryption methods.
|
||||
* <p>
|
||||
* Instantiation of these methods is done via automatic dependency injection.
|
||||
*/
|
||||
public interface EncryptionMethod {
|
||||
|
||||
/**
|
||||
* Hash the given password for the given player name.
|
||||
*
|
||||
* @param password The password to hash
|
||||
* @param name The name of the player (sometimes required to generate a salt with)
|
||||
*
|
||||
* @return The hash result for the password.
|
||||
* @see HashedPassword
|
||||
*/
|
||||
HashedPassword computeHash(String password, String name);
|
||||
|
||||
/**
|
||||
* Hash the given password with the given salt for the given player.
|
||||
*
|
||||
* @param password The password to hash
|
||||
* @param salt The salt to add to the hash
|
||||
* @param name The player's name (sometimes required to generate a salt with)
|
||||
*
|
||||
* @return The hashed password
|
||||
* @see #hasSeparateSalt()
|
||||
*/
|
||||
String computeHash(String password, String salt, String name);
|
||||
|
||||
/**
|
||||
* Check whether the given hash matches the clear-text password.
|
||||
*
|
||||
* @param password The clear-text password to verify
|
||||
* @param hashedPassword The hash to check the password against
|
||||
* @param name The player name to do the check for (sometimes required for generating the salt)
|
||||
*
|
||||
* @return True if the password matches, false otherwise
|
||||
*/
|
||||
boolean comparePassword(String password, HashedPassword hashedPassword, String name);
|
||||
|
||||
/**
|
||||
* Generate a new salt to hash a password with.
|
||||
*
|
||||
* @return The generated salt, null if the method does not use a random text-based salt
|
||||
*/
|
||||
String generateSalt();
|
||||
|
||||
/**
|
||||
* Return whether the encryption method requires the salt to be stored separately and
|
||||
* passed again to {@link #comparePassword(String, HashedPassword, String)}. Note that
|
||||
* an encryption method returning {@code false} does not imply that it uses no salt; it
|
||||
* may be embedded into the hash or it may use the username as salt.
|
||||
*
|
||||
* @return True if the salt has to be stored and retrieved separately, false otherwise
|
||||
*/
|
||||
boolean hasSeparateSalt();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package fr.xephi.authme.security.crypts;
|
||||
|
||||
/**
|
||||
* The result of a hash computation. See {@link #salt} for details.
|
||||
*/
|
||||
public class HashedPassword {
|
||||
|
||||
/** The generated hash. */
|
||||
private final String hash;
|
||||
/**
|
||||
* The generated salt; may be null if no salt is used or if the salt is included
|
||||
* in the hash output. The salt is only not null if {@link EncryptionMethod#hasSeparateSalt()}
|
||||
* returns true for the associated encryption method.
|
||||
* <p>
|
||||
* When the field is not null, it must be stored into the salt column of the data source
|
||||
* and retrieved again to compare a password with the hash.
|
||||
*/
|
||||
private final String salt;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param hash The computed hash
|
||||
* @param salt The generated salt
|
||||
*/
|
||||
public HashedPassword(String hash, String salt) {
|
||||
this.hash = hash;
|
||||
this.salt = salt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for a hash with no separate salt.
|
||||
*
|
||||
* @param hash The computed hash
|
||||
*/
|
||||
public HashedPassword(String hash) {
|
||||
this(hash, null);
|
||||
}
|
||||
|
||||
public String getHash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
public String getSalt() {
|
||||
return salt;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
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;
|
||||
import fr.xephi.authme.util.RandomStringUtils;
|
||||
|
||||
/**
|
||||
* 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 getSaltLength() for length
|
||||
public abstract class HexSaltedMethod implements EncryptionMethod {
|
||||
|
||||
public abstract int getSaltLength();
|
||||
|
||||
@Override
|
||||
public abstract String computeHash(String password, String salt, String name);
|
||||
|
||||
@Override
|
||||
public HashedPassword computeHash(String password, String name) {
|
||||
String salt = generateSalt();
|
||||
return new HashedPassword(computeHash(password, salt, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract boolean comparePassword(String password, HashedPassword hashedPassword, String name);
|
||||
|
||||
@Override
|
||||
public String generateSalt() {
|
||||
return RandomStringUtils.generateHex(getSaltLength());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSeparateSalt() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
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;
|
||||
import fr.xephi.authme.util.RandomStringUtils;
|
||||
|
||||
import static fr.xephi.authme.security.HashUtils.md5;
|
||||
|
||||
@Recommendation(Usage.ACCEPTABLE)
|
||||
@HasSalt(value = SaltType.TEXT, length = 5)
|
||||
public class Ipb3 extends SeparateSaltMethod {
|
||||
|
||||
@Override
|
||||
public String computeHash(String password, String salt, String name) {
|
||||
return md5(md5(salt) + md5(password));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateSalt() {
|
||||
return RandomStringUtils.generateHex(5);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
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).
|
||||
* <p>
|
||||
* The hash uses standard BCrypt with 13 as log<sub>2</sub> number of rounds. Additionally,
|
||||
* Ipb4 requires that the salt be stored in the column "members_pass_hash" as well
|
||||
* (even though BCrypt hashes already contain the salt within themselves).
|
||||
*/
|
||||
@Recommendation(Usage.DOES_NOT_WORK)
|
||||
@HasSalt(value = SaltType.TEXT, length = BCryptHasher.SALT_LENGTH_ENCODED)
|
||||
public class Ipb4 implements EncryptionMethod {
|
||||
|
||||
private BCryptHasher bCryptHasher = new BCryptHasher(BCrypt.Version.VERSION_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";
|
||||
try {
|
||||
BCrypt.HashData parseResult = BCrypt.Version.VERSION_2A.parser.parse(dummyHash.getBytes(UTF_8));
|
||||
return bCryptHasher.hashWithRawSalt(password, parseResult.rawSalt);
|
||||
} catch (IllegalBCryptFormatException |IllegalArgumentException e) {
|
||||
throw new IllegalStateException("Cannot parse hash with salt '" + salt + "'", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
String salt = hash.getHash().substring(7, 29);
|
||||
return new HashedPassword(hash.getHash(), salt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
|
||||
return BCryptHasher.comparePassword(password, hashedPassword.getHash());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateSalt() {
|
||||
return RandomStringUtils.generateLowerUpper(22);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSeparateSalt() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package fr.xephi.authme.security.crypts;
|
||||
|
||||
import fr.xephi.authme.security.HashUtils;
|
||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||
import fr.xephi.authme.security.crypts.description.Usage;
|
||||
|
||||
import static fr.xephi.authme.security.HashUtils.isEqual;
|
||||
|
||||
@Recommendation(Usage.ACCEPTABLE)
|
||||
public class Joomla extends HexSaltedMethod {
|
||||
|
||||
@Override
|
||||
public String computeHash(String password, String salt, String name) {
|
||||
return HashUtils.md5(password + salt) + ":" + salt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String unusedName) {
|
||||
String hash = hashedPassword.getHash();
|
||||
String[] hashParts = hash.split(":");
|
||||
return hashParts.length == 2 && isEqual(hash, computeHash(password, hashParts[1], null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSaltLength() {
|
||||
return 32;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package fr.xephi.authme.security.crypts;
|
||||
|
||||
import fr.xephi.authme.security.HashUtils;
|
||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||
import fr.xephi.authme.security.crypts.description.Usage;
|
||||
|
||||
@Deprecated
|
||||
@Recommendation(Usage.DEPRECATED)
|
||||
public class Md5 extends UnsaltedMethod {
|
||||
|
||||
@Override
|
||||
public String computeHash(String password) {
|
||||
return HashUtils.md5(password);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package fr.xephi.authme.security.crypts;
|
||||
|
||||
import static fr.xephi.authme.security.HashUtils.isEqual;
|
||||
import static fr.xephi.authme.security.HashUtils.md5;
|
||||
|
||||
public class Md5vB extends HexSaltedMethod {
|
||||
|
||||
@Override
|
||||
public String computeHash(String password, String salt, String name) {
|
||||
return "$MD5vb$" + salt + "$" + md5(md5(password) + salt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
|
||||
String hash = hashedPassword.getHash();
|
||||
String[] line = hash.split("\\$");
|
||||
return line.length == 4 && isEqual(hash, computeHash(password, line[2], name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSaltLength() {
|
||||
return 16;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
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;
|
||||
import fr.xephi.authme.util.RandomStringUtils;
|
||||
|
||||
import static fr.xephi.authme.security.HashUtils.md5;
|
||||
|
||||
@Recommendation(Usage.ACCEPTABLE)
|
||||
@HasSalt(value = SaltType.TEXT, length = 8)
|
||||
@SuppressWarnings({"checkstyle:AbbreviationAsWordInName"})
|
||||
public class MyBB extends SeparateSaltMethod {
|
||||
|
||||
@Override
|
||||
public String computeHash(String password, String salt, String name) {
|
||||
return md5(md5(salt) + md5(password));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateSalt() {
|
||||
return RandomStringUtils.generateLowerUpper(8);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package fr.xephi.authme.security.crypts;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import de.rtner.misc.BinTools;
|
||||
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.Recommendation;
|
||||
import fr.xephi.authme.security.crypts.description.Usage;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import fr.xephi.authme.settings.properties.SecuritySettings;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@Recommendation(Usage.RECOMMENDED)
|
||||
public class Pbkdf2 extends HexSaltedMethod {
|
||||
|
||||
private static final int DEFAULT_ROUNDS = 10_000;
|
||||
private final ConsoleLogger logger = ConsoleLoggerFactory.get(Pbkdf2.class);
|
||||
private int numberOfRounds;
|
||||
|
||||
@Inject
|
||||
Pbkdf2(Settings settings) {
|
||||
int configuredRounds = settings.getProperty(SecuritySettings.PBKDF2_NUMBER_OF_ROUNDS);
|
||||
this.numberOfRounds = configuredRounds > 0 ? configuredRounds : DEFAULT_ROUNDS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String computeHash(String password, String salt, String name) {
|
||||
String result = "pbkdf2_sha256$" + numberOfRounds + "$" + salt + "$";
|
||||
PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "UTF-8", salt.getBytes(), numberOfRounds);
|
||||
PBKDF2Engine engine = new PBKDF2Engine(params);
|
||||
|
||||
return result + BinTools.bin2hex(engine.deriveKey(password, 64));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String unusedName) {
|
||||
String[] line = hashedPassword.getHash().split("\\$");
|
||||
if (line.length != 4) {
|
||||
return false;
|
||||
}
|
||||
Integer iterations = Ints.tryParse(line[1]);
|
||||
if (iterations == null) {
|
||||
logger.warning("Cannot read number of rounds for Pbkdf2: '" + line[1] + "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
String salt = line[2];
|
||||
byte[] derivedKey = BinTools.hex2bin(line[3]);
|
||||
PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "UTF-8", salt.getBytes(), iterations, derivedKey);
|
||||
PBKDF2Engine engine = new PBKDF2Engine(params);
|
||||
return engine.verifyKey(password);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSaltLength() {
|
||||
return 16;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
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 java.util.Base64;
|
||||
|
||||
@AsciiRestricted
|
||||
public class Pbkdf2Django extends HexSaltedMethod {
|
||||
|
||||
private static final int DEFAULT_ITERATIONS = 24000;
|
||||
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));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String unusedName) {
|
||||
String[] line = hashedPassword.getHash().split("\\$");
|
||||
if (line.length != 4) {
|
||||
return false;
|
||||
}
|
||||
Integer iterations = Ints.tryParse(line[1]);
|
||||
if (iterations == null) {
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSaltLength() {
|
||||
return 12;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package fr.xephi.authme.security.crypts;
|
||||
|
||||
import fr.xephi.authme.security.HashUtils;
|
||||
import fr.xephi.authme.security.MessageDigestAlgorithm;
|
||||
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.io.UnsupportedEncodingException;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
import static fr.xephi.authme.security.HashUtils.isEqual;
|
||||
import static fr.xephi.authme.security.crypts.BCryptHasher.SALT_LENGTH_ENCODED;
|
||||
|
||||
/**
|
||||
* Encryption method compatible with phpBB3.
|
||||
* <p>
|
||||
* As tested with phpBB 3.2.1, by default new passwords are encrypted with BCrypt $2y$.
|
||||
* For backwards compatibility, phpBB3 supports other hashes for comparison. This implementation
|
||||
* successfully checks against phpBB's salted MD5 hashing algorithm (adaptation of phpass),
|
||||
* as well as plain MD5.
|
||||
*/
|
||||
@Recommendation(Usage.ACCEPTABLE)
|
||||
@HasSalt(value = SaltType.TEXT, length = SALT_LENGTH_ENCODED)
|
||||
public class PhpBB implements EncryptionMethod {
|
||||
|
||||
private final BCrypt2y bCrypt2y = new BCrypt2y();
|
||||
|
||||
@Override
|
||||
public HashedPassword computeHash(String password, String name) {
|
||||
return bCrypt2y.computeHash(password, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String computeHash(String password, String salt, String name) {
|
||||
return bCrypt2y.computeHash(password, salt, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
|
||||
final String hash = hashedPassword.getHash();
|
||||
if (HashUtils.isValidBcryptHash(hash)) {
|
||||
return bCrypt2y.comparePassword(password, hashedPassword, name);
|
||||
} else if (hash.length() == 34) {
|
||||
return PhpassSaltedMd5.phpbb_check_hash(password, hash);
|
||||
} else {
|
||||
return isEqual(hash, PhpassSaltedMd5.md5(password));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateSalt() {
|
||||
// Salt length 22, as seen in https://github.com/phpbb/phpbb/blob/master/phpBB/phpbb/passwords/driver/bcrypt.php
|
||||
// Ours generates 16 chars because the salt must not yet be encoded.
|
||||
return BCryptHasher.generateSalt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSeparateSalt() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Java implementation of the salted MD5 as used in phpBB (adapted from phpass).
|
||||
*
|
||||
* @see <a href="https://github.com/phpbb/phpbb/blob/master/phpBB/phpbb/passwords/driver/salted_md5.php">phpBB's salted_md5.php</a>
|
||||
* @see <a href="http://www.openwall.com/phpass/">phpass</a>
|
||||
*/
|
||||
private static final class PhpassSaltedMd5 {
|
||||
|
||||
private static final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
private static String md5(String data) {
|
||||
try {
|
||||
byte[] bytes = data.getBytes("ISO-8859-1");
|
||||
MessageDigest md5er = HashUtils.getDigest(MessageDigestAlgorithm.MD5);
|
||||
byte[] hash = md5er.digest(bytes);
|
||||
return bytes2hex(hash);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new UnsupportedOperationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static int hexToInt(char ch) {
|
||||
if (ch >= '0' && ch <= '9')
|
||||
return ch - '0';
|
||||
ch = Character.toUpperCase(ch);
|
||||
if (ch >= 'A' && ch <= 'F')
|
||||
return ch - 'A' + 0xA;
|
||||
throw new IllegalArgumentException("Not a hex character: " + ch);
|
||||
}
|
||||
|
||||
private static String bytes2hex(byte[] bytes) {
|
||||
StringBuilder r = new StringBuilder(32);
|
||||
for (byte b : bytes) {
|
||||
String x = Integer.toHexString(b & 0xff);
|
||||
if (x.length() < 2)
|
||||
r.append('0');
|
||||
r.append(x);
|
||||
}
|
||||
return r.toString();
|
||||
}
|
||||
|
||||
private static String pack(String hex) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (int i = 0; i < hex.length(); i += 2) {
|
||||
char c1 = hex.charAt(i);
|
||||
char c2 = hex.charAt(i + 1);
|
||||
char packed = (char) (hexToInt(c1) * 16 + hexToInt(c2));
|
||||
buf.append(packed);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static String _hash_encode64(String input, int count) {
|
||||
StringBuilder output = new StringBuilder();
|
||||
int i = 0;
|
||||
do {
|
||||
int value = input.charAt(i++);
|
||||
output.append(itoa64.charAt(value & 0x3f));
|
||||
if (i < count)
|
||||
value |= input.charAt(i) << 8;
|
||||
output.append(itoa64.charAt((value >> 6) & 0x3f));
|
||||
if (i++ >= count)
|
||||
break;
|
||||
if (i < count)
|
||||
value |= input.charAt(i) << 16;
|
||||
output.append(itoa64.charAt((value >> 12) & 0x3f));
|
||||
if (i++ >= count)
|
||||
break;
|
||||
output.append(itoa64.charAt((value >> 18) & 0x3f));
|
||||
} while (i < count);
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
private static String _hash_crypt_private(String password, String setting) {
|
||||
String output = "*";
|
||||
if (!setting.substring(0, 3).equals("$H$"))
|
||||
return output;
|
||||
int count_log2 = itoa64.indexOf(setting.charAt(3));
|
||||
if (count_log2 < 7 || count_log2 > 30)
|
||||
return output;
|
||||
int count = 1 << count_log2;
|
||||
String salt = setting.substring(4, 12);
|
||||
if (salt.length() != 8)
|
||||
return output;
|
||||
String m1 = md5(salt + password);
|
||||
String hash = pack(m1);
|
||||
do {
|
||||
hash = pack(md5(hash + password));
|
||||
} while (--count > 0);
|
||||
output = setting.substring(0, 12);
|
||||
output += _hash_encode64(hash, 16);
|
||||
return output;
|
||||
}
|
||||
|
||||
private static boolean phpbb_check_hash(String password, String hash) {
|
||||
return isEqual(hash, _hash_crypt_private(password, hash)); // #1561: fix timing issue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package fr.xephi.authme.security.crypts;
|
||||
|
||||
import fr.xephi.authme.security.HashUtils;
|
||||
import fr.xephi.authme.security.crypts.description.AsciiRestricted;
|
||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||
import fr.xephi.authme.security.crypts.description.Usage;
|
||||
import fr.xephi.authme.util.RandomStringUtils;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
@Recommendation(Usage.DO_NOT_USE)
|
||||
@AsciiRestricted
|
||||
public class PhpFusion extends SeparateSaltMethod {
|
||||
|
||||
@Override
|
||||
public String computeHash(String password, String salt, String name) {
|
||||
String algo = "HmacSHA256";
|
||||
String keyString = HashUtils.sha1(salt);
|
||||
try {
|
||||
SecretKeySpec key = new SecretKeySpec(keyString.getBytes("UTF-8"), algo);
|
||||
Mac mac = Mac.getInstance(algo);
|
||||
mac.init(key);
|
||||
byte[] bytes = mac.doFinal(password.getBytes("ASCII"));
|
||||
StringBuilder hash = new StringBuilder();
|
||||
for (byte aByte : bytes) {
|
||||
String hex = Integer.toHexString(0xFF & aByte);
|
||||
if (hex.length() == 1) {
|
||||
hash.append('0');
|
||||
}
|
||||
hash.append(hex);
|
||||
}
|
||||
return hash.toString();
|
||||
} catch (UnsupportedEncodingException | InvalidKeyException | NoSuchAlgorithmException e) {
|
||||
throw new UnsupportedOperationException("Cannot create PHPFUSION hash for " + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateSalt() {
|
||||
return RandomStringUtils.generateHex(12);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package fr.xephi.authme.security.crypts;
|
||||
|
||||
import fr.xephi.authme.security.HashUtils;
|
||||
import fr.xephi.authme.security.MessageDigestAlgorithm;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
|
||||
public class RoyalAuth extends UnsaltedMethod {
|
||||
|
||||
@Override
|
||||
public String computeHash(String password) {
|
||||
MessageDigest algorithm = HashUtils.getDigest(MessageDigestAlgorithm.SHA512);
|
||||
for (int i = 0; i < 25; ++i) {
|
||||
password = HashUtils.hash(password, algorithm);
|
||||
}
|
||||
return password;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
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;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import fr.xephi.authme.settings.properties.SecuritySettings;
|
||||
import fr.xephi.authme.util.RandomStringUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static fr.xephi.authme.security.HashUtils.md5;
|
||||
|
||||
@Recommendation(Usage.ACCEPTABLE) // presuming that length is something sensible (>= 8)
|
||||
@HasSalt(value = SaltType.TEXT) // length defined by the doubleMd5SaltLength setting
|
||||
public class Salted2Md5 extends SeparateSaltMethod {
|
||||
|
||||
private final int saltLength;
|
||||
|
||||
@Inject
|
||||
public Salted2Md5(Settings settings) {
|
||||
saltLength = settings.getProperty(SecuritySettings.DOUBLE_MD5_SALT_LENGTH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String computeHash(String password, String salt, String name) {
|
||||
return md5(md5(password) + salt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateSalt() {
|
||||
return RandomStringUtils.generateHex(saltLength);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package fr.xephi.authme.security.crypts;
|
||||
|
||||
import fr.xephi.authme.security.HashUtils;
|
||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||
import fr.xephi.authme.security.crypts.description.Usage;
|
||||
import fr.xephi.authme.util.RandomStringUtils;
|
||||
|
||||
@Recommendation(Usage.RECOMMENDED)
|
||||
public class SaltedSha512 extends SeparateSaltMethod {
|
||||
|
||||
@Override
|
||||
public String computeHash(String password, String salt, String name) {
|
||||
return HashUtils.sha512(password + salt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateSalt() {
|
||||
return RandomStringUtils.generateHex(32);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package fr.xephi.authme.security.crypts;
|
||||
|
||||
import static fr.xephi.authme.security.HashUtils.isEqual;
|
||||
|
||||
/**
|
||||
* Common supertype for encryption methods which store their salt separately from the hash.
|
||||
*/
|
||||
public abstract class SeparateSaltMethod implements EncryptionMethod {
|
||||
|
||||
@Override
|
||||
public abstract String computeHash(String password, String salt, String name);
|
||||
|
||||
@Override
|
||||
public HashedPassword computeHash(String password, String name) {
|
||||
String salt = generateSalt();
|
||||
return new HashedPassword(computeHash(password, salt, name), salt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract String generateSalt();
|
||||
|
||||
@Override
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
|
||||
return isEqual(hashedPassword.getHash(), computeHash(password, hashedPassword.getSalt(), null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSeparateSalt() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package fr.xephi.authme.security.crypts;
|
||||
|
||||
import fr.xephi.authme.security.HashUtils;
|
||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||
import fr.xephi.authme.security.crypts.description.Usage;
|
||||
|
||||
@Deprecated
|
||||
@Recommendation(Usage.DEPRECATED)
|
||||
public class Sha1 extends UnsaltedMethod {
|
||||
|
||||
@Override
|
||||
public String computeHash(String password) {
|
||||
return HashUtils.sha1(password);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package fr.xephi.authme.security.crypts;
|
||||
|
||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||
import fr.xephi.authme.security.crypts.description.Usage;
|
||||
|
||||
import static fr.xephi.authme.security.HashUtils.isEqual;
|
||||
import static fr.xephi.authme.security.HashUtils.sha256;
|
||||
|
||||
@Recommendation(Usage.RECOMMENDED)
|
||||
public class Sha256 extends HexSaltedMethod {
|
||||
|
||||
@Override
|
||||
public String computeHash(String password, String salt, String name) {
|
||||
return "$SHA$" + salt + "$" + sha256(sha256(password) + salt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
|
||||
String hash = hashedPassword.getHash();
|
||||
String[] line = hash.split("\\$");
|
||||
return line.length == 4 && isEqual(hash, computeHash(password, line[2], name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSaltLength() {
|
||||
return 16;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package fr.xephi.authme.security.crypts;
|
||||
|
||||
import fr.xephi.authme.security.HashUtils;
|
||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||
import fr.xephi.authme.security.crypts.description.Usage;
|
||||
|
||||
@Deprecated
|
||||
@Recommendation(Usage.DEPRECATED)
|
||||
public class Sha512 extends UnsaltedMethod {
|
||||
|
||||
@Override
|
||||
public String computeHash(String password) {
|
||||
return HashUtils.sha512(password);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package fr.xephi.authme.security.crypts;
|
||||
|
||||
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;
|
||||
import fr.xephi.authme.util.RandomStringUtils;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import static fr.xephi.authme.security.HashUtils.isEqual;
|
||||
|
||||
/**
|
||||
* Hashing algorithm for SMF forums.
|
||||
* <p>
|
||||
* The hash algorithm is {@code sha1(strtolower($username) . $password)}. However, an additional four-character
|
||||
* salt is generated for each user, used to generate the login cookie. Therefore, this implementation generates a salt
|
||||
* and declares that it requires a separate salt (the SMF members table has a not-null constraint on the salt column).
|
||||
*
|
||||
* @see <a href="https://www.simplemachines.org/">Simple Machines Forum</a>
|
||||
*/
|
||||
@Recommendation(Usage.DO_NOT_USE)
|
||||
@HasSalt(SaltType.USERNAME)
|
||||
public class Smf implements EncryptionMethod {
|
||||
|
||||
@Override
|
||||
public HashedPassword computeHash(String password, String name) {
|
||||
return new HashedPassword(computeHash(password, null, name), generateSalt());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String computeHash(String password, String salt, String name) {
|
||||
return HashUtils.sha1(name.toLowerCase(Locale.ROOT) + password);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
|
||||
return isEqual(hashedPassword.getHash(), computeHash(password, null, name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateSalt() {
|
||||
return RandomStringUtils.generate(4);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSeparateSalt() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package fr.xephi.authme.security.crypts;
|
||||
|
||||
import com.google.common.escape.Escaper;
|
||||
import com.google.common.io.BaseEncoding;
|
||||
import com.google.common.net.UrlEscapers;
|
||||
import com.google.common.primitives.Ints;
|
||||
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 javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Two factor authentication.
|
||||
*
|
||||
* @see <a href="http://thegreyblog.blogspot.com/2011/12/google-authenticator-using-it-in-your.html">Original source</a>
|
||||
*/
|
||||
@Recommendation(Usage.DOES_NOT_WORK)
|
||||
@HasSalt(SaltType.NONE)
|
||||
public class TwoFactor extends UnsaltedMethod {
|
||||
|
||||
private static final int SCRET_BYTE = 10;
|
||||
private static final int SCRATCH_CODES = 5;
|
||||
private static final int BYTES_PER_SCRATCH_CODE = 4;
|
||||
|
||||
private static final int TIME_PRECISION = 3;
|
||||
private static final String CRYPTO_ALGO = "HmacSHA1";
|
||||
|
||||
private final ConsoleLogger logger = ConsoleLoggerFactory.get(TwoFactor.class);
|
||||
|
||||
/**
|
||||
* Creates a link to a QR barcode with the provided secret.
|
||||
*
|
||||
* @param user the player's name
|
||||
* @param host the server host
|
||||
* @param secret the TOTP secret
|
||||
* @return URL leading to a QR code
|
||||
*/
|
||||
public static String getQrBarcodeUrl(String user, String host, String secret) {
|
||||
String format = "https://www.google.com/chart?chs=130x130&chld=M%%7C0&cht=qr&chl="
|
||||
+ "otpauth://totp/"
|
||||
+ "%s@%s%%3Fsecret%%3D%s";
|
||||
Escaper urlEscaper = UrlEscapers.urlFragmentEscaper();
|
||||
return String.format(format, urlEscaper.escape(user), urlEscaper.escape(host), secret);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String computeHash(String password) {
|
||||
// Allocating the buffer
|
||||
byte[] buffer = new byte[SCRET_BYTE + SCRATCH_CODES * BYTES_PER_SCRATCH_CODE];
|
||||
|
||||
// Filling the buffer with random numbers.
|
||||
// Notice: you want to reuse the same random generator
|
||||
// while generating larger random number sequences.
|
||||
new SecureRandom().nextBytes(buffer);
|
||||
|
||||
// Getting the key and converting it to Base32
|
||||
byte[] secretKey = Arrays.copyOf(buffer, SCRET_BYTE);
|
||||
return BaseEncoding.base32().encode(secretKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
|
||||
try {
|
||||
return checkPassword(hashedPassword.getHash(), password);
|
||||
} catch (Exception e) {
|
||||
logger.logException("Failed to verify two auth code:", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkPassword(String secretKey, String userInput)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
Integer code = Ints.tryParse(userInput);
|
||||
if (code == null) {
|
||||
//code is not an integer
|
||||
return false;
|
||||
}
|
||||
|
||||
long currentTime = Calendar.getInstance().getTimeInMillis() / TimeUnit.SECONDS.toMillis(30);
|
||||
return checkCode(secretKey, code, currentTime);
|
||||
}
|
||||
|
||||
private boolean checkCode(String secret, long code, long t) throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
byte[] decodedKey = BaseEncoding.base32().decode(secret);
|
||||
|
||||
// Window is used to check codes generated in the near past.
|
||||
// You can use this value to tune how far you're willing to go.
|
||||
int window = TIME_PRECISION;
|
||||
for (int i = -window; i <= window; ++i) {
|
||||
long hash = verifyCode(decodedKey, t + i);
|
||||
|
||||
if (hash == code) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// The validation code is invalid.
|
||||
return false;
|
||||
}
|
||||
|
||||
private int verifyCode(byte[] key, long t) throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
byte[] data = new byte[8];
|
||||
long value = t;
|
||||
for (int i = 8; i-- > 0; value >>>= 8) {
|
||||
data[i] = (byte) value;
|
||||
}
|
||||
|
||||
SecretKeySpec signKey = new SecretKeySpec(key, CRYPTO_ALGO);
|
||||
Mac mac = Mac.getInstance(CRYPTO_ALGO);
|
||||
mac.init(signKey);
|
||||
byte[] hash = mac.doFinal(data);
|
||||
|
||||
int offset = hash[20 - 1] & 0xF;
|
||||
|
||||
// We're using a long because Java hasn't got unsigned int.
|
||||
long truncatedHash = 0;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
truncatedHash <<= 8;
|
||||
// We are dealing with signed bytes:
|
||||
// we just keep the first byte.
|
||||
truncatedHash |= (hash[offset + i] & 0xFF);
|
||||
}
|
||||
|
||||
truncatedHash &= 0x7FFF_FFFF;
|
||||
truncatedHash %= 1_000_000;
|
||||
|
||||
return (int) truncatedHash;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
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;
|
||||
|
||||
import static fr.xephi.authme.security.HashUtils.isEqual;
|
||||
|
||||
/**
|
||||
* 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 EncryptionMethod {
|
||||
|
||||
public abstract String computeHash(String password);
|
||||
|
||||
@Override
|
||||
public HashedPassword computeHash(String password, String name) {
|
||||
return new HashedPassword(computeHash(password));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String computeHash(String password, String salt, String name) {
|
||||
return computeHash(password);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
|
||||
return isEqual(hashedPassword.getHash(), computeHash(password));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateSalt() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSeparateSalt() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
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;
|
||||
|
||||
import static fr.xephi.authme.security.HashUtils.isEqual;
|
||||
|
||||
/**
|
||||
* Common supertype of encryption methods that use a player's username
|
||||
* (or something based on it) as embedded salt.
|
||||
*/
|
||||
@Recommendation(Usage.DO_NOT_USE)
|
||||
@HasSalt(SaltType.USERNAME)
|
||||
public abstract class UsernameSaltMethod implements EncryptionMethod {
|
||||
|
||||
@Override
|
||||
public abstract HashedPassword computeHash(String password, String name);
|
||||
|
||||
@Override
|
||||
public String computeHash(String password, String salt, String name) {
|
||||
return computeHash(password, name).getHash();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
|
||||
return isEqual(hashedPassword.getHash(), computeHash(password, name).getHash());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateSalt() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSeparateSalt() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
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;
|
||||
import fr.xephi.authme.util.RandomStringUtils;
|
||||
|
||||
import static fr.xephi.authme.security.HashUtils.sha1;
|
||||
|
||||
@Recommendation(Usage.ACCEPTABLE)
|
||||
@HasSalt(value = SaltType.TEXT, length = 40)
|
||||
public class Wbb3 extends SeparateSaltMethod {
|
||||
|
||||
@Override
|
||||
public String computeHash(String password, String salt, String name) {
|
||||
return sha1(salt.concat(sha1(salt.concat(sha1(password)))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateSalt() {
|
||||
return RandomStringUtils.generateHex(40);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
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;
|
||||
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.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 SecureRandom random = new SecureRandom();
|
||||
|
||||
@Override
|
||||
public HashedPassword computeHash(String password, String name) {
|
||||
byte[] salt = new byte[BYTES_IN_SALT];
|
||||
random.nextBytes(salt);
|
||||
|
||||
String hash = hashInternal(password, salt);
|
||||
return new HashedPassword(hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String computeHash(String password, String salt, String name) {
|
||||
return hashInternal(password, salt.getBytes(UTF_8));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
|
||||
try {
|
||||
BCrypt.HashData hashData = BCrypt.Version.VERSION_2A.parser.parse(hashedPassword.getHash().getBytes(UTF_8));
|
||||
byte[] salt = hashData.rawSalt;
|
||||
String computedHash = hashInternal(password, salt);
|
||||
return isEqual(hashedPassword.getHash(), computedHash);
|
||||
} catch (IllegalBCryptFormatException | IllegalArgumentException e) {
|
||||
logger.logException("Invalid WBB4 hash:", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hashes the given password with the provided salt twice: hash(hash(password, salt), salt).
|
||||
*
|
||||
* @param password the password to hash
|
||||
* @param rawSalt the salt to use
|
||||
* @return WBB4-compatible hash
|
||||
*/
|
||||
private String hashInternal(String password, byte[] rawSalt) {
|
||||
return bCryptHasher.hashWithRawSalt(bCryptHasher.hashWithRawSalt(password, rawSalt), rawSalt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateSalt() {
|
||||
return BCryptHasher.generateSalt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSeparateSalt() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,399 @@
|
||||
package fr.xephi.authme.security.crypts;
|
||||
|
||||
/**
|
||||
* The Whirlpool hashing function.
|
||||
* <p/>
|
||||
* <p/>
|
||||
* <b>References</b>
|
||||
* <p/>
|
||||
* <p/>
|
||||
* The Whirlpool algorithm was developed by <a
|
||||
* href="mailto:pbarreto@scopus.com.br">Paulo S. L. M. Barreto</a> and <a
|
||||
* href="mailto:vincent.rijmen@cryptomathic.com">Vincent Rijmen</a>.
|
||||
* <p/>
|
||||
* See P.S.L.M. Barreto, V. Rijmen, ``The Whirlpool hashing function,'' First
|
||||
* NESSIE workshop, 2000 (tweaked version, 2003),
|
||||
* <https://www.cosic.esat.kuleuven
|
||||
* .ac.be/nessie/workshop/submissions/whirlpool.zip>
|
||||
*
|
||||
* @author Paulo S.L.M. Barreto
|
||||
* @author Vincent Rijmen.
|
||||
* @version 3.0 (2003.03.12)
|
||||
* <p/>
|
||||
* ====================================================================
|
||||
* =========
|
||||
* <p/>
|
||||
* Differences from version 2.1:
|
||||
* <p/>
|
||||
* - Suboptimal diffusion matrix replaced by cir(1, 1, 4, 1, 8, 5, 2,
|
||||
* 9).
|
||||
* <p/>
|
||||
* ====================================================================
|
||||
* =========
|
||||
* <p/>
|
||||
* Differences from version 2.0:
|
||||
* <p/>
|
||||
* - Generation of ISO/IEC 10118-3 test vectors. - Bug fix: nonzero
|
||||
* carry was ignored when tallying the data length (this bug apparently
|
||||
* only manifested itself when feeding data in pieces rather than in a
|
||||
* single chunk at once).
|
||||
* <p/>
|
||||
* Differences from version 1.0:
|
||||
* <p/>
|
||||
* - Original S-box replaced by the tweaked, hardware-efficient
|
||||
* version.
|
||||
* <p/>
|
||||
* ====================================================================
|
||||
* =========
|
||||
* <p/>
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS
|
||||
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||
import fr.xephi.authme.security.crypts.description.Usage;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@Deprecated
|
||||
@Recommendation(Usage.DEPRECATED)
|
||||
public class Whirlpool extends UnsaltedMethod {
|
||||
|
||||
/**
|
||||
* The message digest size (in bits)
|
||||
*/
|
||||
public static final int DIGESTBITS = 512;
|
||||
|
||||
/**
|
||||
* The message digest size (in bytes)
|
||||
*/
|
||||
public static final int DIGESTBYTES = DIGESTBITS >>> 3;
|
||||
|
||||
/**
|
||||
* The number of rounds of the internal dedicated block cipher.
|
||||
*/
|
||||
protected static final int R = 10;
|
||||
|
||||
/**
|
||||
* The substitution box.
|
||||
*/
|
||||
private static final String sbox = "\u1823\uc6E8\u87B8\u014F\u36A6\ud2F5\u796F\u9152" + "\u60Bc\u9B8E\uA30c\u7B35\u1dE0\ud7c2\u2E4B\uFE57" + "\u1577\u37E5\u9FF0\u4AdA\u58c9\u290A\uB1A0\u6B85" + "\uBd5d\u10F4\ucB3E\u0567\uE427\u418B\uA77d\u95d8" + "\uFBEE\u7c66\udd17\u479E\ucA2d\uBF07\uAd5A\u8333" + "\u6302\uAA71\uc819\u49d9\uF2E3\u5B88\u9A26\u32B0" + "\uE90F\ud580\uBEcd\u3448\uFF7A\u905F\u2068\u1AAE" + "\uB454\u9322\u64F1\u7312\u4008\uc3Ec\udBA1\u8d3d" + "\u9700\ucF2B\u7682\ud61B\uB5AF\u6A50\u45F3\u30EF" + "\u3F55\uA2EA\u65BA\u2Fc0\udE1c\uFd4d\u9275\u068A" + "\uB2E6\u0E1F\u62d4\uA896\uF9c5\u2559\u8472\u394c" + "\u5E78\u388c\ud1A5\uE261\uB321\u9c1E\u43c7\uFc04" + "\u5199\u6d0d\uFAdF\u7E24\u3BAB\ucE11\u8F4E\uB7EB" + "\u3c81\u94F7\uB913\u2cd3\uE76E\uc403\u5644\u7FA9" + "\u2ABB\uc153\udc0B\u9d6c\u3174\uF646\uAc89\u14E1" + "\u163A\u6909\u70B6\ud0Ed\ucc42\u98A4\u285c\uF886";
|
||||
|
||||
private static final long[][] C = new long[8][256];
|
||||
private static final long[] rc = new long[R + 1];
|
||||
|
||||
static {
|
||||
for (int x = 0; x < 256; x++) {
|
||||
char c = sbox.charAt(x / 2);
|
||||
long v1 = ((x & 1) == 0) ? c >>> 8 : c & 0xff;
|
||||
long v2 = v1 << 1;
|
||||
if (v2 >= 0x100L) {
|
||||
v2 ^= 0x11dL;
|
||||
}
|
||||
long v4 = v2 << 1;
|
||||
if (v4 >= 0x100L) {
|
||||
v4 ^= 0x11dL;
|
||||
}
|
||||
long v5 = v4 ^ v1;
|
||||
long v8 = v4 << 1;
|
||||
if (v8 >= 0x100L) {
|
||||
v8 ^= 0x11dL;
|
||||
}
|
||||
long v9 = v8 ^ v1;
|
||||
/*
|
||||
* build the circulant table C[0][x] = S[x].[1, 1, 4, 1, 8, 5, 2,
|
||||
* 9]:
|
||||
*/
|
||||
C[0][x] = (v1 << 56) | (v1 << 48) | (v4 << 40) | (v1 << 32) | (v8 << 24) | (v5 << 16) | (v2 << 8) | (v9);
|
||||
/*
|
||||
* build the remaining circulant tables C[t][x] = C[0][x] rotr t
|
||||
*/
|
||||
for (int t = 1; t < 8; t++) {
|
||||
C[t][x] = (C[t - 1][x] >>> 8) | ((C[t - 1][x] << 56));
|
||||
}
|
||||
}
|
||||
/*
|
||||
* build the round constants:
|
||||
*/
|
||||
rc[0] = 0L; /*
|
||||
* not used (assigment kept only to properly initialize all
|
||||
* variables)
|
||||
*/
|
||||
for (int r = 1; r <= R; r++) {
|
||||
int i = 8 * (r - 1);
|
||||
rc[r] = (C[0][i] & 0xff00000000000000L) ^ (C[1][i + 1] & 0x00ff000000000000L) ^ (C[2][i + 2] & 0x0000ff0000000000L) ^ (C[3][i + 3] & 0x000000ff00000000L) ^ (C[4][i + 4] & 0x00000000ff000000L) ^ (C[5][i + 5] & 0x0000000000ff0000L) ^ (C[6][i + 6] & 0x000000000000ff00L) ^ (C[7][i + 7] & 0x00000000000000ffL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Global number of hashed bits (256-bit counter).
|
||||
*/
|
||||
protected final byte[] bitLength = new byte[32];
|
||||
|
||||
/**
|
||||
* Buffer of data to hash.
|
||||
*/
|
||||
protected final byte[] buffer = new byte[64];
|
||||
|
||||
/**
|
||||
* Current number of bits on the buffer.
|
||||
*/
|
||||
protected int bufferBits = 0;
|
||||
|
||||
/**
|
||||
* Current (possibly incomplete) byte slot on the buffer.
|
||||
*/
|
||||
protected int bufferPos = 0;
|
||||
|
||||
/**
|
||||
* The hashing state.
|
||||
*/
|
||||
protected final long[] hash = new long[8];
|
||||
protected final long[] K = new long[8];
|
||||
protected final long[] L = new long[8];
|
||||
protected final long[] block = new long[8];
|
||||
protected final long[] state = new long[8];
|
||||
|
||||
public Whirlpool() {
|
||||
}
|
||||
|
||||
protected static String display(byte[] array) {
|
||||
char[] val = new char[2 * array.length];
|
||||
String hex = "0123456789ABCDEF";
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
int b = array[i] & 0xff;
|
||||
val[2 * i] = hex.charAt(b >>> 4);
|
||||
val[2 * i + 1] = hex.charAt(b & 15);
|
||||
}
|
||||
return String.valueOf(val);
|
||||
}
|
||||
|
||||
/**
|
||||
* The core Whirlpool transform.
|
||||
*/
|
||||
protected void processBuffer() {
|
||||
/*
|
||||
* map the buffer to a block:
|
||||
*/
|
||||
for (int i = 0, j = 0; i < 8; i++, j += 8) {
|
||||
block[i] = (((long) buffer[j]) << 56) ^ (((long) buffer[j + 1] & 0xffL) << 48) ^ (((long) buffer[j + 2] & 0xffL) << 40) ^ (((long) buffer[j + 3] & 0xffL) << 32) ^ (((long) buffer[j + 4] & 0xffL) << 24) ^ (((long) buffer[j + 5] & 0xffL) << 16) ^ (((long) buffer[j + 6] & 0xffL) << 8) ^ (((long) buffer[j + 7] & 0xffL));
|
||||
}
|
||||
/*
|
||||
* compute and apply K^0 to the cipher state:
|
||||
*/
|
||||
for (int i = 0; i < 8; i++) {
|
||||
state[i] = block[i] ^ (K[i] = hash[i]);
|
||||
}
|
||||
/*
|
||||
* iterate over all rounds:
|
||||
*/
|
||||
for (int r = 1; r <= R; r++) {
|
||||
/*
|
||||
* compute K^r from K^{r-1}:
|
||||
*/
|
||||
for (int i = 0; i < 8; i++) {
|
||||
L[i] = 0L;
|
||||
for (int t = 0, s = 56; t < 8; t++, s -= 8) {
|
||||
L[i] ^= C[t][(int) (K[(i - t) & 7] >>> s) & 0xff];
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < 8; i++) {
|
||||
K[i] = L[i];
|
||||
}
|
||||
K[0] ^= rc[r];
|
||||
/*
|
||||
* apply the r-th round transformation:
|
||||
*/
|
||||
for (int i = 0; i < 8; i++) {
|
||||
L[i] = K[i];
|
||||
for (int t = 0, s = 56; t < 8; t++, s -= 8) {
|
||||
L[i] ^= C[t][(int) (state[(i - t) & 7] >>> s) & 0xff];
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < 8; i++) {
|
||||
state[i] = L[i];
|
||||
}
|
||||
}
|
||||
/*
|
||||
* apply the Miyaguchi-Preneel compression function:
|
||||
*/
|
||||
for (int i = 0; i < 8; i++) {
|
||||
hash[i] ^= state[i] ^ block[i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the hashing state.
|
||||
*/
|
||||
public void NESSIEinit() {
|
||||
Arrays.fill(bitLength, (byte) 0);
|
||||
bufferBits = bufferPos = 0;
|
||||
buffer[0] = 0;
|
||||
Arrays.fill(hash, 0L);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delivers input data to the hashing algorithm.
|
||||
*
|
||||
* @param source plaintext data to hash.
|
||||
* @param sourceBits how many bits of plaintext to process.
|
||||
* <p>
|
||||
* This method maintains the invariant: bufferBits < 512
|
||||
* </p>
|
||||
*/
|
||||
public void NESSIEadd(byte[] source, long sourceBits) {
|
||||
/*
|
||||
* sourcePos | +-------+-------+------- ||||||||||||||||||||| source
|
||||
* +-------+-------+-------
|
||||
* +-------+-------+-------+-------+-------+-------
|
||||
* |||||||||||||||||||||| buffer
|
||||
* +-------+-------+-------+-------+-------+------- | bufferPos
|
||||
*/
|
||||
int sourcePos = 0; // index of leftmost source byte containing data (1
|
||||
// to 8 bits).
|
||||
int sourceGap = (8 - ((int) sourceBits & 7)) & 7; // space on
|
||||
// source[sourcePos].
|
||||
int bufferRem = bufferBits & 7; // occupied bits on buffer[bufferPos].
|
||||
int b;
|
||||
// tally the length of the added data:
|
||||
long value = sourceBits;
|
||||
for (int i = 31, carry = 0; i >= 0; i--) {
|
||||
carry += (bitLength[i] & 0xff) + ((int) value & 0xff);
|
||||
bitLength[i] = (byte) carry;
|
||||
carry >>>= 8;
|
||||
value >>>= 8;
|
||||
}
|
||||
// process data in chunks of 8 bits:
|
||||
while (sourceBits > 8) { // at least source[sourcePos] and
|
||||
// source[sourcePos+1] contain data.
|
||||
// take a byte from the source:
|
||||
b = ((source[sourcePos] << sourceGap) & 0xff) | ((source[sourcePos + 1] & 0xff) >>> (8 - sourceGap));
|
||||
if (b < 0 || b >= 256) {
|
||||
throw new RuntimeException("LOGIC ERROR");
|
||||
}
|
||||
// process this byte:
|
||||
buffer[bufferPos++] |= b >>> bufferRem;
|
||||
bufferBits += 8 - bufferRem; // bufferBits = 8*bufferPos;
|
||||
if (bufferBits == 512) {
|
||||
// process data block:
|
||||
processBuffer();
|
||||
// reset buffer:
|
||||
bufferBits = bufferPos = 0;
|
||||
}
|
||||
buffer[bufferPos] = (byte) ((b << (8 - bufferRem)) & 0xff);
|
||||
bufferBits += bufferRem;
|
||||
// proceed to remaining data:
|
||||
sourceBits -= 8;
|
||||
sourcePos++;
|
||||
}
|
||||
// now 0 <= sourceBits <= 8;
|
||||
// furthermore, all data (if any is left) is in source[sourcePos].
|
||||
if (sourceBits > 0) {
|
||||
b = (source[sourcePos] << sourceGap) & 0xff; // bits are
|
||||
// left-justified on b.
|
||||
// process the remaining bits:
|
||||
buffer[bufferPos] |= b >>> bufferRem;
|
||||
} else {
|
||||
b = 0;
|
||||
}
|
||||
if (bufferRem + sourceBits < 8) {
|
||||
// all remaining data fits on buffer[bufferPos], and there still
|
||||
// remains some space.
|
||||
bufferBits += sourceBits;
|
||||
} else {
|
||||
// buffer[bufferPos] is full:
|
||||
bufferPos++;
|
||||
bufferBits += 8 - bufferRem; // bufferBits = 8*bufferPos;
|
||||
sourceBits -= 8 - bufferRem;
|
||||
// now 0 <= sourceBits < 8; furthermore, all data is in
|
||||
// source[sourcePos].
|
||||
if (bufferBits == 512) {
|
||||
// process data block:
|
||||
processBuffer();
|
||||
// reset buffer:
|
||||
bufferBits = bufferPos = 0;
|
||||
}
|
||||
buffer[bufferPos] = (byte) ((b << (8 - bufferRem)) & 0xff);
|
||||
bufferBits += (int) sourceBits;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Get the hash value from the hashing state.
|
||||
* </p>
|
||||
* <p>
|
||||
* This method uses the invariant: bufferBits < 512
|
||||
* </p>
|
||||
* @param digest byte[]
|
||||
*/
|
||||
public void NESSIEfinalize(byte[] digest) {
|
||||
// append a '1'-bit:
|
||||
buffer[bufferPos] |= 0x80 >>> (bufferBits & 7);
|
||||
bufferPos++; // all remaining bits on the current byte are set to zero.
|
||||
// pad with zero bits to complete 512N + 256 bits:
|
||||
if (bufferPos > 32) {
|
||||
while (bufferPos < 64) {
|
||||
buffer[bufferPos++] = 0;
|
||||
}
|
||||
// process data block:
|
||||
processBuffer();
|
||||
// reset buffer:
|
||||
bufferPos = 0;
|
||||
}
|
||||
while (bufferPos < 32) {
|
||||
buffer[bufferPos++] = 0;
|
||||
}
|
||||
// append bit length of hashed data:
|
||||
System.arraycopy(bitLength, 0, buffer, 32, 32);
|
||||
// process data block:
|
||||
processBuffer();
|
||||
// return the completed message digest:
|
||||
for (int i = 0, j = 0; i < 8; i++, j += 8) {
|
||||
long h = hash[i];
|
||||
digest[j] = (byte) (h >>> 56);
|
||||
digest[j + 1] = (byte) (h >>> 48);
|
||||
digest[j + 2] = (byte) (h >>> 40);
|
||||
digest[j + 3] = (byte) (h >>> 32);
|
||||
digest[j + 4] = (byte) (h >>> 24);
|
||||
digest[j + 5] = (byte) (h >>> 16);
|
||||
digest[j + 6] = (byte) (h >>> 8);
|
||||
digest[j + 7] = (byte) (h);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delivers string input data to the hashing algorithm.
|
||||
*
|
||||
* @param source plaintext data to hash (ASCII text string).
|
||||
* This method maintains the invariant: bufferBits < 512
|
||||
*/
|
||||
public void NESSIEadd(String source) {
|
||||
if (source.length() > 0) {
|
||||
byte[] data = new byte[source.length()];
|
||||
for (int i = 0; i < source.length(); i++) {
|
||||
data[i] = (byte) source.charAt(i);
|
||||
}
|
||||
NESSIEadd(data, 8 * data.length);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String computeHash(String password) {
|
||||
byte[] digest = new byte[DIGESTBYTES];
|
||||
NESSIEinit();
|
||||
NESSIEadd(password);
|
||||
NESSIEfinalize(digest);
|
||||
return display(digest);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package fr.xephi.authme.security.crypts;
|
||||
|
||||
import fr.xephi.authme.security.HashUtils;
|
||||
import fr.xephi.authme.security.MessageDigestAlgorithm;
|
||||
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.io.UnsupportedEncodingException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static fr.xephi.authme.security.HashUtils.isEqual;
|
||||
|
||||
@Recommendation(Usage.ACCEPTABLE)
|
||||
@HasSalt(value = SaltType.TEXT, length = 9)
|
||||
// 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 final SecureRandom randomGen = new SecureRandom();
|
||||
|
||||
private String encode64(byte[] src, int count) {
|
||||
int i, value;
|
||||
StringBuilder output = new StringBuilder();
|
||||
i = 0;
|
||||
|
||||
if (src.length < count) {
|
||||
byte[] t = new byte[count];
|
||||
System.arraycopy(src, 0, t, 0, src.length);
|
||||
Arrays.fill(t, src.length, count - 1, (byte) 0);
|
||||
src = t;
|
||||
}
|
||||
|
||||
do {
|
||||
value = src[i] + (src[i] < 0 ? 256 : 0);
|
||||
++i;
|
||||
output.append(itoa64.charAt(value & 63));
|
||||
if (i < count) {
|
||||
value |= (src[i] + (src[i] < 0 ? 256 : 0)) << 8;
|
||||
}
|
||||
output.append(itoa64.charAt((value >> 6) & 63));
|
||||
if (i++ >= count) {
|
||||
break;
|
||||
}
|
||||
if (i < count) {
|
||||
value |= (src[i] + (src[i] < 0 ? 256 : 0)) << 16;
|
||||
}
|
||||
output.append(itoa64.charAt((value >> 12) & 63));
|
||||
if (i++ >= count) {
|
||||
break;
|
||||
}
|
||||
output.append(itoa64.charAt((value >> 18) & 63));
|
||||
} while (i < count);
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
private String crypt(String password, String setting) {
|
||||
String output = "*0";
|
||||
if (((setting.length() < 2) ? setting : setting.substring(0, 2)).equalsIgnoreCase(output)) {
|
||||
output = "*1";
|
||||
}
|
||||
String id = (setting.length() < 3) ? setting : setting.substring(0, 3);
|
||||
if (!(id.equals("$P$") || id.equals("$H$"))) {
|
||||
return output;
|
||||
}
|
||||
int countLog2 = itoa64.indexOf(setting.charAt(3));
|
||||
if (countLog2 < 7 || countLog2 > 30) {
|
||||
return output;
|
||||
}
|
||||
int count = 1 << countLog2;
|
||||
String salt = setting.substring(4, 4 + 8);
|
||||
if (salt.length() != 8) {
|
||||
return output;
|
||||
}
|
||||
MessageDigest md = HashUtils.getDigest(MessageDigestAlgorithm.MD5);
|
||||
byte[] pass = stringToUtf8(password);
|
||||
byte[] hash = md.digest(stringToUtf8(salt + password));
|
||||
do {
|
||||
byte[] t = new byte[hash.length + pass.length];
|
||||
System.arraycopy(hash, 0, t, 0, hash.length);
|
||||
System.arraycopy(pass, 0, t, hash.length, pass.length);
|
||||
hash = md.digest(t);
|
||||
} while (--count > 0);
|
||||
output = setting.substring(0, 12);
|
||||
output += encode64(hash, 16);
|
||||
return output;
|
||||
}
|
||||
|
||||
private String gensaltPrivate(byte[] input) {
|
||||
String output = "$P$";
|
||||
int iterationCountLog2 = 8;
|
||||
output += itoa64.charAt(Math.min(iterationCountLog2 + 5, 30));
|
||||
output += encode64(input, 6);
|
||||
return output;
|
||||
}
|
||||
|
||||
private byte[] stringToUtf8(String string) {
|
||||
try {
|
||||
return string.getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new UnsupportedOperationException("This system doesn't support UTF-8!", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String computeHash(String password) {
|
||||
byte random[] = new byte[6];
|
||||
randomGen.nextBytes(random);
|
||||
return crypt(password, gensaltPrivate(stringToUtf8(new String(random))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
|
||||
String hash = hashedPassword.getHash();
|
||||
String comparedHash = crypt(password, hash);
|
||||
return isEqual(hash, comparedHash);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package fr.xephi.authme.security.crypts;
|
||||
|
||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||
import fr.xephi.authme.security.crypts.description.Usage;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import static fr.xephi.authme.security.HashUtils.isEqual;
|
||||
|
||||
@Recommendation(Usage.RECOMMENDED)
|
||||
public class XAuth extends HexSaltedMethod {
|
||||
|
||||
private static String getWhirlpool(String message) {
|
||||
Whirlpool w = new Whirlpool();
|
||||
byte[] digest = new byte[Whirlpool.DIGESTBYTES];
|
||||
w.NESSIEinit();
|
||||
w.NESSIEadd(message);
|
||||
w.NESSIEfinalize(digest);
|
||||
return Whirlpool.display(digest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String computeHash(String password, String salt, String name) {
|
||||
String hash = getWhirlpool(salt + password).toLowerCase(Locale.ROOT);
|
||||
int saltPos = password.length() >= hash.length() ? hash.length() - 1 : password.length();
|
||||
return hash.substring(0, saltPos) + salt + hash.substring(saltPos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
|
||||
String hash = hashedPassword.getHash();
|
||||
int saltPos = password.length() >= hash.length() ? hash.length() - 1 : password.length();
|
||||
if (saltPos + 12 > hash.length()) {
|
||||
return false;
|
||||
}
|
||||
String salt = hash.substring(saltPos, saltPos + 12);
|
||||
return isEqual(hash, computeHash(password, salt, name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSaltLength() {
|
||||
return 12;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package fr.xephi.authme.security.crypts;
|
||||
|
||||
import at.favre.lib.crypto.bcrypt.BCrypt;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class XfBCrypt extends BCryptBasedHash {
|
||||
|
||||
public static final String SCHEME_CLASS = "XenForo_Authentication_Core12";
|
||||
private static final Pattern HASH_PATTERN = Pattern.compile("\"hash\";s.*\"(.*)?\"");
|
||||
|
||||
XfBCrypt() {
|
||||
super(new BCryptHasher(BCrypt.Version.VERSION_2A, 10));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the password hash from the given BLOB.
|
||||
*
|
||||
* @param blob the blob to process
|
||||
* @return the extracted hash
|
||||
*/
|
||||
public static String getHashFromBlob(byte[] blob) {
|
||||
String line = new String(blob);
|
||||
Matcher m = HASH_PATTERN.matcher(line);
|
||||
if (m.find()) {
|
||||
return m.group(1);
|
||||
}
|
||||
return "*"; // what?
|
||||
}
|
||||
|
||||
public static String serializeHash(String hash) {
|
||||
return "a:1:{s:4:\"hash\";s:" + hash.length() + ":\""+hash+"\";}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package fr.xephi.authme.security.crypts.description;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Denotes a hashing algorithm that is restricted to the ASCII charset.
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface AsciiRestricted {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package fr.xephi.authme.security.crypts.description;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Describes the type of salt the encryption algorithm uses. This is purely for documentation
|
||||
* purposes and is ignored by the code.
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface HasSalt {
|
||||
|
||||
/**
|
||||
* The type of the salt.
|
||||
*
|
||||
* @return The salt type
|
||||
*/
|
||||
SaltType value();
|
||||
|
||||
/**
|
||||
* For text salts, the length of the salt.
|
||||
*
|
||||
* @return The length of the salt the algorithm uses, or 0 if not defined or not applicable.
|
||||
*/
|
||||
int length() default 0;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package fr.xephi.authme.security.crypts.description;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation to mark a hash algorithm with the usage recommendation.
|
||||
*
|
||||
* @see Usage
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Recommendation {
|
||||
|
||||
/**
|
||||
* The recommendation for using the hash algorithm.
|
||||
*
|
||||
* @return The recommended usage
|
||||
*/
|
||||
Usage value();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package fr.xephi.authme.security.crypts.description;
|
||||
|
||||
/**
|
||||
* The type of salt used by an encryption algorithm.
|
||||
*/
|
||||
public enum SaltType {
|
||||
|
||||
/** Random, newly generated text. */
|
||||
TEXT,
|
||||
|
||||
/** Salt is based on the username, including variations and repetitions thereof. */
|
||||
USERNAME,
|
||||
|
||||
/** No salt. */
|
||||
NONE
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package fr.xephi.authme.security.crypts.description;
|
||||
|
||||
/**
|
||||
* Usage recommendation that can be provided for a hash algorithm.
|
||||
* <p>
|
||||
* Use the following rules of thumb:
|
||||
* <ul>
|
||||
* <li>Hashes using MD5 may be {@link #ACCEPTABLE} but never {@link #RECOMMENDED}.</li>
|
||||
* <li>Hashes using no salt or one based on the username should be {@link #DO_NOT_USE}.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public enum Usage {
|
||||
|
||||
/** The hash algorithm appears to be cryptographically secure and is one of the algorithms recommended by AuthMe. */
|
||||
RECOMMENDED,
|
||||
|
||||
/** There are safer algorithms that can be chosen but using the algorithm is generally OK. */
|
||||
ACCEPTABLE,
|
||||
|
||||
/** Hash algorithm is not recommended to be used. Use only if required by another system. */
|
||||
DO_NOT_USE,
|
||||
|
||||
/** Algorithm that is or will be no longer supported actively. */
|
||||
DEPRECATED,
|
||||
|
||||
/** The algorithm does not work properly; do not use. */
|
||||
DOES_NOT_WORK
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user