feat: CLI with Boost.program_options and hash type dispatch

Full argument parsing for all attack modes, hash auto-detection,
salt support, thread count, and JSON output. Dispatch resolves
hasher + attack at compile time via template instantiation.
This commit is contained in:
CarterPerez-dev 2026-03-22 10:39:07 -04:00
parent 3e2c3ff002
commit f90a6ada5e
1 changed files with 178 additions and 2 deletions

View File

@ -1,6 +1,182 @@
// ©AngelaMos | 2026
// main.cpp
int main() {
return 0;
#include <boost/program_options.hpp>
#include <expected>
#include <iostream>
#include <print>
#include <string>
#include "src/attack/BruteForceAttack.hpp"
#include "src/attack/DictionaryAttack.hpp"
#include "src/attack/RuleAttack.hpp"
#include "src/config/Config.hpp"
#include "src/core/Concepts.hpp"
#include "src/core/Engine.hpp"
#include "src/hash/HashDetector.hpp"
#include "src/hash/MD5Hasher.hpp"
#include "src/hash/SHA1Hasher.hpp"
#include "src/hash/SHA256Hasher.hpp"
#include "src/hash/SHA512Hasher.hpp"
namespace po = boost::program_options;
static std::string build_charset(const std::string& spec) {
std::string result;
auto has = [&](std::string_view token) {
return spec.find(token) != std::string::npos;
};
if (has("lower")) { result += config::CHARSET_LOWER; }
if (has("upper")) { result += config::CHARSET_UPPER; }
if (has("digits")) { result += config::CHARSET_DIGITS; }
if (has("special")) { result += config::CHARSET_SPECIAL; }
if (result.empty()) {
result += config::CHARSET_LOWER;
result += config::CHARSET_DIGITS;
}
return result;
}
template <Hasher H>
static auto dispatch_attack(const CrackConfig& cfg)
-> std::expected<CrackResult, CrackError> {
if (cfg.bruteforce) {
return Engine::crack<H, BruteForceAttack>(cfg);
}
if (cfg.use_rules) {
return Engine::crack<H, RuleAttack>(cfg);
}
return Engine::crack<H, DictionaryAttack>(cfg);
}
static auto dispatch_hasher(HashType type, const CrackConfig& cfg)
-> std::expected<CrackResult, CrackError> {
switch (type) {
case HashType::MD5: return dispatch_attack<MD5Hasher>(cfg);
case HashType::SHA1: return dispatch_attack<SHA1Hasher>(cfg);
case HashType::SHA256: return dispatch_attack<SHA256Hasher>(cfg);
case HashType::SHA512: return dispatch_attack<SHA512Hasher>(cfg);
}
return std::unexpected(CrackError::UnsupportedAlgorithm);
}
static void write_json_result(const std::string& path,
const CrackResult& result) {
auto* f = std::fopen(path.c_str(), "w");
if (!f) { return; }
std::fprintf(f,
"{\n"
" \"plaintext\": \"%s\",\n"
" \"hash\": \"%s\",\n"
" \"algorithm\": \"%s\",\n"
" \"elapsed_seconds\": %.4f,\n"
" \"candidates_tested\": %zu,\n"
" \"hashes_per_second\": %.2f\n"
"}\n",
result.plaintext.c_str(),
result.hash.c_str(),
result.algorithm.c_str(),
result.elapsed_seconds,
result.candidates_tested,
result.hashes_per_second);
std::fclose(f);
}
int main(int argc, char* argv[]) {
po::options_description desc("hashcracker - Multi-threaded hash cracking tool");
desc.add_options()
("help,h", "Show help message")
("hash", po::value<std::string>(), "Target hash to crack")
("type", po::value<std::string>()->default_value("auto"),
"Hash type: md5, sha1, sha256, sha512, auto")
("wordlist,w", po::value<std::string>(), "Path to wordlist file")
("bruteforce,b", "Use brute-force attack mode")
("charset", po::value<std::string>()->default_value("lower,digits"),
"Character sets: lower,upper,digits,special")
("max-length", po::value<std::size_t>()->default_value(
config::DEFAULT_MAX_BRUTE_LENGTH), "Max password length for brute-force")
("rules,r", "Apply mutation rules to dictionary words")
("chain-rules", "Chain mutation rules in combination")
("salt", po::value<std::string>(), "Salt value to prepend/append")
("salt-position", po::value<std::string>()->default_value("prepend"),
"Salt position: prepend or append")
("threads,t", po::value<unsigned>()->default_value(
config::DEFAULT_THREAD_COUNT), "Thread count (0 = auto)")
("output,o", po::value<std::string>(), "Write JSON result to file");
po::variables_map vm;
try {
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);
} catch (const po::error& e) {
std::println(stderr, "Error: {}", e.what());
return 1;
}
if (vm.count("help") || !vm.count("hash")) {
std::cout << desc << std::endl;
return vm.count("help") ? 0 : 1;
}
CrackConfig cfg;
cfg.target_hash = vm["hash"].as<std::string>();
cfg.hash_type = vm["type"].as<std::string>();
cfg.thread_count = vm["threads"].as<unsigned>();
cfg.bruteforce = vm.count("bruteforce") > 0;
cfg.use_rules = vm.count("rules") > 0;
cfg.chain_rules = vm.count("chain-rules") > 0;
cfg.max_length = vm["max-length"].as<std::size_t>();
if (vm.count("wordlist")) {
cfg.wordlist_path = vm["wordlist"].as<std::string>();
}
if (vm.count("salt")) {
cfg.salt = vm["salt"].as<std::string>();
}
cfg.salt_position = vm["salt-position"].as<std::string>();
if (vm.count("output")) {
cfg.output_path = vm["output"].as<std::string>();
}
if (cfg.bruteforce) {
cfg.charset = build_charset(vm["charset"].as<std::string>());
} else if (cfg.wordlist_path.empty()) {
std::println(stderr, "Error: --wordlist required for dictionary/rule attacks");
return 1;
}
HashType hash_type;
if (cfg.hash_type == "auto") {
auto detected = HashDetector::detect(cfg.target_hash);
if (!detected.has_value()) {
std::println(stderr, "Error: {}",
crack_error_message(detected.error()));
return 1;
}
hash_type = *detected;
} else if (cfg.hash_type == "md5") {
hash_type = HashType::MD5;
} else if (cfg.hash_type == "sha1") {
hash_type = HashType::SHA1;
} else if (cfg.hash_type == "sha256") {
hash_type = HashType::SHA256;
} else if (cfg.hash_type == "sha512") {
hash_type = HashType::SHA512;
} else {
std::println(stderr, "Error: Unknown hash type '{}'", cfg.hash_type);
return 1;
}
auto result = dispatch_hasher(hash_type, cfg);
if (result.has_value() && !cfg.output_path.empty()) {
write_json_result(cfg.output_path, *result);
}
return result.has_value() ? 0 : 1;
}