Problem
I made this little code to see what brute forcing is like. I made this with a complete guess on how it works. This code works fine but it seems to take much much longer than it really should. I have the Scanner
there so I can tell the program what password it is searching for. I have the timeMillis
also just for personal reasons to see how long it takes to find a password.
I have the
System.out.println(newPass);
to see if I ever run into errors where would I run into it. The way my code works is that it loops chars^length times until it hits the password.
If there is any way I can improve this?
import java.util.Scanner;
import java.util.concurrent.TimeUnit;
public class Main {
static String newPass = "";
static String chars = "0123456789aABbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyzZ";
public static void main(String[] args) {
Scanner userIn = new Scanner(System.in);
String password = userIn.nextLine();
System.out.println("Is using symbols an option? if so type in [Y] if not type in [N]");
String choose = userIn.nextLine();
boolean decideSymb = true;
boolean again = true;
while (again == true) {
if (choose.equalsIgnoreCase("y")) {
again = false;
} else if (choose.equalsIgnoreCase("n")) {
again = false;
decideSymb = false;
} else {
System.out.println("Try again! nIs using symbols an option? if so type in [Y] if not type in [N]");
choose = userIn.nextLine();
}
}
long start = System.currentTimeMillis();
crack(password, decideSymb);
long end = System.currentTimeMillis();
long milliSecs = TimeUnit.MILLISECONDS.toSeconds(end - start);
;
long secs = milliSecs / 1000;
long mins = secs / 60;
long hours = mins / 60;
long days = hours / 24;
long years = days / 365;
days -= (years * 365);
hours -= (days * 24);
mins -= (hours * 60);
secs -= (mins * 60);
milliSecs -= (secs * 1000);
System.out.println("The password is: " + newPass);
if (years > 0) {
if (years == 1) {
System.out.println("it tookn" + years + "yearn" + days + " daysn" + hours + " hoursn" + mins
+ " minsn" + secs + " secsn" + milliSecs + " millisecondsnto find the password");
} else {
System.out.println("it tookn" + years + "yearsn" + days + " daysn" + hours + " hoursn" + mins
+ " minsn" + secs + " secsn" + milliSecs + " millisecondsnto find the password");
}
} else if (days > 0) {
if (days == 1) {
System.out.println("it tookn" + days + " dayn" + hours + " hoursn" + mins + " minsn" + secs
+ " secsn" + milliSecs + " millisecondsnto find the password");
} else {
System.out.println("it tookn" + days + " daysn" + hours + " hoursn" + mins + " minsn" + secs
+ " secsn" + milliSecs + " millisecondsnto find the password");
}
} else if (hours > 0) {
if (hours == 1) {
System.out.println("it tookn" + hours + " hourn" + mins + " minsn" + secs + " secsn" + milliSecs
+ " millisecondsnto find the password");
} else {
System.out.println("it tookn" + hours + " hoursn" + mins + " minsn" + secs + " secsn" + milliSecs
+ " millisecondsnto find the password");
}
} else if (mins > 0) {
if (mins == 1) {
System.out.println("it tookn" + mins + " minn" + secs + " secsn" + milliSecs
+ " millisecondsnto find the password");
} else {
System.out.println("it tookn" + mins + " minsn" + secs + " secsn" + milliSecs
+ " millisecondsnto find the password");
}
} else if (secs > 0) {
if (secs == 1) {
System.out.println("it tookn" + secs + " secn" + milliSecs + " millisecondsnto find the password");
} else {
System.out.println("it tookn" + secs + " secsn" + milliSecs + " millisecondsnto find the password");
}
} else {
System.out.println("it tookn" + milliSecs + " millisecondsnto find the password");
}
System.exit(0);
}
private static void crack(String password, boolean decideSymb) {
if (decideSymb == true) {
chars = "1234567890#%&@aABbCcDdEeFfGgHh!IiJjKkLlMmNnOoPpQqRr$SsTtUuVvWwXxYyzZ";
}
for (int length = 2; length <= 15; length++) {
newPass = "";
for (int n = 0; n < length; n++) {
newPass += "0";
}
int lastInd = length - 1;
while (!newPass.equals(password)) {
String end = "";
for (int n = 0; n < newPass.length(); n++) {
end += "Z";
}
if (newPass.equals(end)) {
break;
}
int indInChars = chars.indexOf(newPass.charAt(lastInd));
if (indInChars < chars.length() && indInChars >= 0) {
boolean t = true;
int howManyZs = 0;
while (t == true) {
if (newPass.charAt(newPass.length() - 1 - howManyZs) == 'Z') {
howManyZs++;
} else {
t = false;
}
}
String reset0s = "";
for (int l = 0; l < howManyZs; l++) {
reset0s += "0";
}
if (lastInd < newPass.length() - 1 && lastInd > 0) {
lastInd--;
indInChars = chars.indexOf(newPass.charAt(lastInd)) + 1;
newPass = newPass.substring(0, lastInd) + chars.charAt(indInChars)
+ newPass.substring(lastInd + 1);
} else if (newPass.length() - howManyZs == 1) {
newPass = chars.substring(chars.indexOf(newPass.charAt(0)) + 1,
chars.indexOf(newPass.charAt(0)) + 2) + reset0s;
} else if (newPass.length() - howManyZs > 1 && howManyZs != 0) {
newPass = newPass.substring(0, newPass.length() - 1 - howManyZs)
+ chars.substring(chars.indexOf(newPass.charAt(newPass.length() - 1 - howManyZs)) + 1,
chars.indexOf(newPass.charAt(newPass.length() - 1 - howManyZs)) + 2)
+ reset0s;
} else {
indInChars = chars.indexOf(newPass.charAt(lastInd)) + 1;
newPass = newPass.substring(0, lastInd) + chars.charAt(indInChars);
}
System.out.println(newPass);
}
}
if (newPass.equals(password)) {
break;
}
}
}
}
Keep in mind I am not gonna use this for malicious reasons. Plus, most software is invulnerable to brute force. This is merely out of fun.
Solution
Do while you must
System.out.println("Is using symbols an option? if so type in [Y] if not type in [N]");
String choose = userIn.nextLine();
boolean decideSymb = true;
boolean again = true;
while (again == true) {
if (choose.equalsIgnoreCase("y")) {
again = false;
} else if (choose.equalsIgnoreCase("n")) {
again = false;
decideSymb = false;
} else {
System.out.println("Try again! nIs using symbols an option? if so type in [Y] if not type in [N]");
choose = userIn.nextLine();
}
}
You have some awkward-feeling repeated code in here (the prompt for input). This can be refactored into a cleaner, more natural feeling do/while
loop:
do {
System.out.println("Is using symbols an option? if so type in [Y] if not type in [N]");
String choose = userIn.nextLine();
} while(!choose.equalsIgnoreCase("y") && !choose.equalsIgnoreCase("n"));
By changing this to a do/while
, we were able to remove those clunky if
statements along with the two flag variables.
The above loop will continuously ask for input until proper input is given.
Or use a pattern you can
Instead of using a loop to shorten the above code, you could use the Scanner
‘s built-in next(Pattern pattern)
. This will ensure that you only get input that follows your specified pattern.
First, create a final Pattern
in a class field so you can easily access it in the code:
public static final Pattern yesOrNo = Pattern.compile("(y|n)", CASE_INSENSITIVE);
The above regex should only pass for either y, n, Y, or N. Also, it may be better to compile the regex in the main method itself.
Then, in your code, instead of having the loop, you can simply write:
System.out.println("Is using symbols an option? if so type in [Y] if not type in [N]");
String choose = userIn.next(yesOrNo);
I believe this is a much better solution.
Too far for a single letter
if (years == 1) {
System.out.println("it tookn" + years + "yearn" + days + " daysn" + hours + " hoursn" + mins
+ " minsn" + secs + " secsn" + milliSecs + " millisecondsnto find the password");
} else {
System.out.println("it tookn" + years + "yearsn" + days + " daysn" + hours + " hoursn" + mins
+ " minsn" + secs + " secsn" + milliSecs + " millisecondsnto find the password");
}
You have two entirely separate conditional statements just to add a single character? That’s a waste, and it makes your code look ugly.
Honestly, I don’t think you’ll even need to worry about that “s”.
- The chances of it being exactly
1
are very low. - It’s such a small thing that most applications ignore.
However, if you really want to have that “s” there, use a ternary instead:
System.out.println("it tookn" + years + "year" + (years == 1 ? "s" : "") + "n" + days + " daysn" + hours + " hoursn" + mins + " minsn" + secs + " secsn" + milliSecs + " millisecondsnto find the password");
The date is exactly…
This chunk of code here:
long secs = milliSecs / 1000;
long mins = secs / 60;
long hours = mins / 60;
long days = hours / 24;
long years = days / 365;
days -= (years * 365);
hours -= (days * 24);
mins -= (hours * 60);
secs -= (mins * 60);
Doesn’t belong. It should be extracted into a separate method that accepts the milliSecs
variable. As for returning
, it could probably return a custom class that looks like this:
public class TimeData {
private final long secs;
private final long mins;
...
}
with getters for each field. This will make your code more OO.
If you want to be extra OOP-y, you can put the method that generates the TimeData
in this TimeData
class, make it static
, and have it return an instance of TimeData
with filled data.
Over-complicating the time printing
Again, you are over-complicating the time printing section. In my opinion, it is perfectly okay to have something like:
“It took 0 days, 0 hours, 24 minutes, 13 seconds, and 1 millisecond.”
Those 0’s there don’t harm the UI at all. Instead of having that huge conditional chain in your code, you should just use System.out.printf
and print out all the time values you have.
Going back to OOP, you could extract this into the TimeData
‘s toString
method.
Pluralisation
You have a lot of code dealing with this, and it’s both repetitive and not quite right (checks only for the plural-ness of the first item, so you might get “it took 1 min 1 secs 1 milliseconds”).
Something like this would be a start:
private static String pluralFormat(String word, long value) {
return value.toString() + " " + word + (value == 1 ? "s" : "") + "n";
}
System.out.println(
"it tookn"
+ pluralFormat("year", years)
+ pluralFormat("day", days)
+ pluralFormat("hour", hours)
+ pluralFormat("min", mins)
+ pluralFormat("sec", secs)
+ pluralFormat("millisecond", milliSecs)
+ "to find the password"
);
Or you could use separate println
s for each line to avoid manually appending the newline character.
Cracking algorithm
It’s a little hard to follow what’s going on here.
First, usually “cracking” a password means that you know a hashed password (the output of hashfunction(password)
) and you crack it by finding what value of password
gives you that hash. Here there is no hash function, you’re just building string permutations until you’ve arrived at password
. This is fine, I just wanted to make a note.
Second, since I’m not fully sure what’s going on with the howManyZs
and your algorithm in general, I’ll just offer some structural suggestions:
Repeating strings
In a couple of places you build a string of n
repeated characters. You could turn that into a static utility method:
private static String repeatString(String s, int n) {
StringBuilder sb = new StringBuilder(n);
while (n-- > 0) {
sb.append(s);
}
return sb.toString();
}
so that you’d end up with, for example
String end = repeatString("Z", newpass.length());
Parameters and variable scopes
To keep data more encapsulated and make your crack
function more reusable, I recommend declaring it like this:
private static String crack(String password, String possibleChars)
- Take in
possibleChars
directly, and let the caller pass eitherchars
orchars + symbols
as needed. - Return the cracked password instead of storing it in a global field
newPass
.