From b003d7906c19feadf2b260fe4409c020f0e40ff3 Mon Sep 17 00:00:00 2001 From: kimci86 Date: Wed, 27 Dec 2023 17:04:22 +0100 Subject: [PATCH] Allow to continue a partial password recovery --- include/Arguments.hpp | 4 +++ include/password.hpp | 4 ++- src/Arguments.cpp | 7 ++++ src/main.cpp | 24 ++++++++++++-- src/password.cpp | 76 +++++++++++++++++++++++++++++++++++-------- 5 files changed, 97 insertions(+), 18 deletions(-) diff --git a/include/Arguments.hpp b/include/Arguments.hpp index faa05e5..126bd52 100644 --- a/include/Arguments.hpp +++ b/include/Arguments.hpp @@ -108,6 +108,9 @@ class Arguments /// \copydoc LengthInterval std::optional length; + /// Starting point for password recovery + std::string recoveryStart; + /// Number of threads to use for parallelized operations int jobs; @@ -150,6 +153,7 @@ class Arguments bruteforce, length, recoverPassword, + recoveryStart, jobs, exhaustive, infoArchive, diff --git a/include/password.hpp b/include/password.hpp index 3738aa1..18b5b67 100644 --- a/include/password.hpp +++ b/include/password.hpp @@ -75,6 +75,8 @@ 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 @@ -82,6 +84,6 @@ class Recovery /// \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 recoverPassword(const Keys& keys, const bytevec& charset, std::size_t minLength, std::size_t maxLength, int jobs, bool exhaustive, Progress& progress); +std::vector 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 diff --git a/src/Arguments.cpp b/src/Arguments.cpp index cb41afb..1ab7dc0 100644 --- a/src/Arguments.cpp +++ b/src/Arguments.cpp @@ -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; @@ -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), diff --git a/src/main.cpp b/src/main.cpp index 066e677..e633897 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -83,6 +83,10 @@ Options to use the internal password representation: -r, --recover-password [ .. | .. | .. | ] Shortcut for --length and --bruteforce options + --continue-recovery + Starting point of the password recovery. Useful to continue a previous + non-exhaustive or interrupted password recovery. + Other options: -j, --jobs Number of threads to use for parallelized operations -e, --exhaustive Exhaustively look for all solutions (keys or @@ -258,15 +262,16 @@ try std::vector passwords; - const auto state = [&]() -> Progress::State + const auto [state, restart] = [&]() -> std::pair { 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) @@ -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(c); + std::cout << std::endl; + + std::cout.fill(fillBefore); + std::cout.flags(flagsBefore); + } return 1; } else diff --git a/src/password.cpp b/src/password.cpp index 111dc20..d760a25 100644 --- a/src/password.cpp +++ b/src/password.cpp @@ -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(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{}; - auto nextCandidateIndex = std::atomic{0}; + auto nextCandidateIndex = std::atomic{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]; @@ -251,16 +258,29 @@ 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(start[worker.prefix.size() + 1])) + ++index_start; worker.prefix.push_back(worker.charset[0]); worker.prefix.push_back(worker.charset[0]); @@ -268,13 +288,18 @@ void recoverPasswordRecursive(Recovery& worker, int jobs, const Keys& initial, P 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{}; - auto nextCandidateIndex = std::atomic{0}; + auto nextCandidateIndex = std::atomic{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]; @@ -290,6 +315,9 @@ 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) @@ -297,6 +325,14 @@ void recoverPasswordRecursive(Recovery& worker, int jobs, const Keys& initial, P 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 { @@ -304,18 +340,26 @@ void recoverPasswordRecursive(Recovery& worker, int jobs, const Keys& initial, P 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) @@ -328,13 +372,15 @@ void recoverPasswordRecursive(Recovery& worker, int jobs, const Keys& initial, P } // namespace -std::vector recoverPassword(const Keys& keys, const bytevec& charset, std::size_t minLength, std::size_t maxLength, int jobs, bool exhaustive, Progress& progress) +std::vector 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 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; @@ -370,10 +416,12 @@ std::vector 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; }