#358 Add future interface methods, remove exception throwing

- Create Utils class for a common implementation of md5/sha1
- Create "foolproof" way of getting the MessageDigest for md5 etc. (MessageDigestAlgorithm enum)
- Create description annotations to annotate algorithms with usage recommendation and salt type
This commit is contained in:
ljacqu
2015-12-26 23:59:32 +01:00
parent 2bb386c488
commit 90a0325194
23 changed files with 387 additions and 206 deletions
@@ -0,0 +1,53 @@
package fr.xephi.authme.security;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public final class HashUtils {
private static final SecureRandom RANDOM = new SecureRandom();
private HashUtils() {
}
public static String hash(String message, MessageDigestAlgorithm algorithm) {
MessageDigest md = getDigest(algorithm);
md.reset();
md.update(message.getBytes());
byte[] digest = md.digest();
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
}
public static String sha1(String message) {
return hash(message, MessageDigestAlgorithm.SHA1);
}
public static String md5(String message) {
return hash(message, MessageDigestAlgorithm.MD5);
}
// Only works for length up to 40!
public static String generateSalt(int length) {
byte[] msg = new byte[40];
RANDOM.nextBytes(msg);
MessageDigest sha1 = getDigest(MessageDigestAlgorithm.SHA1);
sha1.reset();
byte[] digest = sha1.digest(msg);
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)).substring(0, length);
}
public static MessageDigest getDigest(MessageDigestAlgorithm algorithm) {
try {
return MessageDigest.getInstance(algorithm.getKey());
} catch (NoSuchAlgorithmException e) {
throw new UnsupportedOperationException("Your system seems not to support the hash algorithm '"
+ algorithm.getKey() + "'");
}
}
}
@@ -0,0 +1,30 @@
package fr.xephi.authme.security;
import java.security.MessageDigest;
/**
* The Java-supported names to get a {@link MessageDigest} instance with.
*
* @see <a href="http://docs.oracle.com/javase/1.5.0/docs/guide/security/CryptoSpec.html#AppA">
* Crypto Spec Appendix A: Standard Names</a>
*/
public enum MessageDigestAlgorithm {
MD5("MD5"),
SHA1("SHA-1"),
SHA256("SHA-256"),
SHA512("SHA-512");
private final String key;
MessageDigestAlgorithm(String key) {
this.key = key;
}
public String getKey() {
return key;
}
}
@@ -13,8 +13,12 @@
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package fr.xephi.authme.security.crypts;
import fr.xephi.authme.security.crypts.description.HasSalt;
import fr.xephi.authme.security.crypts.description.Usage;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.SaltType;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
/**
@@ -59,11 +63,13 @@ import java.security.SecureRandom;
* @author Damien Miller
* @version 0.2
*/
@Recommendation(Usage.RECOMMENDED)
@HasSalt(value = SaltType.TEXT, length = BCRYPT.BCRYPT_SALT_LEN)
public class BCRYPT implements EncryptionMethod {
// BCrypt parameters
private static final int GENSALT_DEFAULT_LOG2_ROUNDS = 10;
private static final int BCRYPT_SALT_LEN = 16;
protected static final int BCRYPT_SALT_LEN = 16;
// Blowfish parameters
private static final int BLOWFISH_NUM_ROUNDS = 16;
@@ -508,14 +514,20 @@ public class BCRYPT implements EncryptionMethod {
}
@Override
public String computeHash(String password, String salt, String name)
throws NoSuchAlgorithmException {
public String computeHash(String password, String salt, String name) {
return hashpw(password, salt);
}
public String computeHash(String password, String name) {
return hashpw(password, generateSalt());
}
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
public boolean comparePassword(String hash, String password, String playerName) {
return checkpw(password, hash);
}
public String generateSalt() {
return BCRYPT.gensalt();
}
}
@@ -1,26 +1,22 @@
package fr.xephi.authme.security.crypts;
import java.security.NoSuchAlgorithmException;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.Usage;
/**
*/
@Recommendation(Usage.DOES_NOT_WORK)
public class BCRYPT2Y implements EncryptionMethod {
@Override
public String computeHash(String password, String salt, String name)
throws NoSuchAlgorithmException {
public String computeHash(String password, String salt, String name) {
if (salt.length() == 22)
salt = "$2y$10$" + salt;
return (BCRYPT.hashpw(password, salt));
}
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
public boolean comparePassword(String hash, String password, String playerName) {
String ok = hash.substring(0, 29);
if (ok.length() != 29)
return false;
return hash.equals(computeHash(password, ok, playerName));
return ok.length() == 29 && hash.equals(computeHash(password, ok, playerName));
}
}
@@ -1,18 +1,24 @@
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.Usage;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.SaltType;
import fr.xephi.authme.security.crypts.description.HasSalt;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
*/
@Recommendation(Usage.DO_NOT_USE)
@HasSalt(SaltType.USERNAME)
public class CRAZYCRYPT1 implements EncryptionMethod {
private static final char[] CRYPTCHARS = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
protected final Charset charset = Charset.forName("UTF-8");
private static final char[] CRYPTCHARS =
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
private final Charset charset = Charset.forName("UTF-8");
public static String byteArrayToHexString(final byte... args) {
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];
@@ -22,21 +28,23 @@ public class CRAZYCRYPT1 implements EncryptionMethod {
}
@Override
public String computeHash(String password, String salt, String name)
throws NoSuchAlgorithmException {
public String computeHash(String password, String salt, String name) {
return computeHash(password, name);
}
public String computeHash(String password, String name) {
final String text = "ÜÄaeut//&/=I " + password + "7421€547" + name + "__+IÄIH§%NK " + password;
try {
final MessageDigest md = MessageDigest.getInstance("SHA-512");
md.update(text.getBytes(charset), 0, text.length());
return byteArrayToHexString(md.digest());
} catch (final NoSuchAlgorithmException e) {
return null;
}
final MessageDigest md = HashUtils.getDigest(MessageDigestAlgorithm.SHA512);
md.update(text.getBytes(charset), 0, text.length());
return byteArrayToHexString(md.digest());
}
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
return hash.equals(computeHash(password, null, playerName));
public boolean comparePassword(String hash, String password, String playerName) {
return hash.equals(computeHash(password, playerName));
}
public String generateSalt() {
return null;
}
}
@@ -1,13 +1,14 @@
package fr.xephi.authme.security.crypts;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.Usage;
import fr.xephi.authme.security.pbkdf2.PBKDF2Engine;
import fr.xephi.authme.security.pbkdf2.PBKDF2Parameters;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
/**
*/
@Recommendation(Usage.DOES_NOT_WORK)
public class CryptPBKDF2 implements EncryptionMethod {
@Override
@@ -1,18 +1,21 @@
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.security.pbkdf2.PBKDF2Engine;
import fr.xephi.authme.security.pbkdf2.PBKDF2Parameters;
import javax.xml.bind.DatatypeConverter;
import java.security.NoSuchAlgorithmException;
/**
*/
@Recommendation(Usage.OK)
@HasSalt(value = SaltType.TEXT, length = 12)
public class CryptPBKDF2Django implements EncryptionMethod {
@Override
public String computeHash(String password, String salt, String name)
throws NoSuchAlgorithmException {
public String computeHash(String password, String salt, String name) {
String result = "pbkdf2_sha256$15000$" + salt + "$";
PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "ASCII", salt.getBytes(), 15000);
PBKDF2Engine engine = new PBKDF2Engine(params);
@@ -20,9 +23,12 @@ public class CryptPBKDF2Django implements EncryptionMethod {
return result + String.valueOf(DatatypeConverter.printBase64Binary(engine.deriveKey(password, 32)));
}
public String computeHash(String password, String name) {
return computeHash(password, generateSalt(), null);
}
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
public boolean comparePassword(String hash, String password, String playerName) {
String[] line = hash.split("\\$");
String salt = line[2];
byte[] derivedKey = DatatypeConverter.parseBase64Binary(line[3]);
@@ -31,4 +37,8 @@ public class CryptPBKDF2Django implements EncryptionMethod {
return engine.verifyKey(password);
}
public String generateSalt() {
return HashUtils.generateSalt(12);
}
}
@@ -1,32 +1,31 @@
package fr.xephi.authme.security.crypts;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import fr.xephi.authme.security.HashUtils;
import fr.xephi.authme.security.crypts.description.HasSalt;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.SaltType;
import fr.xephi.authme.security.crypts.description.Usage;
/**
*/
@Recommendation(Usage.DO_NOT_USE)
@HasSalt(SaltType.NONE)
public class DOUBLEMD5 implements EncryptionMethod {
private static String getMD5(String message)
throws NoSuchAlgorithmException {
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.reset();
md5.update(message.getBytes());
byte[] digest = md5.digest();
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
@Override
public String computeHash(String password, String salt, String name) {
return computeHash(password, null);
}
public String computeHash(String password, String name) {
return HashUtils.md5(HashUtils.md5(password));
}
public String generateSalt() {
return null;
}
@Override
public String computeHash(String password, String salt, String name)
throws NoSuchAlgorithmException {
return getMD5(getMD5(password));
}
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
return hash.equals(computeHash(password, "", ""));
public boolean comparePassword(String hash, String password, String playerName) {
return hash.equals(computeHash(password, null, null));
}
}
@@ -32,4 +32,8 @@ public interface EncryptionMethod {
boolean comparePassword(String hash, String password, String playerName)
throws NoSuchAlgorithmException;
// String generateSalt();
// String computeHash(String password, String name);
}
@@ -1,32 +1,35 @@
package fr.xephi.authme.security.crypts;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import fr.xephi.authme.security.HashUtils;
import fr.xephi.authme.security.crypts.description.HasSalt;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.SaltType;
import fr.xephi.authme.security.crypts.description.Usage;
/**
*/
@Recommendation(Usage.OK)
@HasSalt(value = SaltType.TEXT, length = 32)
public class JOOMLA implements EncryptionMethod {
private static String getMD5(String message)
throws NoSuchAlgorithmException {
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.reset();
md5.update(message.getBytes());
byte[] digest = md5.digest();
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
@Override
public String computeHash(String password, String salt, String name) {
return HashUtils.md5(password + salt) + ":" + salt;
}
public String computeHash(String password, String name) {
return computeHash(password, generateSalt(), null);
}
public String generateSalt() {
return HashUtils.generateSalt(32);
}
@Override
public String computeHash(String password, String salt, String name)
throws NoSuchAlgorithmException {
return getMD5(password + salt) + ":" + salt;
}
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
String salt = hash.split(":")[1];
return hash.equals(getMD5(password + salt) + ":" + salt);
public boolean comparePassword(String hash, String password, String playerName) {
String[] hashParts = hash.split(":");
if (hashParts.length != 2) {
return false;
}
String salt = hashParts[1];
return hash.equals(computeHash(password, salt, null));
}
}
@@ -1,31 +1,26 @@
package fr.xephi.authme.security.crypts;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import fr.xephi.authme.security.HashUtils;
import fr.xephi.authme.security.crypts.description.HasSalt;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.SaltType;
import fr.xephi.authme.security.crypts.description.Usage;
/**
*/
@Recommendation(Usage.DO_NOT_USE)
@HasSalt(SaltType.NONE)
public class MD5 implements EncryptionMethod {
private static String getMD5(String message)
throws NoSuchAlgorithmException {
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.reset();
md5.update(message.getBytes());
byte[] digest = md5.digest();
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
@Override
public String computeHash(String password, String salt, String name) {
return computeHash(password, null);
}
public String computeHash(String password, String name) {
return HashUtils.md5(password);
}
@Override
public String computeHash(String password, String salt, String name)
throws NoSuchAlgorithmException {
return getMD5(password);
}
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
return hash.equals(computeHash(password, "", ""));
public boolean comparePassword(String hash, String password, String playerName) {
return hash.equals(computeHash(password, null));
}
}
@@ -1,33 +1,34 @@
package fr.xephi.authme.security.crypts;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
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 static fr.xephi.authme.security.HashUtils.md5;
@Recommendation(Usage.OK)
@HasSalt(value = SaltType.TEXT, length = 16)
public class MD5VB implements EncryptionMethod {
private static String getMD5(String message)
throws NoSuchAlgorithmException {
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.reset();
md5.update(message.getBytes());
byte[] digest = md5.digest();
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
@Override
public String computeHash(String password, String salt, String name) {
return "$MD5vb$" + salt + "$" + md5(md5(password) + salt);
}
public String computeHash(String password, String name) {
return computeHash(password, generateSalt(), null);
}
@Override
public String computeHash(String password, String salt, String name)
throws NoSuchAlgorithmException {
return "$MD5vb$" + salt + "$" + getMD5(getMD5(password) + salt);
}
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
public boolean comparePassword(String hash, String password, String playerName) {
String[] line = hash.split("\\$");
return hash.equals(computeHash(password, line[2], ""));
return line.length == 4 && hash.equals(computeHash(password, line[2], ""));
}
public String generateSalt() {
return HashUtils.generateSalt(16);
}
}
@@ -1,32 +1,31 @@
package fr.xephi.authme.security.crypts;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import fr.xephi.authme.security.HashUtils;
import fr.xephi.authme.security.crypts.description.HasSalt;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.SaltType;
import fr.xephi.authme.security.crypts.description.Usage;
/**
*/
@Recommendation(Usage.DO_NOT_USE)
@HasSalt(SaltType.NONE)
public class SHA1 implements EncryptionMethod {
private static String getSHA1(String message)
throws NoSuchAlgorithmException {
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
sha1.reset();
sha1.update(message.getBytes());
byte[] digest = sha1.digest();
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
@Override
public String computeHash(String password, String salt, String name) {
return computeHash(password, null);
}
public String computeHash(String password, String name) {
return HashUtils.sha1(password);
}
@Override
public String computeHash(String password, String salt, String name)
throws NoSuchAlgorithmException {
return getSHA1(password);
public boolean comparePassword(String hash, String password, String playerName) {
return hash.equals(computeHash(password, null));
}
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
return hash.equals(computeHash(password, "", ""));
public String generateSalt() {
return null;
}
}
@@ -59,11 +59,15 @@ package fr.xephi.authme.security.crypts;
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import java.security.NoSuchAlgorithmException;
import fr.xephi.authme.security.crypts.description.HasSalt;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.SaltType;
import fr.xephi.authme.security.crypts.description.Usage;
import java.util.Arrays;
/**
*/
@Recommendation(Usage.DO_NOT_USE)
@HasSalt(SaltType.NONE)
public class WHIRLPOOL implements EncryptionMethod {
/**
@@ -383,8 +387,11 @@ public class WHIRLPOOL implements EncryptionMethod {
}
@Override
public String computeHash(String password, String salt, String name)
throws NoSuchAlgorithmException {
public String computeHash(String password, String salt, String name) {
return computeHash(password, null);
}
public String computeHash(String password, String name) {
byte[] digest = new byte[DIGESTBYTES];
NESSIEinit();
NESSIEadd(password);
@@ -393,8 +400,8 @@ public class WHIRLPOOL implements EncryptionMethod {
}
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
return hash.equals(computeHash(password, "", ""));
public boolean comparePassword(String hash, String password, String playerName) {
return hash.equals(computeHash(password, null, null));
}
}
@@ -1,13 +1,18 @@
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 java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
/**
*/
@Recommendation(Usage.OK)
@HasSalt(value = SaltType.TEXT, length = 9)
public class WORDPRESS implements EncryptionMethod {
private static final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
@@ -102,18 +107,25 @@ public class WORDPRESS implements EncryptionMethod {
}
@Override
public String computeHash(String password, String salt, String name)
throws NoSuchAlgorithmException {
public String computeHash(String password, String salt, String name) {
byte random[] = new byte[6];
this.randomGen.nextBytes(random);
randomGen.nextBytes(random);
return crypt(password, gensaltPrivate(stringToUtf8(new String(random))));
}
public String computeHash(String password, String name) {
return computeHash(password, null, null);
}
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
public boolean comparePassword(String hash, String password, String playerName) {
String comparedHash = crypt(password, hash);
return comparedHash.equals(hash);
}
public String generateSalt() {
// This hash uses a salt, but it is not exposed to the outside
return null;
}
}
@@ -1,9 +1,13 @@
package fr.xephi.authme.security.crypts;
import java.security.NoSuchAlgorithmException;
import fr.xephi.authme.security.HashUtils;
import fr.xephi.authme.security.crypts.description.HasSalt;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.SaltType;
import fr.xephi.authme.security.crypts.description.Usage;
/**
*/
@Recommendation(Usage.RECOMMENDED)
@HasSalt(value = SaltType.TEXT, length = 12)
public class XAUTH implements EncryptionMethod {
public static String getWhirlpool(String message) {
@@ -16,19 +20,25 @@ public class XAUTH implements EncryptionMethod {
}
@Override
public String computeHash(String password, String salt, String name)
throws NoSuchAlgorithmException {
public String computeHash(String password, String salt, String name) {
String hash = getWhirlpool(salt + password).toLowerCase();
int saltPos = (password.length() >= hash.length() ? hash.length() - 1 : password.length());
return hash.substring(0, saltPos) + salt + hash.substring(saltPos);
}
public String computeHash(String password, String name) {
return computeHash(password, generateSalt(), null);
}
@Override
public boolean comparePassword(String hash, String password,
String playerName) throws NoSuchAlgorithmException {
public boolean comparePassword(String hash, String password, String playerName) {
int saltPos = (password.length() >= hash.length() ? hash.length() - 1 : password.length());
String salt = hash.substring(saltPos, saltPos + 12);
return hash.equals(computeHash(password, salt, ""));
}
public String generateSalt() {
return HashUtils.generateSalt(12);
}
}
@@ -0,0 +1,13 @@
package fr.xephi.authme.security.crypts.description;
/**
* Describes the type of salt the encryption algorithm uses. This is purely for documentation
* purposes and is ignored by the code.
*/
public @interface HasSalt {
SaltType value();
int length() default 0;
}
@@ -0,0 +1,10 @@
package fr.xephi.authme.security.crypts.description;
/**
* Annotation to mark a hash algorithm with the usage recommendation, see {@link Usage}.
*/
public @interface Recommendation {
Usage value();
}
@@ -0,0 +1,17 @@
package fr.xephi.authme.security.crypts.description;
/**
* The type of salt used by an encryption algorithms.
*/
public enum SaltType {
/** Random, newly generated text. */
TEXT,
/** The username, including variations or repetitions. */
USERNAME,
/** No salt. */
NONE
}
@@ -0,0 +1,20 @@
package fr.xephi.authme.security.crypts.description;
/**
* Usage recommendation that can be provided for a hash algorithm.
*/
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. */
OK,
/** Hash algorithm is not recommended to be used. Use only if required by another system. */
DO_NOT_USE,
/** The algorithm does not work properly; do not use. */
DOES_NOT_WORK
}