[New] Added Crypt, to generate crypt like strings for salt and pw

This commit is contained in:
Robert von Burg 2017-10-25 09:41:47 +02:00
parent d44da5b587
commit e2aea114d3
6 changed files with 439 additions and 43 deletions

View File

@ -0,0 +1,9 @@
package li.strolch.privilege.base;
public class PrivilegeConstants {
public static final String DEFAULT_ALGORITHM = "PBKDF2WithHmacSHA512";
public static final int DEFAULT_KEY_LENGTH = 256;
public static final int DEFAULT_SMALL_ITERATIONS = 10000;
public static final int DEFAULT_ITERATIONS = 200000;
}

View File

@ -15,6 +15,13 @@
*/
package li.strolch.privilege.handler;
import static li.strolch.privilege.base.PrivilegeConstants.DEFAULT_ALGORITHM;
import static li.strolch.privilege.base.PrivilegeConstants.DEFAULT_ITERATIONS;
import static li.strolch.privilege.base.PrivilegeConstants.DEFAULT_KEY_LENGTH;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
@ -22,28 +29,24 @@ import java.security.spec.InvalidKeySpecException;
import java.text.MessageFormat;
import java.util.Map;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import li.strolch.privilege.base.PrivilegeException;
import li.strolch.privilege.helper.Crypt;
import li.strolch.privilege.helper.XmlConstants;
import li.strolch.utils.helper.StringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>
* This default {@link EncryptionHandler} creates tokens using a {@link SecureRandom} object. Hashing is done by using
* {@link MessageDigest} and the configured algorithm which is passed in the parameters
* </p>
*
*
* Required parameters:
* <ul>
* <li>{@link XmlConstants#XML_PARAM_HASH_ALGORITHM}</li>
* </ul>
*
*
* @author Robert von Burg <eitch@eitchnet.ch>
*/
public class DefaultEncryptionHandler implements EncryptionHandler {
@ -73,6 +76,26 @@ public class DefaultEncryptionHandler implements EncryptionHandler {
*/
private int keyLength;
@Override
public Crypt newCryptInstance() {
return new Crypt().setAlgorithm(this.algorithm).setIterations(this.iterations).setKeyLength(this.keyLength);
}
@Override
public String getAlgorithm() {
return this.algorithm;
}
@Override
public int getIterations() {
return this.iterations;
}
@Override
public int getKeyLength() {
return this.keyLength;
}
@Override
public String nextToken() {
byte[] bytes = new byte[32];
@ -94,8 +117,7 @@ public class DefaultEncryptionHandler implements EncryptionHandler {
SecretKeyFactory skf = SecretKeyFactory.getInstance(this.algorithm);
PBEKeySpec spec = new PBEKeySpec(password, salt, this.iterations, this.keyLength);
SecretKey key = skf.generateSecret(spec);
byte[] res = key.getEncoded();
return res;
return key.getEncoded();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new IllegalStateException(e);
@ -108,14 +130,17 @@ public class DefaultEncryptionHandler implements EncryptionHandler {
this.secureRandom = new SecureRandom();
// get hash algorithm parameters
this.algorithm = parameterMap.getOrDefault(XmlConstants.XML_PARAM_HASH_ALGORITHM, "PBKDF2WithHmacSHA512");
this.iterations = Integer.parseInt(parameterMap.getOrDefault(XmlConstants.XML_PARAM_HASH_ITERATIONS, "200000"));
this.keyLength = Integer.parseInt(parameterMap.getOrDefault(XmlConstants.XML_PARAM_HASH_KEY_LENGTH, "256"));
this.algorithm = parameterMap.getOrDefault(XmlConstants.XML_PARAM_HASH_ALGORITHM, DEFAULT_ALGORITHM);
this.iterations = Integer.parseInt(
parameterMap.getOrDefault(XmlConstants.XML_PARAM_HASH_ITERATIONS, String.valueOf(DEFAULT_ITERATIONS)));
this.keyLength = Integer.parseInt(
parameterMap.getOrDefault(XmlConstants.XML_PARAM_HASH_KEY_LENGTH, String.valueOf(DEFAULT_KEY_LENGTH)));
// test hash algorithm
try {
hashPassword("test".toCharArray(), "test".getBytes()); //$NON-NLS-1$
DefaultEncryptionHandler.logger.info(MessageFormat.format("Using hashing algorithm {0}", this.algorithm)); //$NON-NLS-1$
DefaultEncryptionHandler.logger
.info(MessageFormat.format("Using hashing algorithm {0}", this.algorithm)); //$NON-NLS-1$
} catch (Exception e) {
String msg = "[{0}] Defined parameter {1} is invalid because of underlying exception: {2}"; //$NON-NLS-1$
msg = MessageFormat.format(msg, EncryptionHandler.class.getName(), XmlConstants.XML_PARAM_HASH_ALGORITHM,

View File

@ -17,46 +17,76 @@ package li.strolch.privilege.handler;
import java.util.Map;
import li.strolch.privilege.helper.Crypt;
/**
* The {@link EncryptionHandler} exposes API which is used to handle encrypting of strings, or returning secure tokens
* for certificates and so forth
*
*
* @author Robert von Burg <eitch@eitchnet.ch>
*/
public interface EncryptionHandler {
/**
* Generates a token which can be used to identify certificates and so forth
*
* @return a new token
* Returns a new crypt instance
*
* @return a new crypt instance
*/
public String nextToken();
Crypt newCryptInstance();
/**
* Returns the configured algorithm
*
* @return the configured algorithm
*/
String getAlgorithm();
/**
* Returns the configured iterations
*
* @return the configured iterations
*/
int getIterations();
/**
* Returns the configured key length
*
* @return the configured key length
*/
int getKeyLength();
/**
* Generates a token which can be used to identify certificates and so forth
*
*
* @return a new token
*/
public byte[] nextSalt();
String nextToken();
/**
* Generates a token which can be used to identify certificates and so forth
*
* @return a new token
*/
byte[] nextSalt();
/**
* Hashes the given password with the given salt with the configured algorithm
*
*
* @param password
* the password
* the password
* @param salt
* the salt
*
* the salt
*
* @return the hashed password
*/
public byte[] hashPassword(final char[] password, final byte[] salt);
byte[] hashPassword(final char[] password, final byte[] salt);
/**
* Initialize the concrete {@link EncryptionHandler}. The passed parameter map contains any configuration the
* concrete {@link EncryptionHandler} might need
*
*
* @param parameterMap
* a map containing configuration properties
* a map containing configuration properties
*/
public void initialize(Map<String, String> parameterMap);
void initialize(Map<String, String> parameterMap);
}

View File

@ -0,0 +1,226 @@
package li.strolch.privilege.helper;
import static li.strolch.privilege.base.PrivilegeConstants.DEFAULT_SMALL_ITERATIONS;
import static li.strolch.utils.helper.StringHelper.fromHexString;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import li.strolch.utils.dbc.DBC;
import li.strolch.utils.helper.StringHelper;
public class Crypt {
private String algorithm;
private int keyLength;
private int iterations;
private byte[] salt;
private byte[] password;
public Crypt() {
// nothing to do
}
public String getAlgorithm() {
return algorithm;
}
public Crypt setAlgorithm(String algorithm) {
this.algorithm = algorithm;
return this;
}
public byte[] getSalt() {
return salt;
}
public Crypt setSalt(byte[] salt) {
this.salt = salt;
return this;
}
public int getKeyLength() {
return this.keyLength;
}
public Crypt setKeyLength(int keyLength) {
this.keyLength = keyLength;
return this;
}
public int getIterations() {
return this.iterations;
}
public Crypt setIterations(int iterations) {
this.iterations = iterations;
return this;
}
public byte[] getPassword() {
return password;
}
public Crypt setPassword(byte[] password) {
this.password = password;
return this;
}
public Crypt parseCrypt(String crypt) {
DBC.PRE.assertNotEmpty("crypt can no be empty", crypt);
if (crypt.contains("$")) {
String[] parts = crypt.split("\\$");
if (parts.length == 5) {
setAlgorithm(parts[1], true);
Map<String, String> algOptions = parseAlgOptions(parts[2]);
if (algOptions == null)
this.iterations = DEFAULT_SMALL_ITERATIONS;
else
this.iterations = Integer.parseInt(algOptions.get("rounds"));
this.salt = fromHexString(parts[3]);
this.password = fromHexString(parts[4]);
} else if (parts.length == 4) {
setAlgorithm(parts[1], true);
this.iterations = DEFAULT_SMALL_ITERATIONS;
this.salt = fromHexString(parts[2]);
this.password = fromHexString(parts[3]);
} else if (parts.length == 3) {
setAlgorithm(parts[1], false);
this.password = fromHexString(parts[2]);
} else {
throw new IllegalStateException("Wrong number of $ chars in " + crypt + ": " + parts.length);
}
} else {
this.algorithm = "SHA-512";
this.password = fromHexString(crypt);
}
return this;
}
public void assertSame(char[] password) {
if (!isSame(password))
throw new IllegalStateException("Passwords not the same");
}
public boolean isSame(char[] password) {
if (this.password == null)
throw new IllegalStateException("password not set, call parseCrypt() first!");
if (password == null)
throw new IllegalStateException("password must not be null");
try {
byte[] hash;
if (this.salt == null) {
hash = StringHelper.hash(this.algorithm, new String(password).getBytes());
} else {
PBEKeySpec spec = new PBEKeySpec(password, this.salt, this.iterations, this.keyLength);
SecretKeyFactory skf = SecretKeyFactory.getInstance(this.algorithm);
SecretKey key = skf.generateSecret(spec);
hash = key.getEncoded();
}
return Arrays.equals(hash, this.password);
} catch (Exception e) {
throw new IllegalStateException("Failed validation password for algorithm " + this.algorithm, e);
}
}
public String toCryptString() {
StringBuilder sb = new StringBuilder();
sb.append("$");
switch (this.algorithm) {
case "MD5":
sb.append("1");
break;
case "PBKDF2WithHmacSHA256":
case "SHA-256":
sb.append("5");
break;
case "PBKDF2WithHmacSHA512":
case "SHA-512":
sb.append("6");
break;
default:
throw new IllegalStateException("Unhandled algorithm " + this.algorithm);
}
if (this.iterations != 0 && this.iterations != DEFAULT_SMALL_ITERATIONS) {
sb.append("$");
sb.append("rounds");
sb.append("=");
sb.append(iterations);
}
if (this.salt != null) {
sb.append("$");
sb.append(StringHelper.toHexString(this.salt));
}
sb.append("$");
sb.append(StringHelper.toHexString(this.password));
return sb.toString();
}
private Map<String, String> parseAlgOptions(String part) {
String[] options = part.split(",");
Map<String, String> algOptions = new HashMap<>(options.length);
for (String option : options) {
if (option.trim().isEmpty())
continue;
if (!option.contains("="))
throw new IllegalStateException("Option " + option + " is missing = char");
String[] keyValue = option.split("=");
algOptions.put(keyValue[0].trim(), keyValue[1].trim());
}
return algOptions;
}
private void setAlgorithm(String id, boolean hasSalt) {
switch (id) {
case "1":
this.algorithm = "MD5";
this.keyLength = 0;
break;
case "5":
this.algorithm = hasSalt ? "PBKDF2WithHmacSHA256" : "SHA-256";
this.keyLength = 256;
break;
case "6":
this.algorithm = hasSalt ? "PBKDF2WithHmacSHA512" : "SHA-512";
this.keyLength = 256;
break;
default:
throw new IllegalStateException("Unhandled ID " + id);
}
}
}

View File

@ -15,13 +15,16 @@
*/
package li.strolch.privilege.helper;
import static li.strolch.privilege.base.PrivilegeConstants.DEFAULT_ALGORITHM;
import static li.strolch.privilege.base.PrivilegeConstants.DEFAULT_SMALL_ITERATIONS;
import static li.strolch.privilege.base.PrivilegeConstants.DEFAULT_KEY_LENGTH;
import javax.crypto.SecretKeyFactory;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.SecretKeyFactory;
import li.strolch.privilege.handler.DefaultEncryptionHandler;
import li.strolch.utils.helper.StringHelper;
@ -29,16 +32,17 @@ import li.strolch.utils.helper.StringHelper;
* <p>
* Simple main class which can be used to create a hash from a password which the user must type in at the command line
* </p>
*
*
* @author Robert von Burg <eitch@eitchnet.ch>
*/
public class PasswordCreator {
/**
* @param args
* the args from the command line, NOT USED
* the args from the command line, NOT USED
*
* @throws Exception
* thrown if anything goes wrong
* thrown if anything goes wrong
*/
@SuppressWarnings("nls")
public static void main(String[] args) throws Exception {
@ -49,11 +53,11 @@ public class PasswordCreator {
String hashAlgorithm = null;
while (hashAlgorithm == null) {
System.out.print("Hash Algorithm [PBKDF2WithHmacSHA512]: ");
System.out.print("Hash Algorithm [" + DEFAULT_ALGORITHM + "]: ");
String readLine = r.readLine().trim();
if (readLine.isEmpty()) {
hashAlgorithm = "PBKDF2WithHmacSHA512";
hashAlgorithm = DEFAULT_ALGORITHM;
} else {
try {
@ -68,11 +72,11 @@ public class PasswordCreator {
int iterations = -1;
while (iterations == -1) {
System.out.print("Hash iterations [10000]: ");
System.out.print("Hash iterations [" + DEFAULT_SMALL_ITERATIONS + "]: ");
String readLine = r.readLine().trim();
if (readLine.isEmpty()) {
iterations = 10000;
iterations = DEFAULT_SMALL_ITERATIONS;
} else {
try {
@ -86,11 +90,11 @@ public class PasswordCreator {
int keyLength = -1;
while (keyLength == -1) {
System.out.print("Hash keyLength [256]: ");
System.out.print("Hash keyLength [" + DEFAULT_KEY_LENGTH + "]: ");
String readLine = r.readLine().trim();
if (readLine.isEmpty()) {
keyLength = 256;
keyLength = DEFAULT_KEY_LENGTH;
} else {
try {
@ -98,6 +102,7 @@ public class PasswordCreator {
if (keyLength <= 0)
throw new IllegalArgumentException("KeyLength must be > 0");
} catch (Exception e) {
System.err.println(e.getLocalizedMessage());
System.err.println(e.getLocalizedMessage());
keyLength = -1;
}
@ -128,8 +133,9 @@ public class PasswordCreator {
System.out.println("Salt is: " + saltS);
System.out.println();
System.out.println(XmlConstants.XML_ATTR_PASSWORD + "=\"" + passwordHashS + "\" "
+ XmlConstants.XML_ATTR_SALT + "=\"" + saltS + "\"");
System.out.println(
XmlConstants.XML_ATTR_PASSWORD + "=\"" + passwordHashS + "\" " + XmlConstants.XML_ATTR_SALT + "=\""
+ saltS + "\"");
System.out.println();
}
}

View File

@ -0,0 +1,100 @@
package li.strolch.privilege.test;
import static li.strolch.privilege.base.PrivilegeConstants.*;
import static org.junit.Assert.assertEquals;
import java.util.HashMap;
import java.util.Map;
import li.strolch.privilege.handler.DefaultEncryptionHandler;
import li.strolch.privilege.helper.Crypt;
import li.strolch.privilege.helper.XmlConstants;
import org.junit.BeforeClass;
import org.junit.Test;
public class CryptTest {
private static DefaultEncryptionHandler encryptionHandler;
@BeforeClass
public static void beforeClass() {
Map<String, String> parameterMap = new HashMap<>();
parameterMap.put(XmlConstants.XML_PARAM_HASH_ALGORITHM, DEFAULT_ALGORITHM);
parameterMap.put(XmlConstants.XML_PARAM_HASH_ITERATIONS, "" + DEFAULT_SMALL_ITERATIONS);
parameterMap.put(XmlConstants.XML_PARAM_HASH_KEY_LENGTH, "" + DEFAULT_KEY_LENGTH);
encryptionHandler = new DefaultEncryptionHandler();
encryptionHandler.initialize(parameterMap);
}
@Test
public void shouldAssertSamePassword01() {
String hash = "$1$21232f297a57a5a743894a0e4a801fc3";
char[] password = "admin".toCharArray();
Crypt crypt = new Crypt().parseCrypt(hash);
crypt.assertSame(password);
assertEquals(hash, crypt.toCryptString());
}
@Test
public void shouldAssertSamePassword05() {
String hash = "$5$8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918";
char[] password = "admin".toCharArray();
Crypt crypt = new Crypt().parseCrypt(hash);
crypt.assertSame(password);
assertEquals(hash, crypt.toCryptString());
}
@Test
public void shouldAssertSamePassword06() {
String hash = "$6$c7ad44cbad762a5da0a452f9e854fdc1e0e7a52a38015f23f3eab1d80b931dd472634dfac71cd34ebc35d16ab7fb8a90c81f975113d6c7538dc69dd8de9077ec";
char[] password = "admin".toCharArray();
Crypt crypt = new Crypt().parseCrypt(hash);
crypt.assertSame(password);
assertEquals(hash, crypt.toCryptString());
}
@Test
public void shouldAssertSamePassword15() {
String hash = "$5$61646d696e$f4aec2c20dd0c3ff0547f4bd56facd76097cce7c613da80c67842b6a357fdc04";
char[] password = "admin".toCharArray();
Crypt crypt = new Crypt().parseCrypt(hash);
crypt.assertSame(password);
assertEquals(hash, crypt.toCryptString());
}
@Test
public void shouldAssertSamePassword16() {
String hash = "$6$rounds=5000$61646d696e$5a39ca7443147f9bf549ee0c2d5ded0640690ed56ef8c903e1b0da2a3339010b";
char[] password = "admin".toCharArray();
Crypt crypt = new Crypt().parseCrypt(hash);
crypt.assertSame(password);
assertEquals(hash, crypt.toCryptString());
}
@Test
public void shouldAssertSamePassword20() {
char[] password = "admin".toCharArray();
byte[] salt = "admin".getBytes();
byte[] passwordHash = encryptionHandler.hashPassword(password, salt);
Crypt crypt = encryptionHandler.newCryptInstance();
crypt.setSalt(salt);
crypt.setPassword(passwordHash);
String hash = "$6$61646d696e$cb69962946617da006a2f95776d78b49e5ec7941d2bdb2d25cdb05f957f64344";
assertEquals(hash, crypt.toCryptString());
}
}