From 6a52609f36a6a031ddfc9574e2a77c2f32097b76 Mon Sep 17 00:00:00 2001 From: jomu Date: Wed, 19 Aug 2015 13:51:24 +0000 Subject: [PATCH] created new PasswordUtil class based on BouncyCastle classes --- pom.xml | 33 +- security/pom.xml | 8 +- .../shared/security/OldPasswordUtil.java | 237 ++++++++++++++ .../shared/security/PasswordUtil.java | 306 ++++-------------- .../shared/security/OldPasswordUtilTest.java | 154 +++++++++ .../shared/security/PasswordUtilTest.java | 199 +++--------- 6 files changed, 528 insertions(+), 409 deletions(-) create mode 100644 security/src/main/java/de/muehlencord/shared/security/OldPasswordUtil.java create mode 100644 security/src/test/java/de/muehlencord/shared/security/OldPasswordUtilTest.java diff --git a/pom.xml b/pom.xml index 4d1a8ee..059c43e 100644 --- a/pom.xml +++ b/pom.xml @@ -28,14 +28,13 @@ commons-codec commons-codec 1.6 - - + - com.lambdaworks - scrypt - 1.4.0 - - + commons-net + commons-net + 3.3 + + org.apache.logging.log4j log4j-api @@ -58,20 +57,24 @@ javaee-api 6.0 - - + com.enterprisedt edtFTPj 1.5.3 - + - commons-net - commons-net - 3.3 - - + com.lambdaworks + scrypt + 1.4.0 + + + org.bouncycastle + bcprov-jdk15on + 1.52 + + de.muehlencord.shared shared-util diff --git a/security/pom.xml b/security/pom.xml index 73f5997..c23b5a7 100644 --- a/security/pom.xml +++ b/security/pom.xml @@ -27,8 +27,7 @@ junit junit - - + commons-codec commons-codec @@ -42,7 +41,10 @@ org.apache.logging.log4j log4j-api - + + org.bouncycastle + bcprov-jdk15on + com.lambdaworks scrypt diff --git a/security/src/main/java/de/muehlencord/shared/security/OldPasswordUtil.java b/security/src/main/java/de/muehlencord/shared/security/OldPasswordUtil.java new file mode 100644 index 0000000..4743bfb --- /dev/null +++ b/security/src/main/java/de/muehlencord/shared/security/OldPasswordUtil.java @@ -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); + } +} diff --git a/security/src/main/java/de/muehlencord/shared/security/PasswordUtil.java b/security/src/main/java/de/muehlencord/shared/security/PasswordUtil.java index aa37795..18b4c13 100644 --- a/security/src/main/java/de/muehlencord/shared/security/PasswordUtil.java +++ b/security/src/main/java/de/muehlencord/shared/security/PasswordUtil.java @@ -1,237 +1,69 @@ -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 PasswordUtil { - - /** 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); - } -} +package de.muehlencord.shared.security; + +import java.security.SecureRandom; +import org.bouncycastle.crypto.generators.SCrypt; +import org.bouncycastle.util.encoders.Base64; + +/** + * TODO: migrate to shared library + * + * @author joern.muehlencord + */ +public class PasswordUtil { + + private final static SecureRandom SECURERANDOM = new SecureRandom(); + + private final static int CPU_MEMORY_COST_PARAMETER = 16384; + private final static int BLOCK_SIZE = 8; + private final static int PARALLELIZATION = 1; + private final static int KEY_LENGTH = 32; + + private final String SYSTEMSALT; + + public PasswordUtil(String systemSaltBase64Coded) { + // TODO make some tests like lengths etc + this.SYSTEMSALT = systemSaltBase64Coded; + } + + public String getHash(String clearPassword) { + + // generate user salt + byte[] userSaltBytes = new byte[32]; + 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 validationHash = getPasswordHash(SYSTEMSALT, userSalt, clearPassword); + return validationHash.equals(passwordHash); + } + + private String getPasswordHash(String systemSaltBase64, String userSaltBase64, String clearPassword) { + byte[] systemSalt = systemSaltBase64.getBytes(); + byte[] userSalt = userSaltBase64.getBytes(); + byte[] salt = new byte[systemSalt.length + userSalt.length]; + + System.arraycopy(systemSalt, 0, salt, 0, systemSalt.length); + System.arraycopy(userSalt, 0, salt, systemSalt.length, userSalt.length); + + return new String(Base64.encode(SCrypt.generate(clearPassword.getBytes(), salt, CPU_MEMORY_COST_PARAMETER, BLOCK_SIZE, PARALLELIZATION, KEY_LENGTH))); + } +} diff --git a/security/src/test/java/de/muehlencord/shared/security/OldPasswordUtilTest.java b/security/src/test/java/de/muehlencord/shared/security/OldPasswordUtilTest.java new file mode 100644 index 0000000..4fd8128 --- /dev/null +++ b/security/src/test/java/de/muehlencord/shared/security/OldPasswordUtilTest.java @@ -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)); + } + +} \ No newline at end of file diff --git a/security/src/test/java/de/muehlencord/shared/security/PasswordUtilTest.java b/security/src/test/java/de/muehlencord/shared/security/PasswordUtilTest.java index 3e097f3..d4dbc79 100644 --- a/security/src/test/java/de/muehlencord/shared/security/PasswordUtilTest.java +++ b/security/src/test/java/de/muehlencord/shared/security/PasswordUtilTest.java @@ -1,154 +1,45 @@ -package de.muehlencord.shared.security; - -import static de.muehlencord.shared.security.PasswordUtil.getScryptHash; -import static de.muehlencord.shared.security.PasswordUtil.validateScryptHash; -import org.junit.Test; -import static org.junit.Assert.*; - -/** - * - * @author jomu - */ -public class PasswordUtilTest { - - /** - * Test of createSaltString method, of class PasswordUtil. - */ - @Test - public void createSaltString() throws Exception { - System.out.println("createSaltString"); - int saltLength = 40; - String result = PasswordUtil.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 = PasswordUtil.getMD5Password(plainTextPassword, saltLength); - String password1 = result1[0]; - String salt1 = result1[1]; - assertNotNull(result1); - assertNotNull(password1); - assertNotNull(salt1); - - String[] result2 = PasswordUtil.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 = 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 - public void getRandomStringNullPrefix() throws SecurityException { - System.out.println ("getRandomStringNullPrefix"); - String randomString = PasswordUtil.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)); - } - -} \ No newline at end of file +package de.muehlencord.shared.security; + +import de.muehlencord.shared.security.PasswordUtil; +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.BeforeClass; + +/** + * + * @author joern.muehlencord + */ +public class PasswordUtilTest { + + private static SecureRandom secureRandom; + private static String systemSalt64Coded; + private static byte[] systemSaltBytes; + + @BeforeClass + public static void init() { + secureRandom = new SecureRandom(); + + systemSaltBytes = new byte[32]; + secureRandom.nextBytes (systemSaltBytes); + systemSalt64Coded = new String(Base64.encode (systemSaltBytes)); + } + + + @Test + public void testGetHash() { + PasswordUtil pwUtil = new PasswordUtil(systemSalt64Coded); + + String password1 = pwUtil.getHash("password"); + String password2 = pwUtil.getHash("password"); + + assertFalse (password1.equals(password2)); + assertTrue (pwUtil.matches ("password", password1)); + assertFalse (pwUtil.matches ("wrongpassword", password1)); + + + } + +}