I crafted an answer using Javascript, because it's what I am used to and no language is specified. I am confident that you can find/create an analog in whatever other language you please.
First off, I stole a function from This answer to create a cartesian product. The explanation of cartesian products is not really in the scope of this question, but suffice to say it combines arrays of possible characters into arrays of possible strings (very oversimplified).
OK, so ckozl's function is :
function cartProd(paramArray) {
function addTo(curr, args) {
var i, copy,
rest = args.slice(1),
last = !rest.length,
result = [];
for (i = 0; i < args[0].length; i++) {
copy = curr.slice();
copy.push(args[0][i]);
if (last) {
result.push(copy);
} else {
result = result.concat(addTo(copy, rest));
}
}
return result;
}
return addTo([], Array.prototype.slice.call(paramArray));
}
and mine is :
function findSubs(string) {
//create a linked list of sub characters
//expect lower case input
subs={
"p":["p","P"],
"a":["a","A","4","@"],
"s":["s","S","$","5"]
}
//create an (initially empty) array to store possible characters in each position
result=[]
for (var i=0;i<string.length;i++) {
result[i]=subs[string.charAt(i)]; //Would be more robust if you added logic here to handle it not being a key in the subs object.
}
/*
so result might look like:
[
["p","P"],
["a","A","4","@"],
["s","S","$","5"],
["s","S","$","5"]
]
for an input of "pass"
*/
return cartProd(result)
}
Honestly, ckzol's function does all the heavy lifting here. You would need to flesh out the subs array before use, and this does not handle adding characters at the end of the string (because it seems kind of arbitrary).
Also note I have ever so slightly modified the cartProd function, such that we can hand it an array of arrays as input params rather than multiple arrays overloading the arguments.
Edit: also note that the returned value is an array of arrays, so you would want to iterate through it and for each element e
do e.join("")
.
Edit again: also note that you could easily extend this to apply other mutations to the password. For instance, if you wanted to add a number between 0 and 99 to the end of the string you could push an extra array onto the end of result, containing ["0","1","2",...,"99"]
. You could include an empty element (""
) in this array to also output possibilities not including the number suffix. The same logic could be used to add a prefix. Essentially you are building an array of arrays of possible characters in the result array, so you can include any mutations you can think of.