Skip to content

Commit

Permalink
Allow to continue a partial password recovery
Browse files Browse the repository at this point in the history
  • Loading branch information
kimci86 committed Dec 27, 2023
1 parent e4234e7 commit b003d79
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 18 deletions.
4 changes: 4 additions & 0 deletions include/Arguments.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ class Arguments
/// \copydoc LengthInterval
std::optional<LengthInterval> length;

/// Starting point for password recovery
std::string recoveryStart;

/// Number of threads to use for parallelized operations
int jobs;

Expand Down Expand Up @@ -150,6 +153,7 @@ class Arguments
bruteforce,
length,
recoverPassword,
recoveryStart,
jobs,
exhaustive,
infoArchive,
Expand Down
4 changes: 3 additions & 1 deletion include/password.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,15 @@ class Recovery
/// \param charset The set of characters with which to constitute password candidates
/// \param minLength The smallest password length to try
/// \param maxLength The greatest password length to try
/// \param start Starting point in the password search space.
/// Also used as an output parameter to tell where to restart.
/// \param jobs Number of threads to use
/// \param exhaustive True to try and find all valid passwords,
/// false to stop searching after the first one is found
/// \param progress Object to report progress
/// \return A vector of passwords associated with the given keys.
/// A vector is needed instead of a single string because there can be
/// collisions (i.e. several passwords for the same keys).
std::vector<std::string> recoverPassword(const Keys& keys, const bytevec& charset, std::size_t minLength, std::size_t maxLength, int jobs, bool exhaustive, Progress& progress);
std::vector<std::string> recoverPassword(const Keys& keys, const bytevec& charset, std::size_t minLength, std::size_t maxLength, std::string& start, int jobs, bool exhaustive, Progress& progress);

