From 16d309e1e5483b5083671b550f33394604c475cf Mon Sep 17 00:00:00 2001 From: binsky Date: Sat, 5 Feb 2022 16:56:55 +0100 Subject: [PATCH 1/5] implement simple http api to make get requests --- src/main/java/HttpApi.java | 27 +++++++++++++++++++++++++++ src/test/java/HttpApiTest.java | 19 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 src/main/java/HttpApi.java create mode 100644 src/test/java/HttpApiTest.java diff --git a/src/main/java/HttpApi.java b/src/main/java/HttpApi.java new file mode 100644 index 0000000..1e3beef --- /dev/null +++ b/src/main/java/HttpApi.java @@ -0,0 +1,27 @@ +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); + } in .close(); + + return response.toString(); + } + return null; + } +} 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(); + } + } +} From 1d591771f4b709178e062080237531704c6d4655 Mon Sep 17 00:00:00 2001 From: binsky Date: Sat, 5 Feb 2022 17:12:55 +0100 Subject: [PATCH 2/5] implement sha1 hash generation --- src/main/java/PasswordValidator.java | 29 ++++++++++++++++++++++++ src/test/java/PasswordValidatorTest.java | 7 ++++++ 2 files changed, 36 insertions(+) diff --git a/src/main/java/PasswordValidator.java b/src/main/java/PasswordValidator.java index 9cb4f03..cddd65f 100644 --- a/src/main/java/PasswordValidator.java +++ b/src/main/java/PasswordValidator.java @@ -1,3 +1,6 @@ +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.regex.Pattern; public class PasswordValidator { @@ -54,4 +57,30 @@ public class PasswordValidator { public void setRequireDigit(boolean requireDigit) { this.requireDigit = requireDigit; } + + 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; + } } diff --git a/src/test/java/PasswordValidatorTest.java b/src/test/java/PasswordValidatorTest.java index 78d9335..2ffd935 100644 --- a/src/test/java/PasswordValidatorTest.java +++ b/src/test/java/PasswordValidatorTest.java @@ -47,4 +47,11 @@ class PasswordValidatorTest { assertFalse(passwordValidator.validate("ABCDEF8")); assertTrue(passwordValidator.validate("abCDE8F")); } + + @Test + void getSHA1Hash() { + assertEquals("356A192B7913B04C54574D18C28D46E6395428AB".toLowerCase(), PasswordValidator.getSHA1Hash("1")); + assertEquals("A233F0E898ED0661D6D47ED0958F16B52E537231".toLowerCase(), PasswordValidator.getSHA1Hash("asdf12")); + assertNull(PasswordValidator.getSHA1Hash("")); + } } From c8c871c6c202f6766fa10175bf4b227bbcee14e3 Mon Sep 17 00:00:00 2001 From: binsky Date: Sat, 5 Feb 2022 17:35:53 +0100 Subject: [PATCH 3/5] implement method to check if a password has been pwned --- src/main/java/HttpApi.java | 7 ++++--- src/main/java/PasswordValidator.java | 24 ++++++++++++++++++++++++ src/test/java/PasswordValidatorTest.java | 5 +++++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/main/java/HttpApi.java b/src/main/java/HttpApi.java index 1e3beef..601b696 100644 --- a/src/main/java/HttpApi.java +++ b/src/main/java/HttpApi.java @@ -16,9 +16,10 @@ public class HttpApi { String inputLine; StringBuffer response = new StringBuffer(); - while ((inputLine = in .readLine()) != null) { - response.append(inputLine); - } in .close(); + while ((inputLine = in.readLine()) != null) { + response.append(inputLine + "\n"); + } + in.close(); return response.toString(); } diff --git a/src/main/java/PasswordValidator.java b/src/main/java/PasswordValidator.java index cddd65f..49a60e4 100644 --- a/src/main/java/PasswordValidator.java +++ b/src/main/java/PasswordValidator.java @@ -1,3 +1,6 @@ +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -83,4 +86,25 @@ public class PasswordValidator { return null; } + + public static boolean isPwned(String password) { + String sha1 = PasswordValidator.getSHA1Hash(password); + if (sha1 != null) { + String url = "https://api.pwnedpasswords.com/range/" + 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) { + if (sha1.toUpperCase().endsWith(line.split(":")[0])) { + return true; + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + return false; + } } diff --git a/src/test/java/PasswordValidatorTest.java b/src/test/java/PasswordValidatorTest.java index 2ffd935..f2c5106 100644 --- a/src/test/java/PasswordValidatorTest.java +++ b/src/test/java/PasswordValidatorTest.java @@ -54,4 +54,9 @@ class PasswordValidatorTest { assertEquals("A233F0E898ED0661D6D47ED0958F16B52E537231".toLowerCase(), PasswordValidator.getSHA1Hash("asdf12")); assertNull(PasswordValidator.getSHA1Hash("")); } + + @Test + void isPwned() { + assertTrue(PasswordValidator.isPwned("asdf12")); + } } From 3ba0dd6158fa3d4e98272a2f52a61ccbe3ad5339 Mon Sep 17 00:00:00 2001 From: binsky Date: Sat, 5 Feb 2022 17:45:18 +0100 Subject: [PATCH 4/5] refactor method to check if a password has been pwned --- src/main/java/PasswordValidator.java | 6 ++++-- src/test/java/PasswordValidatorTest.java | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/PasswordValidator.java b/src/main/java/PasswordValidator.java index 49a60e4..c3bf21a 100644 --- a/src/main/java/PasswordValidator.java +++ b/src/main/java/PasswordValidator.java @@ -15,6 +15,7 @@ public class PasswordValidator { 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) { @@ -90,13 +91,14 @@ public class PasswordValidator { public static boolean isPwned(String password) { String sha1 = PasswordValidator.getSHA1Hash(password); if (sha1 != null) { - String url = "https://api.pwnedpasswords.com/range/" + sha1.substring(0, 5); + 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) { - if (sha1.toUpperCase().endsWith(line.split(":")[0])) { + String[] lineSplit = line.split(":"); + if (lineSplit.length > 0 && sha1.toUpperCase().endsWith(lineSplit[0])) { return true; } } diff --git a/src/test/java/PasswordValidatorTest.java b/src/test/java/PasswordValidatorTest.java index f2c5106..d0db88a 100644 --- a/src/test/java/PasswordValidatorTest.java +++ b/src/test/java/PasswordValidatorTest.java @@ -58,5 +58,7 @@ class PasswordValidatorTest { @Test void isPwned() { assertTrue(PasswordValidator.isPwned("asdf12")); + assertFalse(PasswordValidator.isPwned("=phan0johB4aisae6Mie0jeip9Saejahc0iuvuth7ahv9uoni6o*_.+")); + assertFalse(PasswordValidator.isPwned("")); } } From 469af0b96cffa3f5a42b2a1bb28bdc2a15028e19 Mon Sep 17 00:00:00 2001 From: binsky Date: Sat, 5 Feb 2022 17:51:31 +0100 Subject: [PATCH 5/5] implement checking pwned password in password validator --- src/main/java/PasswordValidator.java | 11 +++++++++++ src/test/java/PasswordValidatorTest.java | 13 +++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/main/java/PasswordValidator.java b/src/main/java/PasswordValidator.java index c3bf21a..800b269 100644 --- a/src/main/java/PasswordValidator.java +++ b/src/main/java/PasswordValidator.java @@ -11,6 +11,7 @@ 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]).+$"); @@ -26,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; } @@ -62,6 +65,14 @@ public class PasswordValidator { 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 { diff --git a/src/test/java/PasswordValidatorTest.java b/src/test/java/PasswordValidatorTest.java index d0db88a..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,6 +50,15 @@ 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