created new PasswordUtil class based on BouncyCastle classes
This commit is contained in:
19
pom.xml
19
pom.xml
@ -29,11 +29,10 @@
|
|||||||
<artifactId>commons-codec</artifactId>
|
<artifactId>commons-codec</artifactId>
|
||||||
<version>1.6</version>
|
<version>1.6</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.lambdaworks</groupId>
|
<groupId>commons-net</groupId>
|
||||||
<artifactId>scrypt</artifactId>
|
<artifactId>commons-net</artifactId>
|
||||||
<version>1.4.0</version>
|
<version>3.3</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
@ -59,7 +58,6 @@
|
|||||||
<version>6.0</version>
|
<version>6.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.enterprisedt</groupId>
|
<groupId>com.enterprisedt</groupId>
|
||||||
<artifactId>edtFTPj</artifactId>
|
<artifactId>edtFTPj</artifactId>
|
||||||
@ -67,9 +65,14 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>commons-net</groupId>
|
<groupId>com.lambdaworks</groupId>
|
||||||
<artifactId>commons-net</artifactId>
|
<artifactId>scrypt</artifactId>
|
||||||
<version>3.3</version>
|
<version>1.4.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcprov-jdk15on</artifactId>
|
||||||
|
<version>1.52</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@ -28,7 +28,6 @@
|
|||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>commons-codec</groupId>
|
<groupId>commons-codec</groupId>
|
||||||
<artifactId>commons-codec</artifactId>
|
<artifactId>commons-codec</artifactId>
|
||||||
@ -42,7 +41,10 @@
|
|||||||
<groupId>org.apache.logging.log4j</groupId>
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
<artifactId>log4j-api</artifactId>
|
<artifactId>log4j-api</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcprov-jdk15on</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.lambdaworks</groupId>
|
<groupId>com.lambdaworks</groupId>
|
||||||
<artifactId>scrypt</artifactId>
|
<artifactId>scrypt</artifactId>
|
||||||
|
|||||||
@ -0,0 +1,237 @@
|
|||||||
|
package de.muehlencord.shared.security;
|
||||||
|
|
||||||
|
import static com.lambdaworks.crypto.SCryptUtil.check;
|
||||||
|
import static com.lambdaworks.crypto.SCryptUtil.scrypt;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import org.apache.commons.codec.binary.Base64;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author joern@muehlencord.de
|
||||||
|
*/
|
||||||
|
public abstract class OldPasswordUtil {
|
||||||
|
|
||||||
|
/** logging object */
|
||||||
|
// private final static Logger LOGGER = Logger.getLogger(PasswordUtil.class);
|
||||||
|
|
||||||
|
/** SCrypt CPU cost parameter */
|
||||||
|
private final static int scryptCpuCostParameter = 16384;
|
||||||
|
/** SCrypt memory cost parameter */
|
||||||
|
private final static int scryptMemCostParameter = 8;
|
||||||
|
/** SCrypt paralelization parameter */
|
||||||
|
private final static int scryptParallelizationParameter = 1;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns password (pos 0) and the salt (pos 1) of given plaintext password. Both strings are base64 encoded
|
||||||
|
*
|
||||||
|
* @param plainTextPassword he
|
||||||
|
* @param saltLength the length of the salt to use
|
||||||
|
* @return the password (pos 0) and the salt (pos 1) of given plaintext password. Both strings are base64 encoded
|
||||||
|
*
|
||||||
|
* @throws de.muehlencord.shared.security.SecurityException if any error occurs during the password generation
|
||||||
|
*/
|
||||||
|
public static String[] getMD5Password(final String plainTextPassword, final int saltLength) throws SecurityException {
|
||||||
|
byte[] unHashedPassword = getBase64MD5HashedPassword(plainTextPassword);
|
||||||
|
byte[] salt = createSalt(saltLength);
|
||||||
|
byte[] hashedPassword = hashPasswordWithSalt(unHashedPassword, salt);
|
||||||
|
|
||||||
|
// test
|
||||||
|
String saltStr = base64Encode(salt);
|
||||||
|
byte[] salt2 = base64Decode(saltStr);
|
||||||
|
if (!Arrays.equals(salt, salt2)) {
|
||||||
|
throw new SecurityException("Salt conversion failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
String[] returnValue = new String[2];
|
||||||
|
returnValue[0] = base64Encode(hashedPassword);
|
||||||
|
returnValue[1] = base64Encode(salt);
|
||||||
|
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the given password (plain text) matches the given crypted password. The crypted password is hashed with the given salt. Both strings, crypted
|
||||||
|
* password and salt, have to be base64 encoded
|
||||||
|
*
|
||||||
|
* @param plainTextPassword the plaintext password to compare to
|
||||||
|
* @param cryptedPasswordStr the crypted password to compare to
|
||||||
|
* @param saltStr the salt needed to hash the plaintext password with to get the correct crypted password if both passwords match
|
||||||
|
* @return true, if and only if the encryption of plainTextPassword (hashed with saltStr) equals to cryptedPasswordStr
|
||||||
|
*
|
||||||
|
* @throws de.muehlencord.shared.security.SecurityException if any error occures during the check
|
||||||
|
*/
|
||||||
|
public static boolean checkPassword(String plainTextPassword, String cryptedPasswordStr, String saltStr) throws SecurityException {
|
||||||
|
byte[] salt = base64Decode(saltStr);
|
||||||
|
byte[] newPassword = getBase64MD5HashedPassword(plainTextPassword);
|
||||||
|
byte[] newHashedPassword = hashPasswordWithSalt(newPassword, salt);
|
||||||
|
byte[] crytepdPassword = base64Decode(cryptedPasswordStr);
|
||||||
|
return Arrays.equals(crytepdPassword, newHashedPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns a new salt as a string
|
||||||
|
*
|
||||||
|
* @param saltLength the length of the salt
|
||||||
|
* @return a new salt as a string (base64 encoded)
|
||||||
|
*
|
||||||
|
* @throws SecurityException if the creation of the salt fails
|
||||||
|
*/
|
||||||
|
public static String createSaltString(int saltLength) throws SecurityException {
|
||||||
|
byte[] saltByteArray = createSalt(saltLength);
|
||||||
|
String saltString = base64Encode(saltByteArray);
|
||||||
|
if (saltString.length() > saltLength) {
|
||||||
|
return saltString.substring(0, saltLength);
|
||||||
|
} else {
|
||||||
|
return saltString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** returns a random string with total length starting with prefix string
|
||||||
|
*
|
||||||
|
* @param prefix the prefix to start the string with
|
||||||
|
* @param length the maximum length of the string (including prefix)
|
||||||
|
* @return a random string
|
||||||
|
*
|
||||||
|
* @throws SecurityException if the random string could not be computed
|
||||||
|
*/
|
||||||
|
public static String getRandomString(final String prefix, int length) throws SecurityException {
|
||||||
|
String usedPrefix = (prefix == null ? "" : prefix);
|
||||||
|
|
||||||
|
int idLength = length - usedPrefix.length();
|
||||||
|
return usedPrefix + createSaltString(idLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* *** private methods *** */
|
||||||
|
/**
|
||||||
|
* creates a salt and returns the value as byte[]
|
||||||
|
*
|
||||||
|
* @param saltLength the length the salt string should have
|
||||||
|
* @return the generated salt as byte[]
|
||||||
|
*
|
||||||
|
* @throws SecurityException if the salt creation fails
|
||||||
|
*/
|
||||||
|
private static byte[] createSalt(int saltLength) throws SecurityException {
|
||||||
|
try {
|
||||||
|
SecureRandom sha1SecureRandom = SecureRandom.getInstance("SHA1PRNG");
|
||||||
|
|
||||||
|
byte salt[] = new byte[saltLength];
|
||||||
|
synchronized (sha1SecureRandom) {
|
||||||
|
sha1SecureRandom.nextBytes(salt);
|
||||||
|
}
|
||||||
|
return salt;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new SecurityException("Cannot created salt", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hashes the given password (md5 hashed, base64 coded) with the given salt
|
||||||
|
*
|
||||||
|
* @param text the text to salt
|
||||||
|
* @param salt the salt to use
|
||||||
|
* @return the input text salted with password
|
||||||
|
*
|
||||||
|
* @throws SecurityException
|
||||||
|
*/
|
||||||
|
private static byte[] hashPasswordWithSalt(byte text[], byte salt[]) throws SecurityException {
|
||||||
|
try {
|
||||||
|
MessageDigest sha1Algorithm = MessageDigest.getInstance("SHA-1");
|
||||||
|
byte[] digest;
|
||||||
|
synchronized (sha1Algorithm) {
|
||||||
|
sha1Algorithm.reset();
|
||||||
|
sha1Algorithm.update(salt);
|
||||||
|
digest = sha1Algorithm.digest(text);
|
||||||
|
}
|
||||||
|
return digest;
|
||||||
|
} catch (NoSuchAlgorithmException ex) {
|
||||||
|
throw new SecurityException("Cannot hash password with salt", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the given password as md5 without appliying salt
|
||||||
|
*
|
||||||
|
* @param plainTextPassword the password to convert
|
||||||
|
* @return the given password as md5 without appliying salt
|
||||||
|
*
|
||||||
|
* @throws SecurityException if the passwor cannot be converted
|
||||||
|
*/
|
||||||
|
private static byte[] getBase64MD5HashedPassword(final String plainTextPassword) throws SecurityException {
|
||||||
|
try {
|
||||||
|
MessageDigest algorithm = MessageDigest.getInstance("MD5");
|
||||||
|
algorithm.reset();
|
||||||
|
algorithm.update(plainTextPassword.getBytes());
|
||||||
|
byte[] messageDigest = algorithm.digest();
|
||||||
|
StringBuilder buf = new StringBuilder();
|
||||||
|
for (int i = 0; i < messageDigest.length; i++) {
|
||||||
|
int halfbyte = (messageDigest[i] >>> 4) & 0x0F;
|
||||||
|
int twoHalfs = 0;
|
||||||
|
do {
|
||||||
|
if ((0 <= halfbyte) && (halfbyte <= 9)) {
|
||||||
|
buf.append((char) ('0' + halfbyte));
|
||||||
|
} else {
|
||||||
|
buf.append((char) ('a' + (halfbyte - 10)));
|
||||||
|
}
|
||||||
|
halfbyte = messageDigest[i] & 0x0F;
|
||||||
|
} while (twoHalfs++ < 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// take password and hash with salt
|
||||||
|
byte[] unHashedPassword = base64Decode(buf.toString());
|
||||||
|
|
||||||
|
return unHashedPassword;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new SecurityException("Cannot created password", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the plain byte[] as base64 coded string
|
||||||
|
*
|
||||||
|
* @param data the data to convert
|
||||||
|
* @return the plain byte[] as base64 coded string
|
||||||
|
*/
|
||||||
|
private static String base64Encode(final byte[] data) {
|
||||||
|
Base64 encoder = new Base64();
|
||||||
|
byte[] result = encoder.encode(data);
|
||||||
|
return new String(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the given base64 coded string as decoded byte[]
|
||||||
|
*
|
||||||
|
* @param data the string to convert
|
||||||
|
* @return the given base64 coded string as decoded byte[]
|
||||||
|
*/
|
||||||
|
private static byte[] base64Decode(final String data) {
|
||||||
|
Base64 decoder = new Base64();
|
||||||
|
return decoder.decode(data.getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the crypted parameter string for the given plain text password
|
||||||
|
*
|
||||||
|
* @param plainPassword the plain text password to crypt
|
||||||
|
* @return the crypted password string
|
||||||
|
*/
|
||||||
|
public static String getScryptHash(String plainPassword) {
|
||||||
|
return scrypt(plainPassword, scryptCpuCostParameter, scryptMemCostParameter, scryptParallelizationParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns true, if the given plainPassword re-encrypted matches the given crypted password
|
||||||
|
*
|
||||||
|
* @param plainPassword the plain password to validate
|
||||||
|
* @param hashedPassword the encrypted password to validate against
|
||||||
|
* @return true, if the encrypted string of the given plain password matches the provided crypted password
|
||||||
|
*/
|
||||||
|
public static boolean validateScryptHash(String plainPassword, String hashedPassword) {
|
||||||
|
return check(plainPassword, hashedPassword);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,237 +1,69 @@
|
|||||||
package de.muehlencord.shared.security;
|
package de.muehlencord.shared.security;
|
||||||
|
|
||||||
import static com.lambdaworks.crypto.SCryptUtil.check;
|
|
||||||
import static com.lambdaworks.crypto.SCryptUtil.scrypt;
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.Arrays;
|
import org.bouncycastle.crypto.generators.SCrypt;
|
||||||
import org.apache.commons.codec.binary.Base64;
|
import org.bouncycastle.util.encoders.Base64;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* TODO: migrate to shared library
|
||||||
*
|
*
|
||||||
* @author joern@muehlencord.de
|
* @author joern.muehlencord
|
||||||
*/
|
*/
|
||||||
public abstract class PasswordUtil {
|
public class PasswordUtil {
|
||||||
|
|
||||||
/** logging object */
|
private final static SecureRandom SECURERANDOM = new SecureRandom();
|
||||||
// private final static Logger LOGGER = Logger.getLogger(PasswordUtil.class);
|
|
||||||
|
|
||||||
/** SCrypt CPU cost parameter */
|
private final static int CPU_MEMORY_COST_PARAMETER = 16384;
|
||||||
private final static int scryptCpuCostParameter = 16384;
|
private final static int BLOCK_SIZE = 8;
|
||||||
/** SCrypt memory cost parameter */
|
private final static int PARALLELIZATION = 1;
|
||||||
private final static int scryptMemCostParameter = 8;
|
private final static int KEY_LENGTH = 32;
|
||||||
/** SCrypt paralelization parameter */
|
|
||||||
private final static int scryptParallelizationParameter = 1;
|
|
||||||
|
|
||||||
|
private final String SYSTEMSALT;
|
||||||
|
|
||||||
/**
|
public PasswordUtil(String systemSaltBase64Coded) {
|
||||||
* returns password (pos 0) and the salt (pos 1) of given plaintext password. Both strings are base64 encoded
|
// TODO make some tests like lengths etc
|
||||||
*
|
this.SYSTEMSALT = systemSaltBase64Coded;
|
||||||
* @param plainTextPassword he
|
}
|
||||||
* @param saltLength the length of the salt to use
|
|
||||||
* @return the password (pos 0) and the salt (pos 1) of given plaintext password. Both strings are base64 encoded
|
|
||||||
*
|
|
||||||
* @throws de.muehlencord.shared.security.SecurityException if any error occurs during the password generation
|
|
||||||
*/
|
|
||||||
public static String[] getMD5Password(final String plainTextPassword, final int saltLength) throws SecurityException {
|
|
||||||
byte[] unHashedPassword = getBase64MD5HashedPassword(plainTextPassword);
|
|
||||||
byte[] salt = createSalt(saltLength);
|
|
||||||
byte[] hashedPassword = hashPasswordWithSalt(unHashedPassword, salt);
|
|
||||||
|
|
||||||
// test
|
public String getHash(String clearPassword) {
|
||||||
String saltStr = base64Encode(salt);
|
|
||||||
byte[] salt2 = base64Decode(saltStr);
|
// generate user salt
|
||||||
if (!Arrays.equals(salt, salt2)) {
|
byte[] userSaltBytes = new byte[32];
|
||||||
throw new SecurityException("Salt conversion failed");
|
SECURERANDOM.nextBytes(userSaltBytes);
|
||||||
|
String userSalt = new String(Base64.encode(userSaltBytes));
|
||||||
|
|
||||||
|
// create passwordhash with salt
|
||||||
|
String passwordHash = getPasswordHash(SYSTEMSALT, userSalt, clearPassword);
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(userSalt);
|
||||||
|
sb.append(":");
|
||||||
|
sb.append(passwordHash);
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matches(String clearPassword, String passwordHashWithSalt) {
|
||||||
|
if (!passwordHashWithSalt.contains(":")) {
|
||||||
|
// TODO add exception handling
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String userSalt = passwordHashWithSalt.substring(0, passwordHashWithSalt.indexOf(":"));
|
||||||
|
String passwordHash = passwordHashWithSalt.substring(passwordHashWithSalt.indexOf(":")+1);
|
||||||
|
|
||||||
String[] returnValue = new String[2];
|
String validationHash = getPasswordHash(SYSTEMSALT, userSalt, clearPassword);
|
||||||
returnValue[0] = base64Encode(hashedPassword);
|
return validationHash.equals(passwordHash);
|
||||||
returnValue[1] = base64Encode(salt);
|
|
||||||
|
|
||||||
return returnValue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private String getPasswordHash(String systemSaltBase64, String userSaltBase64, String clearPassword) {
|
||||||
* Checks if the given password (plain text) matches the given crypted password. The crypted password is hashed with the given salt. Both strings, crypted
|
byte[] systemSalt = systemSaltBase64.getBytes();
|
||||||
* password and salt, have to be base64 encoded
|
byte[] userSalt = userSaltBase64.getBytes();
|
||||||
*
|
byte[] salt = new byte[systemSalt.length + userSalt.length];
|
||||||
* @param plainTextPassword the plaintext password to compare to
|
|
||||||
* @param cryptedPasswordStr the crypted password to compare to
|
|
||||||
* @param saltStr the salt needed to hash the plaintext password with to get the correct crypted password if both passwords match
|
|
||||||
* @return true, if and only if the encryption of plainTextPassword (hashed with saltStr) equals to cryptedPasswordStr
|
|
||||||
*
|
|
||||||
* @throws de.muehlencord.shared.security.SecurityException if any error occures during the check
|
|
||||||
*/
|
|
||||||
public static boolean checkPassword(String plainTextPassword, String cryptedPasswordStr, String saltStr) throws SecurityException {
|
|
||||||
byte[] salt = base64Decode(saltStr);
|
|
||||||
byte[] newPassword = getBase64MD5HashedPassword(plainTextPassword);
|
|
||||||
byte[] newHashedPassword = hashPasswordWithSalt(newPassword, salt);
|
|
||||||
byte[] crytepdPassword = base64Decode(cryptedPasswordStr);
|
|
||||||
return Arrays.equals(crytepdPassword, newHashedPassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
System.arraycopy(systemSalt, 0, salt, 0, systemSalt.length);
|
||||||
* returns a new salt as a string
|
System.arraycopy(userSalt, 0, salt, systemSalt.length, userSalt.length);
|
||||||
*
|
|
||||||
* @param saltLength the length of the salt
|
|
||||||
* @return a new salt as a string (base64 encoded)
|
|
||||||
*
|
|
||||||
* @throws SecurityException if the creation of the salt fails
|
|
||||||
*/
|
|
||||||
public static String createSaltString(int saltLength) throws SecurityException {
|
|
||||||
byte[] saltByteArray = createSalt(saltLength);
|
|
||||||
String saltString = base64Encode(saltByteArray);
|
|
||||||
if (saltString.length() > saltLength) {
|
|
||||||
return saltString.substring(0, saltLength);
|
|
||||||
} else {
|
|
||||||
return saltString;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** returns a random string with total length starting with prefix string
|
return new String(Base64.encode(SCrypt.generate(clearPassword.getBytes(), salt, CPU_MEMORY_COST_PARAMETER, BLOCK_SIZE, PARALLELIZATION, KEY_LENGTH)));
|
||||||
*
|
|
||||||
* @param prefix the prefix to start the string with
|
|
||||||
* @param length the maximum length of the string (including prefix)
|
|
||||||
* @return a random string
|
|
||||||
*
|
|
||||||
* @throws SecurityException if the random string could not be computed
|
|
||||||
*/
|
|
||||||
public static String getRandomString(final String prefix, int length) throws SecurityException {
|
|
||||||
String usedPrefix = (prefix == null ? "" : prefix);
|
|
||||||
|
|
||||||
int idLength = length - usedPrefix.length();
|
|
||||||
return usedPrefix + createSaltString(idLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* *** private methods *** */
|
|
||||||
/**
|
|
||||||
* creates a salt and returns the value as byte[]
|
|
||||||
*
|
|
||||||
* @param saltLength the length the salt string should have
|
|
||||||
* @return the generated salt as byte[]
|
|
||||||
*
|
|
||||||
* @throws SecurityException if the salt creation fails
|
|
||||||
*/
|
|
||||||
private static byte[] createSalt(int saltLength) throws SecurityException {
|
|
||||||
try {
|
|
||||||
SecureRandom sha1SecureRandom = SecureRandom.getInstance("SHA1PRNG");
|
|
||||||
|
|
||||||
byte salt[] = new byte[saltLength];
|
|
||||||
synchronized (sha1SecureRandom) {
|
|
||||||
sha1SecureRandom.nextBytes(salt);
|
|
||||||
}
|
|
||||||
return salt;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
throw new SecurityException("Cannot created salt", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* hashes the given password (md5 hashed, base64 coded) with the given salt
|
|
||||||
*
|
|
||||||
* @param text the text to salt
|
|
||||||
* @param salt the salt to use
|
|
||||||
* @return the input text salted with password
|
|
||||||
*
|
|
||||||
* @throws SecurityException
|
|
||||||
*/
|
|
||||||
private static byte[] hashPasswordWithSalt(byte text[], byte salt[]) throws SecurityException {
|
|
||||||
try {
|
|
||||||
MessageDigest sha1Algorithm = MessageDigest.getInstance("SHA-1");
|
|
||||||
byte[] digest;
|
|
||||||
synchronized (sha1Algorithm) {
|
|
||||||
sha1Algorithm.reset();
|
|
||||||
sha1Algorithm.update(salt);
|
|
||||||
digest = sha1Algorithm.digest(text);
|
|
||||||
}
|
|
||||||
return digest;
|
|
||||||
} catch (NoSuchAlgorithmException ex) {
|
|
||||||
throw new SecurityException("Cannot hash password with salt", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns the given password as md5 without appliying salt
|
|
||||||
*
|
|
||||||
* @param plainTextPassword the password to convert
|
|
||||||
* @return the given password as md5 without appliying salt
|
|
||||||
*
|
|
||||||
* @throws SecurityException if the passwor cannot be converted
|
|
||||||
*/
|
|
||||||
private static byte[] getBase64MD5HashedPassword(final String plainTextPassword) throws SecurityException {
|
|
||||||
try {
|
|
||||||
MessageDigest algorithm = MessageDigest.getInstance("MD5");
|
|
||||||
algorithm.reset();
|
|
||||||
algorithm.update(plainTextPassword.getBytes());
|
|
||||||
byte[] messageDigest = algorithm.digest();
|
|
||||||
StringBuilder buf = new StringBuilder();
|
|
||||||
for (int i = 0; i < messageDigest.length; i++) {
|
|
||||||
int halfbyte = (messageDigest[i] >>> 4) & 0x0F;
|
|
||||||
int twoHalfs = 0;
|
|
||||||
do {
|
|
||||||
if ((0 <= halfbyte) && (halfbyte <= 9)) {
|
|
||||||
buf.append((char) ('0' + halfbyte));
|
|
||||||
} else {
|
|
||||||
buf.append((char) ('a' + (halfbyte - 10)));
|
|
||||||
}
|
|
||||||
halfbyte = messageDigest[i] & 0x0F;
|
|
||||||
} while (twoHalfs++ < 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// take password and hash with salt
|
|
||||||
byte[] unHashedPassword = base64Decode(buf.toString());
|
|
||||||
|
|
||||||
return unHashedPassword;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
throw new SecurityException("Cannot created password", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns the plain byte[] as base64 coded string
|
|
||||||
*
|
|
||||||
* @param data the data to convert
|
|
||||||
* @return the plain byte[] as base64 coded string
|
|
||||||
*/
|
|
||||||
private static String base64Encode(final byte[] data) {
|
|
||||||
Base64 encoder = new Base64();
|
|
||||||
byte[] result = encoder.encode(data);
|
|
||||||
return new String(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns the given base64 coded string as decoded byte[]
|
|
||||||
*
|
|
||||||
* @param data the string to convert
|
|
||||||
* @return the given base64 coded string as decoded byte[]
|
|
||||||
*/
|
|
||||||
private static byte[] base64Decode(final String data) {
|
|
||||||
Base64 decoder = new Base64();
|
|
||||||
return decoder.decode(data.getBytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns the crypted parameter string for the given plain text password
|
|
||||||
*
|
|
||||||
* @param plainPassword the plain text password to crypt
|
|
||||||
* @return the crypted password string
|
|
||||||
*/
|
|
||||||
public static String getScryptHash(String plainPassword) {
|
|
||||||
return scrypt(plainPassword, scryptCpuCostParameter, scryptMemCostParameter, scryptParallelizationParameter);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns true, if the given plainPassword re-encrypted matches the given crypted password
|
|
||||||
*
|
|
||||||
* @param plainPassword the plain password to validate
|
|
||||||
* @param hashedPassword the encrypted password to validate against
|
|
||||||
* @return true, if the encrypted string of the given plain password matches the provided crypted password
|
|
||||||
*/
|
|
||||||
public static boolean validateScryptHash(String plainPassword, String hashedPassword) {
|
|
||||||
return check(plainPassword, hashedPassword);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,154 @@
|
|||||||
|
package de.muehlencord.shared.security;
|
||||||
|
|
||||||
|
import static de.muehlencord.shared.security.OldPasswordUtil.getScryptHash;
|
||||||
|
import static de.muehlencord.shared.security.OldPasswordUtil.validateScryptHash;
|
||||||
|
import org.junit.Test;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author jomu
|
||||||
|
*/
|
||||||
|
public class OldPasswordUtilTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test of createSaltString method, of class PasswordUtil.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void createSaltString() throws Exception {
|
||||||
|
System.out.println("createSaltString");
|
||||||
|
int saltLength = 40;
|
||||||
|
String result = OldPasswordUtil.createSaltString(saltLength);
|
||||||
|
assertNotNull(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test of getMD5Password method, of class PasswordUtil.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void getMD5Password() throws Exception {
|
||||||
|
System.out.println("getMD5Password");
|
||||||
|
String plainTextPassword = "";
|
||||||
|
int saltLength = 40;
|
||||||
|
String[] result1 = OldPasswordUtil.getMD5Password(plainTextPassword, saltLength);
|
||||||
|
String password1 = result1[0];
|
||||||
|
String salt1 = result1[1];
|
||||||
|
assertNotNull(result1);
|
||||||
|
assertNotNull(password1);
|
||||||
|
assertNotNull(salt1);
|
||||||
|
|
||||||
|
String[] result2 = OldPasswordUtil.getMD5Password(plainTextPassword, saltLength);
|
||||||
|
String password2 = result2[0];
|
||||||
|
String salt2 = result2[1];
|
||||||
|
assertNotNull(result2);
|
||||||
|
assertNotNull(password2);
|
||||||
|
assertNotNull(salt2);
|
||||||
|
|
||||||
|
assertNotSame(result1, result2);
|
||||||
|
assertNotSame(password1, password2);
|
||||||
|
assertNotSame(salt1, salt2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test of checkPassword method, of class PasswordUtil.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void checkPassword() throws Exception {
|
||||||
|
System.out.println("checkPassword");
|
||||||
|
String plainTextPassword = "welcome";
|
||||||
|
String plainTextPassword2 = "this is not the correct password";
|
||||||
|
|
||||||
|
String[] data = OldPasswordUtil.getMD5Password(plainTextPassword, 40);
|
||||||
|
String cryptedPassword = data[0];
|
||||||
|
String salt = data[1];
|
||||||
|
|
||||||
|
String salt2 = OldPasswordUtil.createSaltString(40);
|
||||||
|
String salt3 = OldPasswordUtil.createSaltString(10);
|
||||||
|
|
||||||
|
boolean expResult = true;
|
||||||
|
boolean result = OldPasswordUtil.checkPassword(plainTextPassword, cryptedPassword, salt);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
|
||||||
|
expResult = false;
|
||||||
|
result = OldPasswordUtil.checkPassword(plainTextPassword2, cryptedPassword, salt);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
|
||||||
|
expResult = false;
|
||||||
|
result = OldPasswordUtil.checkPassword(plainTextPassword, cryptedPassword, salt2);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
|
||||||
|
expResult = false;
|
||||||
|
result = OldPasswordUtil.checkPassword(plainTextPassword, cryptedPassword, salt3);
|
||||||
|
assertEquals(expResult, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getRandomString() throws SecurityException {
|
||||||
|
System.out.println ("getRandomString");
|
||||||
|
String randomString = OldPasswordUtil.getRandomString("test-", 32);
|
||||||
|
System.out.println(randomString);
|
||||||
|
assertNotNull(randomString);
|
||||||
|
assertTrue("string must start with prefix", randomString.startsWith("test"));
|
||||||
|
assertEquals("string length check", 32, randomString.length());
|
||||||
|
|
||||||
|
String randomString2 = OldPasswordUtil.getRandomString("test-", 32);
|
||||||
|
System.out.println(randomString2);
|
||||||
|
assertNotNull(randomString2);
|
||||||
|
assertTrue("string must start with prefix", randomString2.startsWith("test"));
|
||||||
|
assertEquals("string length check", 32, randomString2.length());
|
||||||
|
|
||||||
|
assertNotSame(randomString, randomString2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getRandomStringBlankPrefix() throws SecurityException {
|
||||||
|
System.out.println ("getRandomStringBlankPrefix");
|
||||||
|
String randomString = OldPasswordUtil.getRandomString("", 32);
|
||||||
|
System.out.println(randomString);
|
||||||
|
assertNotNull(randomString);
|
||||||
|
assertEquals("string length check", 32, randomString.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getRandomStringNullPrefix() throws SecurityException {
|
||||||
|
System.out.println ("getRandomStringNullPrefix");
|
||||||
|
String randomString = OldPasswordUtil.getRandomString(null, 32);
|
||||||
|
System.out.println(randomString);
|
||||||
|
assertNotNull(randomString);
|
||||||
|
assertEquals("string length check", 32, randomString.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* test the hashPassword method
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testGetScryptHash() {
|
||||||
|
String hash1 = getScryptHash("secret");
|
||||||
|
String hash2 = getScryptHash("secret");
|
||||||
|
System.out.println (hash1);
|
||||||
|
System.out.println (hash2);
|
||||||
|
assertNotNull (hash1);
|
||||||
|
assertNotNull (hash2);
|
||||||
|
// even if password is the same, the has must not be the same due to correct usage of salts
|
||||||
|
assertFalse (hash1.equals (hash2));
|
||||||
|
|
||||||
|
assertTrue (hash1.length() == 79);
|
||||||
|
assertTrue (hash2.length() == 79);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* test for validating passwords
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testValidateScryptHash() {
|
||||||
|
String hash1 = getScryptHash("secret");
|
||||||
|
String hash2 = getScryptHash("secret");
|
||||||
|
assertTrue ("hash must match if correct password is given",validateScryptHash("secret", hash1));
|
||||||
|
assertTrue ("hash must match if correct password is given", validateScryptHash("secret", hash2));
|
||||||
|
assertFalse ("hash must not match if wrong password is given", validateScryptHash("secret2", hash1));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,154 +1,45 @@
|
|||||||
package de.muehlencord.shared.security;
|
package de.muehlencord.shared.security;
|
||||||
|
|
||||||
import static de.muehlencord.shared.security.PasswordUtil.getScryptHash;
|
import de.muehlencord.shared.security.PasswordUtil;
|
||||||
import static de.muehlencord.shared.security.PasswordUtil.validateScryptHash;
|
import java.security.SecureRandom;
|
||||||
|
import org.bouncycastle.util.encoders.Base64;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import static org.junit.Assert.*;
|
import org.junit.BeforeClass;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author jomu
|
* @author joern.muehlencord
|
||||||
*/
|
*/
|
||||||
public class PasswordUtilTest {
|
public class PasswordUtilTest {
|
||||||
|
|
||||||
/**
|
private static SecureRandom secureRandom;
|
||||||
* Test of createSaltString method, of class PasswordUtil.
|
private static String systemSalt64Coded;
|
||||||
*/
|
private static byte[] systemSaltBytes;
|
||||||
@Test
|
|
||||||
public void createSaltString() throws Exception {
|
|
||||||
System.out.println("createSaltString");
|
|
||||||
int saltLength = 40;
|
|
||||||
String result = PasswordUtil.createSaltString(saltLength);
|
|
||||||
assertNotNull(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
@BeforeClass
|
||||||
* Test of getMD5Password method, of class PasswordUtil.
|
public static void init() {
|
||||||
*/
|
secureRandom = new SecureRandom();
|
||||||
@Test
|
|
||||||
public void getMD5Password() throws Exception {
|
|
||||||
System.out.println("getMD5Password");
|
|
||||||
String plainTextPassword = "";
|
|
||||||
int saltLength = 40;
|
|
||||||
String[] result1 = PasswordUtil.getMD5Password(plainTextPassword, saltLength);
|
|
||||||
String password1 = result1[0];
|
|
||||||
String salt1 = result1[1];
|
|
||||||
assertNotNull(result1);
|
|
||||||
assertNotNull(password1);
|
|
||||||
assertNotNull(salt1);
|
|
||||||
|
|
||||||
String[] result2 = PasswordUtil.getMD5Password(plainTextPassword, saltLength);
|
systemSaltBytes = new byte[32];
|
||||||
String password2 = result2[0];
|
secureRandom.nextBytes (systemSaltBytes);
|
||||||
String salt2 = result2[1];
|
systemSalt64Coded = new String(Base64.encode (systemSaltBytes));
|
||||||
assertNotNull(result2);
|
|
||||||
assertNotNull(password2);
|
|
||||||
assertNotNull(salt2);
|
|
||||||
|
|
||||||
assertNotSame(result1, result2);
|
|
||||||
assertNotSame(password1, password2);
|
|
||||||
assertNotSame(salt1, salt2);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test of checkPassword method, of class PasswordUtil.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void checkPassword() throws Exception {
|
|
||||||
System.out.println("checkPassword");
|
|
||||||
String plainTextPassword = "welcome";
|
|
||||||
String plainTextPassword2 = "this is not the correct password";
|
|
||||||
|
|
||||||
String[] data = PasswordUtil.getMD5Password(plainTextPassword, 40);
|
|
||||||
String cryptedPassword = data[0];
|
|
||||||
String salt = data[1];
|
|
||||||
|
|
||||||
String salt2 = PasswordUtil.createSaltString(40);
|
|
||||||
String salt3 = PasswordUtil.createSaltString(10);
|
|
||||||
|
|
||||||
boolean expResult = true;
|
|
||||||
boolean result = PasswordUtil.checkPassword(plainTextPassword, cryptedPassword, salt);
|
|
||||||
assertEquals(expResult, result);
|
|
||||||
|
|
||||||
expResult = false;
|
|
||||||
result = PasswordUtil.checkPassword(plainTextPassword2, cryptedPassword, salt);
|
|
||||||
assertEquals(expResult, result);
|
|
||||||
|
|
||||||
expResult = false;
|
|
||||||
result = PasswordUtil.checkPassword(plainTextPassword, cryptedPassword, salt2);
|
|
||||||
assertEquals(expResult, result);
|
|
||||||
|
|
||||||
expResult = false;
|
|
||||||
result = PasswordUtil.checkPassword(plainTextPassword, cryptedPassword, salt3);
|
|
||||||
assertEquals(expResult, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getRandomString() throws SecurityException {
|
|
||||||
System.out.println ("getRandomString");
|
|
||||||
String randomString = PasswordUtil.getRandomString("test-", 32);
|
|
||||||
System.out.println(randomString);
|
|
||||||
assertNotNull(randomString);
|
|
||||||
assertTrue("string must start with prefix", randomString.startsWith("test"));
|
|
||||||
assertEquals("string length check", 32, randomString.length());
|
|
||||||
|
|
||||||
String randomString2 = PasswordUtil.getRandomString("test-", 32);
|
|
||||||
System.out.println(randomString2);
|
|
||||||
assertNotNull(randomString2);
|
|
||||||
assertTrue("string must start with prefix", randomString2.startsWith("test"));
|
|
||||||
assertEquals("string length check", 32, randomString2.length());
|
|
||||||
|
|
||||||
assertNotSame(randomString, randomString2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getRandomStringBlankPrefix() throws SecurityException {
|
|
||||||
System.out.println ("getRandomStringBlankPrefix");
|
|
||||||
String randomString = PasswordUtil.getRandomString("", 32);
|
|
||||||
System.out.println(randomString);
|
|
||||||
assertNotNull(randomString);
|
|
||||||
assertEquals("string length check", 32, randomString.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getRandomStringNullPrefix() throws SecurityException {
|
public void testGetHash() {
|
||||||
System.out.println ("getRandomStringNullPrefix");
|
PasswordUtil pwUtil = new PasswordUtil(systemSalt64Coded);
|
||||||
String randomString = PasswordUtil.getRandomString(null, 32);
|
|
||||||
System.out.println(randomString);
|
|
||||||
assertNotNull(randomString);
|
|
||||||
assertEquals("string length check", 32, randomString.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
String password1 = pwUtil.getHash("password");
|
||||||
* test the hashPassword method
|
String password2 = pwUtil.getHash("password");
|
||||||
*/
|
|
||||||
@Test
|
assertFalse (password1.equals(password2));
|
||||||
public void testGetScryptHash() {
|
assertTrue (pwUtil.matches ("password", password1));
|
||||||
String hash1 = getScryptHash("secret");
|
assertFalse (pwUtil.matches ("wrongpassword", password1));
|
||||||
String hash2 = getScryptHash("secret");
|
|
||||||
System.out.println (hash1);
|
|
||||||
System.out.println (hash2);
|
|
||||||
assertNotNull (hash1);
|
|
||||||
assertNotNull (hash2);
|
|
||||||
// even if password is the same, the has must not be the same due to correct usage of salts
|
|
||||||
assertFalse (hash1.equals (hash2));
|
|
||||||
|
|
||||||
assertTrue (hash1.length() == 79);
|
|
||||||
assertTrue (hash2.length() == 79);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* test for validating passwords
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testValidateScryptHash() {
|
|
||||||
String hash1 = getScryptHash("secret");
|
|
||||||
String hash2 = getScryptHash("secret");
|
|
||||||
assertTrue ("hash must match if correct password is given",validateScryptHash("secret", hash1));
|
|
||||||
assertTrue ("hash must match if correct password is given", validateScryptHash("secret", hash2));
|
|
||||||
assertFalse ("hash must not match if wrong password is given", validateScryptHash("secret2", hash1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user