diff --git a/src/main/java/HttpApi.java b/src/main/java/HttpApi.java new file mode 100644 index 0000000..601b696 --- /dev/null +++ b/src/main/java/HttpApi.java @@ -0,0 +1,28 @@ +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; + +public class HttpApi { + public static String sendHttpGETRequest(String url) throws IOException { + URL obj = new URL(url); + HttpURLConnection httpURLConnection = (HttpURLConnection) obj.openConnection(); + httpURLConnection.setRequestMethod("GET"); + int responseCode = httpURLConnection.getResponseCode(); + + if (responseCode == HttpURLConnection.HTTP_OK) { + BufferedReader in = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream())); + String inputLine; + StringBuffer response = new StringBuffer(); + + while ((inputLine = in.readLine()) != null) { + response.append(inputLine + "\n"); + } + in.close(); + + return response.toString(); + } + return null; + } +} diff --git a/src/main/java/PasswordValidator.java b/src/main/java/PasswordValidator.java index 9cb4f03..800b269 100644 --- a/src/main/java/PasswordValidator.java +++ b/src/main/java/PasswordValidator.java @@ -1,3 +1,9 @@ +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.regex.Pattern; public class PasswordValidator { @@ -5,10 +11,12 @@ public class PasswordValidator { boolean requireUppercase = true; boolean requireLowercase = true; boolean requireDigit = true; + boolean checkPwned = true; private final Pattern uppercasePattern = Pattern.compile("^(?=.*[A-Z]).+$"); private final Pattern lowercasePattern = Pattern.compile("^(?=.*[a-z]).+$"); private final Pattern digitPattern = Pattern.compile("^(?=.*\\d).+$"); + private static final String pwnedPasswordsApiUrl = "https://api.pwnedpasswords.com/range/"; public boolean validate(String password) { if (password.length() < minLength) { @@ -19,6 +27,8 @@ public class PasswordValidator { return false; } else if (requireDigit && !digitPattern.matcher(password).matches()) { return false; + } else if (checkPwned && isPwned(password)) { + return false; } return true; } @@ -54,4 +64,60 @@ public class PasswordValidator { public void setRequireDigit(boolean requireDigit) { this.requireDigit = requireDigit; } + + public boolean isCheckPwned() { + return checkPwned; + } + + public void setCheckPwned(boolean checkPwned) { + this.checkPwned = checkPwned; + } + + public static String getSHA1Hash(String input) { + if (input.length() > 0) { + try { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] messageDigest = md.digest(input.getBytes()); + + // Convert byte array into signum representation + BigInteger no = new BigInteger(1, messageDigest); + + // Convert message digest into hex value + StringBuilder hashtext = new StringBuilder(); + hashtext.append(no.toString(16)); + + // Add preceding 0s to make it 32 bit + while (hashtext.length() < 32) { + hashtext.insert(0, "0"); + } + return hashtext.toString(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + } + + return null; + } + + public static boolean isPwned(String password) { + String sha1 = PasswordValidator.getSHA1Hash(password); + if (sha1 != null) { + String url = pwnedPasswordsApiUrl + sha1.substring(0, 5); + try { + String result = HttpApi.sendHttpGETRequest(url); + BufferedReader bufReader = new BufferedReader(new StringReader(result)); + String line = null; + while ((line = bufReader.readLine()) != null) { + String[] lineSplit = line.split(":"); + if (lineSplit.length > 0 && sha1.toUpperCase().endsWith(lineSplit[0])) { + return true; + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + return false; + } } diff --git a/src/test/java/HttpApiTest.java b/src/test/java/HttpApiTest.java new file mode 100644 index 0000000..56aa6da --- /dev/null +++ b/src/test/java/HttpApiTest.java @@ -0,0 +1,19 @@ +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.*; + +class HttpApiTest { + + @Test + void sendHttpGETRequest() { + assertDoesNotThrow(() -> HttpApi.sendHttpGETRequest("https://httpbin.org/get")); + try { + assertTrue(Objects.requireNonNull(HttpApi.sendHttpGETRequest("https://httpbin.org/get")).contains("args")); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/test/java/PasswordValidatorTest.java b/src/test/java/PasswordValidatorTest.java index 78d9335..ddbd653 100644 --- a/src/test/java/PasswordValidatorTest.java +++ b/src/test/java/PasswordValidatorTest.java @@ -14,6 +14,7 @@ class PasswordValidatorTest { passwordValidator.setRequireUppercase(false); passwordValidator.setRequireLowercase(false); passwordValidator.setRequireDigit(false); + passwordValidator.setCheckPwned(false); assertFalse(passwordValidator.validate("abcde")); assertTrue(passwordValidator.validate("abcdef")); assertTrue(passwordValidator.validate("abcdefg")); @@ -22,6 +23,7 @@ class PasswordValidatorTest { passwordValidator.setRequireUppercase(true); passwordValidator.setRequireLowercase(false); passwordValidator.setRequireDigit(false); + passwordValidator.setCheckPwned(false); assertFalse(passwordValidator.validate("abcdef")); assertTrue(passwordValidator.validate("abCdef")); assertTrue(passwordValidator.validate("ABCDEF")); @@ -30,6 +32,7 @@ class PasswordValidatorTest { passwordValidator.setRequireUppercase(true); passwordValidator.setRequireLowercase(true); passwordValidator.setRequireDigit(false); + passwordValidator.setCheckPwned(false); assertFalse(passwordValidator.validate("abcdef")); assertTrue(passwordValidator.validate("abCdef")); assertFalse(passwordValidator.validate("ABCDEF")); @@ -38,6 +41,7 @@ class PasswordValidatorTest { passwordValidator.setRequireUppercase(true); passwordValidator.setRequireLowercase(true); passwordValidator.setRequireDigit(true); + passwordValidator.setCheckPwned(false); assertFalse(passwordValidator.validate("8")); assertFalse(passwordValidator.validate("12345678")); assertFalse(passwordValidator.validate("abcdef")); @@ -46,5 +50,28 @@ class PasswordValidatorTest { assertFalse(passwordValidator.validate("ABCDEF")); assertFalse(passwordValidator.validate("ABCDEF8")); assertTrue(passwordValidator.validate("abCDE8F")); + + // test password pwned check + passwordValidator.setRequireUppercase(true); + passwordValidator.setRequireLowercase(true); + passwordValidator.setRequireDigit(true); + passwordValidator.setCheckPwned(true); + assertFalse(passwordValidator.validate("8")); + assertFalse(passwordValidator.validate("asdf12")); + assertTrue(passwordValidator.validate("=phan0johB4aisae6Mie0jeip9Saejahc0iuvuth7ahv9uoni6o*_.+")); + } + + @Test + void getSHA1Hash() { + assertEquals("356A192B7913B04C54574D18C28D46E6395428AB".toLowerCase(), PasswordValidator.getSHA1Hash("1")); + assertEquals("A233F0E898ED0661D6D47ED0958F16B52E537231".toLowerCase(), PasswordValidator.getSHA1Hash("asdf12")); + assertNull(PasswordValidator.getSHA1Hash("")); + } + + @Test + void isPwned() { + assertTrue(PasswordValidator.isPwned("asdf12")); + assertFalse(PasswordValidator.isPwned("=phan0johB4aisae6Mie0jeip9Saejahc0iuvuth7ahv9uoni6o*_.+")); + assertFalse(PasswordValidator.isPwned("")); } }