Add tools task to generate an encryption algorithm overview

This commit is contained in:
ljacqu 2015-12-31 13:33:00 +01:00
parent a0da423a7b
commit 6475cecd79
10 changed files with 465 additions and 3 deletions

View File

@ -10,8 +10,6 @@ import fr.xephi.authme.security.crypts.description.HasSalt;
import java.nio.charset.Charset;
import java.security.MessageDigest;
@Recommendation(Usage.DO_NOT_USE)
@HasSalt(SaltType.USERNAME)
public class CRAZYCRYPT1 extends UsernameSaltMethod {
private static final char[] CRYPTCHARS =

View File

@ -8,7 +8,7 @@ import fr.xephi.authme.security.crypts.description.Usage;
import static fr.xephi.authme.security.HashUtils.md5;
@Recommendation(Usage.DO_NOT_USE)
@Recommendation(Usage.ACCEPTABLE)
@HasSalt(value = SaltType.TEXT, length = 5)
public class IPB3 extends SeparateSaltMethod {

View File

@ -1,9 +1,15 @@
package fr.xephi.authme.security.crypts;
import fr.xephi.authme.security.RandomString;
import fr.xephi.authme.security.crypts.description.HasSalt;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.SaltType;
import fr.xephi.authme.security.crypts.description.Usage;
import static fr.xephi.authme.security.HashUtils.md5;
@Recommendation(Usage.ACCEPTABLE)
@HasSalt(value = SaltType.TEXT, length = 8)
public class MYBB extends SeparateSaltMethod {
@Override

View File

@ -1,9 +1,15 @@
package fr.xephi.authme.security.crypts;
import fr.xephi.authme.security.RandomString;
import fr.xephi.authme.security.crypts.description.HasSalt;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.SaltType;
import fr.xephi.authme.security.crypts.description.Usage;
import static fr.xephi.authme.security.HashUtils.sha1;
@Recommendation(Usage.ACCEPTABLE)
@HasSalt(value = SaltType.TEXT, length = 40)
public class WBB3 extends SeparateSaltMethod {
@Override

View File

@ -0,0 +1,78 @@
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
<!-- File auto-generated on Thu Dec 31 13:26:54 CET 2015. See hashmethods/hash_algorithms.tpl.md -->
## Hash Algorithms
AuthMe supports the following hash algorithms for storing your passwords safely.
Algorithm | Recommendation | Hash length | ASCII | | Salt type | Length | Separate? |
--------- | -------------- | ----------- | ----- | - | --------- | ------ | --------- |
BCRYPT | Recommended | 60 | | | Text | |
BCRYPT2Y | Recommended | 60 | | | Text | 22 |
CRAZYCRYPT1 | Do not use | 128 | | | Username | |
DOUBLEMD5 | Do not use | 32 | | | None | |
IPB3 | Acceptable | 32 | | | Text | 5 | Y
JOOMLA | Recommended | 65 | | | Text | 32 |
MD5 | Do not use | 32 | | | None | |
MD5VB | Acceptable | 56 | | | Text | 16 |
MYBB | Acceptable | 32 | | | Text | 8 | Y
PBKDF2 | Does not work | 329 | | | Text | 12 |
PBKDF2DJANGO | Acceptable | 77 | Y | | Text | 12 |
PHPBB | Acceptable | 34 | | | Text | 16 |
PHPFUSION | Do not use | 64 | Y | | | | Y
ROYALAUTH | Do not use | 128 | | | None | |
SALTED2MD5 | Acceptable | 32 | | | Text | | Y
SALTEDSHA512 | Recommended | 128 | | | | | Y
SHA1 | Do not use | 40 | | | None | |
SHA256 | Recommended | 86 | | | Text | 16 |
SHA512 | Do not use | 128 | | | None | |
SMF | Do not use | 40 | | | Username | |
WBB3 | Acceptable | 40 | | | Text | 40 | Y
WBB4 | Does not work | 60 | | | Text | 8 |
WHIRLPOOL | Do not use | 128 | | | None | |
WORDPRESS | Do not use | 34 | | | Text | 9 |
XAUTH | Recommended | 140 | | | Text | 12 |
CUSTOM | | | | | | | |
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
### Columns
#### Algorithm
The algorithm is the hashing algorithm used to store passwords with. Default is SHA256 and is recommended.
You can change the hashing algorithm in the config.yml: under `security`, locate `passwordHash`.
#### Recommendation
The recommendation lists our usage recommendation in terms of how secure it is (not how _well_ the algorithm works!).
- Recommended: The hash algorithm appears to be cryptographically secure and is one we recommend.
- Acceptable: There are safer algorithms that can be chosen but using the algorithm is generally OK.
- Do not use: Hash algorithm isn't sufficiently secure. Use only if required to hook into another system.
- Does not work: The algorithm does not work properly; do not use.
#### Hash Length
The length of the hashes the algorithm produces. Note that the hash length is not (primarily) indicative of
whether an algorithm is secure or not.
#### ASCII
If denoted with a **y**, means that the algorithm is restricted to ASCII characters only, i.e. it will simply ignore
"special characters" such as `ÿ` or `Â`. Note that we do not recommend the use of "special characters" in passwords.
#### Salt Columns
Before hashing, a _salt_ may be appended to the password to make the hash more secure. The following columns describe
the salt the algorithm uses.
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
##### Salt Type
We do not recommend the usage
of any algorithm that doesn't use a randomly generated text as salt. This "salt type" column indicates what type of
salt the algorithm uses:
- Text: randomly generated text (see also the following column, "Length")
- Username: the salt is constructed from the username (bad)
- None: the algorithm uses no salt (bad)
##### Length
If applicable (salt type is "Text"), indicates the length of the generated salt. The longer the better.
If this column is empty when the salt type is "Text", it typically means the salt length can be defined in config.yml.
##### Separate
If denoted with a **y**, it means that the salt is stored in a separate column in the database. This is neither good
or bad.

View File

@ -0,0 +1,135 @@
package hashmethods;
import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.security.crypts.EncryptionMethod;
import fr.xephi.authme.security.crypts.HexSaltedMethod;
import fr.xephi.authme.security.crypts.description.AsciiRestricted;
import fr.xephi.authme.security.crypts.description.HasSalt;
import fr.xephi.authme.security.crypts.description.Recommendation;
import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import static com.google.common.collect.Sets.newHashSet;
/**
* Gathers information on {@link fr.xephi.authme.security.crypts.EncryptionMethod} implementations based on
* the annotations in {@link fr.xephi.authme.security.crypts.description}.
*/
public class EncryptionMethodInfoGatherer {
@SuppressWarnings("unchecked")
private final static Set<Class<? extends Annotation>> RELEVANT_ANNOTATIONS =
newHashSet(HasSalt.class, Recommendation.class, AsciiRestricted.class);
private Map<HashAlgorithm, MethodDescription> descriptions;
public EncryptionMethodInfoGatherer() {
descriptions = new LinkedHashMap<>();
constructDescriptions();
}
public Map<HashAlgorithm, MethodDescription> getDescriptions() {
return descriptions;
}
private void constructDescriptions() {
for (HashAlgorithm algorithm : HashAlgorithm.values()) {
Class<? extends EncryptionMethod> methodClazz = algorithm.getClazz();
if (!HashAlgorithm.CUSTOM.equals(algorithm) && !methodClazz.isAnnotationPresent(Deprecated.class)) {
MethodDescription description = createDescription(methodClazz);
descriptions.put(algorithm, description);
}
}
}
private static MethodDescription createDescription(Class<? extends EncryptionMethod> clazz) {
EncryptionMethod method = instantiateMethod(clazz);
MethodDescription description = new MethodDescription(clazz);
description.setHashLength(method.computeHash("test", "user").getHash().length());
description.setHasSeparateSalt(method.hasSeparateSalt());
Map<Class<?>, Annotation> annotationMap = gatherAnnotations(clazz);
if (annotationMap.containsKey(HasSalt.class)) {
setSaltInformation(description, returnTyped(annotationMap, HasSalt.class), method);
}
if (annotationMap.containsKey(Recommendation.class)) {
description.setUsage(returnTyped(annotationMap, Recommendation.class).value());
}
if (annotationMap.containsKey(AsciiRestricted.class)) {
description.setAsciiRestricted(true);
}
return description;
}
private static Map<Class<?>, Annotation> gatherAnnotations(Class<?> methodClass) {
// Note ljacqu 20151231: The map could be Map<Class<? extends Annotation>, Annotation> and it has the constraint
// that for a key Class<T>, the value is of type T. We write a simple "Class<?>" for brevity.
Map<Class<?>, Annotation> collection = new HashMap<>();
Class<?> currentMethodClass = methodClass;
while (currentMethodClass != null) {
getRelevantAnnotations(currentMethodClass, collection);
currentMethodClass = getSuperClass(currentMethodClass);
}
return collection;
}
// Parameters could be Class<? extends EncryptionMethod>; Map<Class<? extends Annotation>, Annotation>
// but the constraint doesn't have any technical relevance, so just clutters the code
private static void getRelevantAnnotations(Class<?> methodClass, Map<Class<?>, Annotation> collection) {
for (Annotation annotation : methodClass.getAnnotations()) {
if (RELEVANT_ANNOTATIONS.contains(annotation.annotationType())
&& !collection.containsKey(annotation.annotationType())) {
collection.put(annotation.annotationType(), annotation);
}
}
}
/**
* Returns the super class of the given encryption method if it is also of EncryptionMethod type.
* (Anything beyond EncryptionMethod is not of interest.)
*/
private static Class<?> getSuperClass(Class<?> methodClass) {
Class<?> zuper = methodClass.getSuperclass();
if (EncryptionMethod.class.isAssignableFrom(zuper)) {
return zuper;
}
return null;
}
/**
* Set the salt information for the given encryption method and the found {@link HasSalt} annotation.
* Also gets the salt length from {@link HexSaltedMethod#getSaltLength()} for such instances.
*
* @param description The description to update
* @param hasSalt The associated HasSalt annotation
* @param method The encryption method
*/
private static void setSaltInformation(MethodDescription description, HasSalt hasSalt, EncryptionMethod method) {
description.setSaltType(hasSalt.value());
if (hasSalt.length() != 0) {
description.setSaltLength(hasSalt.length());
} else if (method instanceof HexSaltedMethod) {
int saltLength = ((HexSaltedMethod) method).getSaltLength();
description.setSaltLength(saltLength);
}
}
private static EncryptionMethod instantiateMethod(Class<? extends EncryptionMethod> clazz) {
try {
return clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException("Could not instantiate " + clazz, e);
}
}
// Convenience method for retrieving an annotation in a typed fashion.
// We know implicitly that the key of the map always corresponds to the type of the value
private static <T> T returnTyped(Map<Class<?>, Annotation> map, Class<T> key) {
return key.cast(map.get(key));
}
}

View File

@ -0,0 +1,100 @@
package hashmethods;
import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.util.WrapperMock;
import utils.ANewMap;
import utils.FileUtils;
import utils.TagReplacer;
import utils.ToolTask;
import utils.ToolsConstants;
import java.util.Map;
import java.util.Scanner;
/**
* Task for generating the markdown page describing the AuthMe hash algorithms.
*
* @see {@link fr.xephi.authme.security.HashAlgorithm}
*/
public class HashAlgorithmsDescriptionTask implements ToolTask {
private static final String CUR_FOLDER = ToolsConstants.TOOLS_SOURCE_ROOT + "hashmethods/";
private static final String OUTPUT_FILE = ToolsConstants.DOCS_FOLDER + "hash_algorithms.md";
@Override
public void execute(Scanner scanner) {
// Unfortunately, we need the Wrapper to be around to work with Settings, and certain encryption methods
// directly read from the Settings file
WrapperMock.createInstance();
Settings.bCryptLog2Rounds = 8;
Settings.saltLength = 8;
// Gather info and construct a row for each method
EncryptionMethodInfoGatherer infoGatherer = new EncryptionMethodInfoGatherer();
Map<HashAlgorithm, MethodDescription> descriptions = infoGatherer.getDescriptions();
final String methodRows = constructMethodRows(descriptions);
// Write to the docs file
Map<String, String> tags = ANewMap.with("method_rows", methodRows).build();
FileUtils.generateFileFromTemplate(CUR_FOLDER + "hash_algorithms.tpl.md", OUTPUT_FILE, tags);
}
private static String constructMethodRows(Map<HashAlgorithm, MethodDescription> descriptions) {
final String rowTemplate = FileUtils.readFromFile(CUR_FOLDER + "hash_algorithms_row.tpl.md");
StringBuilder result = new StringBuilder();
for (Map.Entry<HashAlgorithm, MethodDescription> entry : descriptions.entrySet()) {
MethodDescription description = entry.getValue();
Map<String, String> tags = ANewMap
.with("name", asString(entry.getKey()))
.and("recommendation", asString(description.getUsage()))
.and("hash_length", asString(description.getHashLength()))
.and("ascii_restricted", asString(description.isAsciiRestricted()))
.and("salt_type", asString(description.getSaltType()))
.and("salt_length", asString(description.getSaltLength()))
.and("separate_salt", asString(description.hasSeparateSalt()))
.build();
result.append(TagReplacer.applyReplacements(rowTemplate, tags));
}
return result.toString();
}
@Override
public String getTaskName() {
return "describeHashAlgos";
}
// ----
// String representations
// ----
private static String asString(boolean value) {
return value ? "Y" : "";
}
private static String asString(int value) {
return String.valueOf(value);
}
private static String asString(Integer value) {
if (value == null) {
return "";
}
return String.valueOf(value);
}
private static String asString(HashAlgorithm value) {
return value.toString();
}
private static <E extends Enum<E>> String asString(E value) {
if (value == null) {
return "";
}
// Get the enum name and replace something like "DO_NOT_USE" to "Do not use"
String enumName = value.toString().replace("_", " ");
return enumName.length() > 2
? enumName.substring(0, 1) + enumName.substring(1).toLowerCase()
: enumName;
}
}

View File

@ -0,0 +1,85 @@
package hashmethods;
import fr.xephi.authme.security.crypts.EncryptionMethod;
import fr.xephi.authme.security.crypts.description.SaltType;
import fr.xephi.authme.security.crypts.description.Usage;
/**
* Description of a {@link EncryptionMethod}.
*/
public class MethodDescription {
/** The implementation class the description belongs to. */
private final Class<? extends EncryptionMethod> method;
/** The type of the salt that is used. */
private SaltType saltType;
/** The length of the salt for SaltType.TEXT salts. */
private Integer saltLength;
/** The usage recommendation. */
private Usage usage;
/** Whether or not the encryption method is restricted to ASCII characters for proper functioning. */
private boolean asciiRestricted;
/** Whether or not the encryption method requires its salt stored separately. */
private boolean hasSeparateSalt;
/** The length of the hash output, based on a test hash (i.e. assumes same length for all hashes.) */
private int hashLength;
public MethodDescription(Class<? extends EncryptionMethod> method) {
this.method = method;
}
// Trivial getters and setters
public Class<? extends EncryptionMethod> getMethod() {
return method;
}
public SaltType getSaltType() {
return saltType;
}
public void setSaltType(SaltType saltType) {
this.saltType = saltType;
}
public Integer getSaltLength() {
return saltLength;
}
public void setSaltLength(int saltLength) {
this.saltLength = saltLength;
}
public Usage getUsage() {
return usage;
}
public void setUsage(Usage usage) {
this.usage = usage;
}
public boolean isAsciiRestricted() {
return asciiRestricted;
}
public void setAsciiRestricted(boolean asciiRestricted) {
this.asciiRestricted = asciiRestricted;
}
public boolean hasSeparateSalt() {
return hasSeparateSalt;
}
public void setHasSeparateSalt(boolean hasSeparateSalt) {
this.hasSeparateSalt = hasSeparateSalt;
}
public int getHashLength() {
return hashLength;
}
public void setHashLength(int hashLength) {
this.hashLength = hashLength;
}
}

View File

@ -0,0 +1,53 @@
<!-- {gen_warning} -->
<!-- File auto-generated on {gen_date}. See hashmethods/hash_algorithms.tpl.md -->
## Hash Algorithms
AuthMe supports the following hash algorithms for storing your passwords safely.
Algorithm | Recommendation | Hash length | ASCII | | Salt type | Length | Separate? |
--------- | -------------- | ----------- | ----- | - | --------- | ------ | --------- |
{method_rows}CUSTOM | | | | | | | |
<!-- {gen_warning} -->
### Columns
#### Algorithm
The algorithm is the hashing algorithm used to store passwords with. Default is SHA256 and is recommended.
You can change the hashing algorithm in the config.yml: under `security`, locate `passwordHash`.
#### Recommendation
The recommendation lists our usage recommendation in terms of how secure it is (not how _well_ the algorithm works!).
- Recommended: The hash algorithm appears to be cryptographically secure and is one we recommend.
- Acceptable: There are safer algorithms that can be chosen but using the algorithm is generally OK.
- Do not use: Hash algorithm isn't sufficiently secure. Use only if required to hook into another system.
- Does not work: The algorithm does not work properly; do not use.
#### Hash Length
The length of the hashes the algorithm produces. Note that the hash length is not (primarily) indicative of
whether an algorithm is secure or not.
#### ASCII
If denoted with a **y**, means that the algorithm is restricted to ASCII characters only, i.e. it will simply ignore
"special characters" such as `ÿ` or `Â`. Note that we do not recommend the use of "special characters" in passwords.
#### Salt Columns
Before hashing, a _salt_ may be appended to the password to make the hash more secure. The following columns describe
the salt the algorithm uses.
<!-- {gen_warning} -->
##### Salt Type
We do not recommend the usage
of any algorithm that doesn't use a randomly generated text as salt. This "salt type" column indicates what type of
salt the algorithm uses:
- Text: randomly generated text (see also the following column, "Length")
- Username: the salt is constructed from the username (bad)
- None: the algorithm uses no salt (bad)
##### Length
If applicable (salt type is "Text"), indicates the length of the generated salt. The longer the better.
If this column is empty when the salt type is "Text", it typically means the salt length can be defined in config.yml.
##### Separate
If denoted with a **y**, it means that the salt is stored in a separate column in the database. This is neither good
or bad.

View File

@ -0,0 +1 @@
{name} | {recommendation} | {hash_length} | {ascii_restricted} | | {salt_type} | {salt_length} | {separate_salt}