As an attempt to learn multithreading better, I wrote a program to crack the password of a ZIP file. It is sort of slow, processing a three-digit password of the 95 printable ASCII characters in about 1:45 minutes. This is my class that actually handles the cracking:
class DecryptPassword
{
private readonly List<char> _charList;
private readonly int[] _currentPassword;
private readonly string _endPassword;
public DecryptPassword(List<char> charList, string startPassword, string endPassword)
{
_charList = charList;
_endPassword = endPassword;
var passwordLength = Math.Max(startPassword.Length, endPassword.Length);
_currentPassword = startPassword.Select(c => _charList.IndexOf(c)).ToArray();
while (_currentPassword.Length != passwordLength)
{
_currentPassword = _currentPassword.Concat(new [] {0}).ToArray();
}
}
public string CalculatePassword()
{
while (true)
{
var password = GetPasswordAsString();
try
{
if (ZipFile.CheckZipPassword(Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\CrackMe3.zip", password))
{
return password;
}
}
catch (BadCrcException)
{
// For some reason, sometimes a BadCRCException is thrown.
// I have never had it thrown on the real password,
// but this may be an issue for that.
// My best guess is that the speed of access the file,
// or perhaps accessing it from multiple threads, is the issue
}
if (password == _endPassword) { break; }
CalculateNextPassword();
}
return null;
}
private void CalculateNextPassword()
{
for (var index = _currentPassword.Length - 1; index >= 0; index--)
{
if (_currentPassword[index] == _charList.Count - 1)
{
_currentPassword[index] = 0;
continue;
}
_currentPassword[index]++;
break;
}
}
private string GetPasswordAsString()
{
return new string(_currentPassword.Select(i => _charList[i]).ToArray());
}
}
The constructor takes the character set to be used and a beginning and ending range of passwords. For now, it assumes both passwords are the same length, which is useful for when you know the number of characters in the password.
CalculatePassword()
iterates over the range of passwords, and returns the password when found. If the password is not found, it returns null
.
CalculateNextPassword()
will calculate the next password in a manner similar to addition in base-N math.
GetPasswordAsString()
will return the current password, stored as an array of int
s, as a string.
I generate my tasks like this, leaving Windows to manage the threads. Based on watching it in debug mode, it does create several threads, but not 95:
// characters sorted by ASCII code
private static readonly List<char> CharList = new List<char>
{
' ',
'!',
'"',
'#',
'$',
'%',
'&',
'\'',
'(',
')',
'*',
'+',
',',
'-',
'.',
'/',
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
':',
';',
'<',
'=',
'>',
'?',
'@',
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
'[',
'\\',
']',
'^',
'_',
'`',
'a',
'b',
'c',
'd',
'e',
'f',
'g',
'h',
'i',
'j',
'k',
'l',
'm',
'n',
'o',
'p',
'q',
'r',
's',
't',
'u',
'v',
'w',
'x',
'y',
'z',
'{',
'|',
'}',
'~',
};
static void Main()
{
List<Task<string>> tasks;
StartTasks(out tasks);
Console.WriteLine(tasks.First(t => t.Result != null).Result);
}
private static void StartTasks(out List<Task<string>> tasks)
{
var source = new CancellationTokenSource();
var token = source.Token;
// split problem into 95 tasks, each group calculates as follows:
// "c ", "c !" "c "", ... "c! ", "c!!", ... "c~}", "c~~"
tasks = CharList.Select(c => StartTask(c + " ", c + "~~", token)).ToList();
while (true)
{
Task.WaitAny(tasks.ToArray());
if (tasks.Any(t => t.Result != null))
{
Console.WriteLine("Cancelling");
source.Cancel();
break;
}
}
}
static async Task<string> StartTask(string start, string end, CancellationToken token)
{
var decryptor = new DecryptPassword(CharList, start, end);
var task = new Task<string>(() => decryptor.CalculatePassword(), token);
task.Start();
return await task;
}
Next on my list is to generate more tasks, especially when working with larger passwords, so basic pointers on how to split the password into groups for each task more efficiently would be welcome, but please do not provide a full solution - I need to think this through myself.