#endif // BKCRACK_PASSWORD_HPP
7 changes: 7 additions & 0 deletions src/Arguments.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,12 @@ void Arguments::parseArgument()
}, parseInterval(readString("length")));
bruteforce = readCharset();
break;
case Option::recoveryStart:
{
const bytevec checkpoint = readHex("checkpoint");
recoveryStart.assign(checkpoint.begin(), checkpoint.end());
break;
}
case Option::jobs:
jobs = readInt("count");
break;
Expand Down Expand Up @@ -330,6 +336,7 @@ Arguments::Option Arguments::readOption(const std::string& description)
PAIRS(-b, --bruteforce, bruteforce),
PAIRS(-l, --length, length),
PAIRS(-r, --recover-password, recoverPassword),
PAIR ( --continue-recovery, recoveryStart),
PAIRS(-j, --jobs, jobs),
PAIRS(-e, --exhaustive, exhaustive),
PAIRS(-L, --list, infoArchive),
Expand Down
24 changes: 21 additions & 3 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ Options to use the internal password representation:
-r, --recover-password [ <min>..<max> | <min>.. | ..<max> | <max> ] <charset>
Shortcut for --length and --bruteforce options
--continue-recovery <checkpoint>
Starting point of the password recovery. Useful to continue a previous
non-exhaustive or interrupted password recovery.
Other options:
-j, --jobs <count> Number of threads to use for parallelized operations
-e, --exhaustive Exhaustively look for all solutions (keys or
Expand Down Expand Up @@ -258,15 +262,16 @@ try

std::vector<std::string> passwords;

const auto state = [&]() -> Progress::State
const auto [state, restart] = [&]() -> std::pair<Progress::State, std::string>
{
const auto& charset = *args.bruteforce;
const auto& [minLength, maxLength] = args.length.value_or(Arguments::LengthInterval{});
std::string start = args.recoveryStart;

ConsoleProgress progress(std::cout);
SigintHandler sigintHandler(progress.state);
passwords = recoverPassword(keysvec.front(), charset, minLength, maxLength, args.jobs, args.exhaustive, progress);
return progress.state;
passwords = recoverPassword(keysvec.front(), charset, minLength, maxLength, start, args.jobs, args.exhaustive, progress);
return {progress.state, start};
}();

if(state == Progress::State::Canceled)
Expand All @@ -278,6 +283,19 @@ try
if(passwords.empty())
{
std::cout << "Could not recover password" << std::endl;
if(state == Progress::State::Canceled)
{
const auto flagsBefore = std::cout.setf(std::ios::hex, std::ios::basefield);
const auto fillBefore = std::cout.fill('0');

std::cout << "You may resume the password recovery with the option: --continue-recovery ";
for(byte c : restart)
std::cout << std::setw(2) << static_cast<int>(c);
std::cout << std::endl;

std::cout.fill(fillBefore);
std::cout.flags(flagsBefore);
}
return 1;
}
else
Expand Down
76 changes: 62 additions & 14 deletions src/password.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -225,21 +225,28 @@ void Recovery::recursion(int i)
namespace
{

void recoverPasswordRecursive(Recovery& worker, int jobs, const Keys& initial, Progress& progress)
void recoverPasswordRecursive(Recovery& worker, int jobs, const Keys& initial, const std::string& start, std::string& restart, Progress& progress)
{
const int charsetSize = worker.charset.size();

int index_start = 0;
if(worker.prefix.size() < start.size())
while(worker.charset[index_start] < static_cast<unsigned char>(start[worker.prefix.size()]))
++index_start;

if(worker.prefix.size() + 1 + 9 == worker.length) // bruteforce one character in parallel
{
const int charsetSize = worker.charset.size();

worker.prefix.push_back(worker.charset[0]);

progress.done += index_start * charsetSize;

const auto threadCount = std::clamp(jobs, 1, charsetSize);
auto threads = std::vector<std::thread>{};
auto nextCandidateIndex = std::atomic<int>{0};
auto nextCandidateIndex = std::atomic<int>{index_start};
for(auto i = 0; i < threadCount; ++i)
threads.emplace_back(
[&nextCandidateIndex, charsetSize, &progress, worker, initial]() mutable {
for(auto i = nextCandidateIndex++; i < charsetSize && progress.state == Progress::State::Normal; i = nextCandidateIndex++)
for(auto i = nextCandidateIndex++; i < charsetSize; i = nextCandidateIndex++)
{
byte pm4 = worker.charset[i];

Expand All @@ -251,30 +258,48 @@ void recoverPasswordRecursive(Recovery& worker, int jobs, const Keys& initial, P
worker.recoverLongPassword(init);

progress.done += charsetSize;

if(progress.state != Progress::State::Normal)
break;
}
});
for(auto& thread : threads)
thread.join();

worker.prefix.pop_back();

if(nextCandidateIndex < charsetSize)
{
restart = worker.prefix;
restart.push_back(worker.charset[nextCandidateIndex]);
restart.append(worker.length - 6 - restart.size(), worker.charset[0]);
}
}
else if(worker.prefix.size() + 2 + 9 == worker.length) // bruteforce two characters in parallel
{
const int charsetSize = worker.charset.size();
index_start *= charsetSize;
if(worker.prefix.size() + 1 < start.size())
while(worker.charset[index_start] < static_cast<unsigned char>(start[worker.prefix.size() + 1]))
++index_start;

worker.prefix.push_back(worker.charset[0]);
worker.prefix.push_back(worker.charset[0]);

const bool reportProgress = worker.prefix.size() == 2;
const bool reportProgressCoarse = worker.prefix.size() == 3;

if(reportProgress)
progress.done += index_start;
else if(reportProgressCoarse)
progress.done += index_start / charsetSize;

const auto threadCount = std::clamp(jobs, 1, charsetSize);
auto threads = std::vector<std::thread>{};
auto nextCandidateIndex = std::atomic<int>{0};
auto nextCandidateIndex = std::atomic<int>{index_start};
for(auto i = 0; i < threadCount; ++i)
threads.emplace_back(
[&nextCandidateIndex, charsetSize, &progress, worker, initial, reportProgress, reportProgressCoarse]() mutable {
for(auto i = nextCandidateIndex++; i < charsetSize * charsetSize && progress.state == Progress::State::Normal; i = nextCandidateIndex++)
for(auto i = nextCandidateIndex++; i < charsetSize * charsetSize; i = nextCandidateIndex++)
{
byte pm4 = worker.charset[i / charsetSize];
byte pm3 = worker.charset[i % charsetSize];
Expand All @@ -290,32 +315,51 @@ void recoverPasswordRecursive(Recovery& worker, int jobs, const Keys& initial, P

if(reportProgress || (reportProgressCoarse && i % charsetSize == 0))
progress.done++;

if(progress.state != Progress::State::Normal)
break;
}
});
for(auto& thread : threads)
thread.join();

worker.prefix.pop_back();
worker.prefix.pop_back();

if(nextCandidateIndex < charsetSize * charsetSize)
{
restart = worker.prefix;
restart.push_back(worker.charset[nextCandidateIndex / charsetSize]);
restart.push_back(worker.charset[nextCandidateIndex % charsetSize]);
restart.append(worker.length - 6 - restart.size(), worker.charset[0]);
}
}
else // try password prefixes recursively
{
worker.prefix.push_back(worker.charset[0]);

const bool reportProgress = worker.prefix.size() == 2;

for(byte pi : worker.charset)
if(worker.prefix.size() == 1)
progress.done += index_start * charsetSize;
else if(reportProgress)
progress.done += index_start;

for(int i = index_start; i < charsetSize; i++)
{
byte pi = worker.charset[i];

Keys init = initial;
init.update(pi);

worker.prefix.back() = pi;

recoverPasswordRecursive(worker, jobs, init, progress);
recoverPasswordRecursive(worker, jobs, init, i == index_start ? start : "", restart, progress);

// Because the recursive call may explore only a fraction of its
// search space, check that it was run in full before counting progress.
if(progress.state != Progress::State::Normal)

if(!restart.empty())
break;

if(reportProgress)
Expand All @@ -328,13 +372,15 @@ void recoverPasswordRecursive(Recovery& worker, int jobs, const Keys& initial, P

} // namespace

std::vector<std::string> recoverPassword(const Keys& keys, const bytevec& charset, std::size_t minLength, std::size_t maxLength, int jobs, bool exhaustive, Progress& progress)
std::vector<std::string> recoverPassword(const Keys& keys, const bytevec& charset, std::size_t minLength, std::size_t maxLength, std::string& start, int jobs, bool exhaustive, Progress& progress)
{
std::vector<std::string> solutions;
std::mutex solutionsMutex;
Recovery worker(keys, charset, solutions, solutionsMutex, exhaustive, progress);

for(std::size_t length = minLength; length <= maxLength; length++)
std::string restart;
const std::size_t startLength = std::max(minLength, start.empty() ? 0 : start.size() + 6);
for(std::size_t length = startLength; length <= maxLength; length++)
{
if(progress.state != Progress::State::Normal)
break;
Expand Down Expand Up @@ -370,10 +416,12 @@ std::vector<std::string> recoverPassword(const Keys& keys, const bytevec& charse
progress.done = 0;
progress.total = charset.size() * charset.size();

recoverPasswordRecursive(worker, jobs, Keys{}, progress);
recoverPasswordRecursive(worker, jobs, Keys{}, length == startLength ? start : "", restart, progress);
}
}
}

start = restart;

return solutions;
}

0 comments on commit b003d79

Please sign in to comment.