简体   繁体   中英

Permutations in Java without using an array

The code below loops through all of the characters submitted, puts them into a string array and then after hashing them, checks against the hashed password originally entered. This logic works fine for up to 5 character passwords however when I get to 6 characters the number of permutations (36^6) is so large that a memory exception is thrown due to the size of the array being too large.

I am therefore trying to modify it too use just string rather than an array to stop this memory issue but cant quite get it to work, any obvious suggestions?

You can just enumerate all the possible passwords containing letters and digits by counting from 0 to infinity and converting the number toString with base 36.

long k = 0;
while (true) {
    String pwd = Long.toString(k++, 36);
    if (SHA1(pwd).equals(hashedPassword)) {
        System.out.println(pwd);
    }
}

Here, toString works for a base up to 36, using [0-9a-z] , ie it works in your case, but if you want to include special characters you will have to create your own number-to-password function (think division and modulo), but the rest remains the same.

This way, the memory requirement is O(1), but of course the complexity is still O(36 n ) for passwords with up to n characters.


One problem with this approach is that -- as with all representations of numbers -- leading zeros will be omitted, thus Long.toString will never produce a password starting with 0 (except 0 itself). To counter this, you could use two nested loops -- the outer loop iterating the number of digits in the password and the inner loop iterating the numbers up to 36 d and padding the string with leading zeros, or loop from 36 d to 2*36 d and cut away the first (non-zero) digit. This may seem like a lot more work than before, but in fact it just produces twice as many passwords.

for (int d = 1; d < 3; d++) {
    long p = pow(36, d);
    for (long n = p; n < 2 * p; n++) {
        String pwd = Long.toString(n, 36).substring(1);
        System.out.println(n + " " + pwd);
    }
}

Output:

0
1
...
z
00
01
...
zz

In addition to tobias' answer.

Here's a way you can use your specified alphabet to recurse through all possible combinations of it, up to a certain length, and operate on each of them without storing them unless you detect that it is a match.

// Depth first combinations
private static void combineRecurse(String base, char[] alphabet, int maxLen,
        Consumer<String> consumer) {
    if (base.length() < maxLen - 1) {
        for (int i = 0; i < alphabet.length; i++) {
            String newPermutation = base + alphabet[i];
            consumer.accept(newPermutation);
            combineRecurse(newPermutation, alphabet, maxLen, consumer);
        }
    } else {
        // the new permutiation will be the maxlength
        for (int i = 0; i < alphabet.length; i++) {
            consumer.accept(base + alphabet[i]);
        }
    }
}

public static void forEachCombination(char[] alphabet, int maxLen, Consumer<String> consumer) {
    if(alphabet == null || consumer == null || maxLen < 1) return;
    combineRecurse("", alphabet, maxLen, consumer);
}

public static void main(String[] args) {

    char[] alphabet = { ... };
    final String hashedPassword = SHA1(password);
    final int maxlen = 16;

    List<String> possiblePasswords = new ArrayList<>();
    forEachCombination(alphabet, maxlen, s -> {
        // System.out.println(s);
        if (SHA1(s).equals(hashedPassword)) {
            possiblePasswords.add(s);
        }
    });
}

Making this iterative rather than recursive might be better, but for now the recursive is easy to implement and is probably more easily understandable for a beginner.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM