From 12aad793c67d565535491f1f64ba0af3f0881f34 Mon Sep 17 00:00:00 2001 From: Andrii Solianyk Date: Fri, 21 Nov 2025 19:57:57 +0100 Subject: [PATCH] bruteforce implemented --- README.md | 2 +- .../_11a/passmetric/PasswordController.java | 11 +- .../passmetric/service/BruteForceService.java | 131 ++++++++++++++++++ src/main/resources/static/css/password.css | 21 ++- src/main/resources/templates/password.html | 63 +++++++-- 5 files changed, 205 insertions(+), 23 deletions(-) create mode 100644 src/main/java/iz/_11a/passmetric/service/BruteForceService.java diff --git a/README.md b/README.md index ab5d0a5..066f7ae 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ które pomogą mu stworzyć silne i unikalne zabezpieczenie konta. ### Założenia: -- [ ] Sprawdza ile hasło będzie łamane przy pomocy metody Bruteforce (Andrii Solianyk) +- [x] Sprawdza ile hasło będzie łamane przy pomocy metody Bruteforce (Andrii Solianyk) - [x] Sprawdza czy hasło nie wyciekło (Patryk Kania) - [x] Sprawdza jego złożoność (Hubert Salwa) - [x] Proponuje zmiany hasła w celu poprawy jego złożoności (Hubert Salwa) diff --git a/src/main/java/iz/_11a/passmetric/PasswordController.java b/src/main/java/iz/_11a/passmetric/PasswordController.java index 47e33f3..d61f2c5 100644 --- a/src/main/java/iz/_11a/passmetric/PasswordController.java +++ b/src/main/java/iz/_11a/passmetric/PasswordController.java @@ -1,6 +1,7 @@ package iz._11a.passmetric; import iz._11a.passmetric.model.PasswordLeakResult; +import iz._11a.passmetric.service.BruteForceService; import iz._11a.passmetric.service.PasswordLeakService; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; @@ -10,11 +11,12 @@ import java.util.*; @Controller public class PasswordController { - private final PasswordLeakService service; + private final PasswordLeakService passwordLeakService; - public PasswordController(PasswordLeakService service) { - this.service = service; + public PasswordController(PasswordLeakService passwordLeakService) { + this.passwordLeakService = passwordLeakService; } + @GetMapping("/") public String home() { return "password"; @@ -29,12 +31,13 @@ public class PasswordController { int score = calculateScore(password); String strengthText = strengthText(score); List tips = generateTips(password); - PasswordLeakResult leakResult = service.checkLeakWithCount(password); + PasswordLeakResult leakResult = passwordLeakService.checkLeakWithCount(password); response.put("strengthText", strengthText); response.put("progress", score * 20); // pasek postępu 0–100% response.put("tips", tips); response.put("leaked", leakResult.isLeaked() ? "Hasło wyciekło " + leakResult.getCount() +" razy" : "Hasło nie występuje w wyciekach"); + response.put("timetohack", BruteForceService.estimateTimeToHackFormatted(password)); return response; } diff --git a/src/main/java/iz/_11a/passmetric/service/BruteForceService.java b/src/main/java/iz/_11a/passmetric/service/BruteForceService.java new file mode 100644 index 0000000..1de79cd --- /dev/null +++ b/src/main/java/iz/_11a/passmetric/service/BruteForceService.java @@ -0,0 +1,131 @@ +package iz._11a.passmetric.service; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +public class BruteForceService { + + private static final long ATTEMPTS_PER_SECOND = 1_000_000_000L; + + private static final int LOWERCASE_SIZE = 26; // a-z + private static final int UPPERCASE_SIZE = 26; // A-Z + private static final int DIGITS_SIZE = 10; // 0-9 + private static final int SPECIAL_CHARS_SIZE = 32; // !@#$%^&*() и т.д. + + private static final BigDecimal SECONDS_IN_MINUTE = BigDecimal.valueOf(60); + private static final BigDecimal SECONDS_IN_HOUR = SECONDS_IN_MINUTE.multiply(BigDecimal.valueOf(60)); + private static final BigDecimal SECONDS_IN_DAY = SECONDS_IN_HOUR.multiply(BigDecimal.valueOf(24)); + private static final BigDecimal SECONDS_IN_YEAR = SECONDS_IN_DAY.multiply(new BigDecimal("365.25")); + + public static String estimateTimeToHackFormatted(String password) { + int charsetSize = calculateCharsetSize(password); + if (charsetSize == 0) { + return "nie można oszacować czasu dla pustego hasła"; + } + int passwordLength = password.length(); + + BigDecimal totalCombinations = BigDecimal.valueOf(charsetSize).pow(passwordLength); + BigDecimal averageAttempts = totalCombinations.divide(BigDecimal.valueOf(2.0), RoundingMode.HALF_UP); + + return formatTime(averageAttempts.divide(BigDecimal.valueOf(ATTEMPTS_PER_SECOND), 10, RoundingMode.HALF_UP).doubleValue()); + } + + + public static String formatTime(double seconds) { + if (seconds < 1.0) { + return "mniej niż sekundę"; + } + + BigDecimal totalSeconds = BigDecimal.valueOf(seconds); + BigDecimal[] yearsAndRemainder = totalSeconds.divideAndRemainder(SECONDS_IN_YEAR); + long years = yearsAndRemainder[0].longValue(); + + if (years > 1_000_000_000L) { + return "∞ (nieskończenie długo)"; + } + + StringBuilder result = new StringBuilder(); + + if (years > 0) { + result.append(years).append(" ").append(getYearsForm(years)).append(" "); + } + + BigDecimal[] daysAndRemainder = yearsAndRemainder[1].divideAndRemainder(SECONDS_IN_DAY); + long days = daysAndRemainder[0].longValue(); + if (days > 0) { + result.append(days).append(" ").append(getDaysForm(days)).append(" "); + } + + BigDecimal[] hoursAndRemainder = daysAndRemainder[1].divideAndRemainder(SECONDS_IN_HOUR); + long hours = hoursAndRemainder[0].longValue(); + if (hours > 0) { + result.append(hours).append(" ").append(getHoursForm(hours)).append(" "); + } + + BigDecimal[] minutesAndRemainder = hoursAndRemainder[1].divideAndRemainder(SECONDS_IN_MINUTE); + long minutes = minutesAndRemainder[0].longValue(); + if (minutes > 0) { + result.append(minutes).append(" ").append(getMinutesForm(minutes)).append(" "); + } + + long remainingSeconds = minutesAndRemainder[1].setScale(0, RoundingMode.HALF_UP).longValue(); + if (remainingSeconds > 0) { + result.append(remainingSeconds).append(" ").append(getSecondsForm(remainingSeconds)); + } + + return result.toString().trim(); + } + + + private static String getYearsForm(long years) { + if (years == 1) return "rok"; + if (years % 10 >= 2 && years % 10 <= 4 && (years % 100 < 10 || years % 100 >= 20)) return "lata"; + return "lat"; + } + + private static String getDaysForm(long days) { + if (days == 1) return "dzień"; + return "dni"; + } + + private static String getHoursForm(long hours) { + if (hours == 1) return "godzina"; + if (hours % 10 >= 2 && hours % 10 <= 4 && (hours % 100 < 10 || hours % 100 >= 20)) return "godziny"; + return "godzin"; + } + + private static String getMinutesForm(long minutes) { + if (minutes == 1) return "minuta"; + if (minutes % 10 >= 2 && minutes % 10 <= 4 && (minutes % 100 < 10 || minutes % 100 >= 20)) return "minuty"; + return "minut"; + } + + private static String getSecondsForm(long seconds) { + if (seconds == 1) return "sekunda"; + if (seconds % 10 >= 2 && seconds % 10 <= 4 && (seconds % 100 < 10 || seconds % 100 >= 20)) return "sekundy"; + return "sekund"; + } + + + private static int calculateCharsetSize(String password) { + int charsetSize = 0; + boolean hasLowercase = false; + boolean hasUppercase = false; + boolean hasDigits = false; + boolean hasSpecial = false; + + for (char c : password.toCharArray()) { + if (Character.isLowerCase(c)) hasLowercase = true; + else if (Character.isUpperCase(c)) hasUppercase = true; + else if (Character.isDigit(c)) hasDigits = true; + else hasSpecial = true; + } + + if (hasLowercase) charsetSize += LOWERCASE_SIZE; + if (hasUppercase) charsetSize += UPPERCASE_SIZE; + if (hasDigits) charsetSize += DIGITS_SIZE; + if (hasSpecial) charsetSize += SPECIAL_CHARS_SIZE; + + return charsetSize; + } +} diff --git a/src/main/resources/static/css/password.css b/src/main/resources/static/css/password.css index 5f332a7..120bf17 100644 --- a/src/main/resources/static/css/password.css +++ b/src/main/resources/static/css/password.css @@ -47,6 +47,7 @@ body { display: grid; place-items: center; padding: 24px; + align-content: space-evenly; } h1 { @@ -59,7 +60,7 @@ h1 { } form { - width: min(92vw, 480px); + width: min(92vw, 540px); background: var(--panel); border: 1px solid var(--border); padding: 20px 18px; @@ -118,7 +119,7 @@ input[type="password"]:focus { } .message { - width: min(92vw, 480px); + width: min(92vw, 540px); margin: 12px auto 0; padding: 12px 14px; border-radius: 12px; @@ -184,7 +185,7 @@ input[type="password"]:focus { } #strengthBarContainer { - width: min(92vw, 480px); + width: min(92vw, 540px); margin: 10px auto 0; height: 10px; background: #e2e8f0; @@ -200,9 +201,21 @@ input[type="password"]:focus { } ul#tipsList { - width: min(92vw, 480px); + width: min(92vw, 540px); margin: 10px auto 0; padding-left: 20px; font-size: 14px; color: var(--text); + list-style: disc; } + +ul#tipsList.ok { + padding-left: 14px; + list-style: none; + color: #86efac; +} + +ul#tipsList li { + margin: 0.3rem 0.5rem; +} + diff --git a/src/main/resources/templates/password.html b/src/main/resources/templates/password.html index 8148a3e..9f80040 100644 --- a/src/main/resources/templates/password.html +++ b/src/main/resources/templates/password.html @@ -20,19 +20,26 @@
-

-

-
+

+

+

+ +