feat: all hashers (MD5, SHA1, SHA256, SHA512) and HashDetector

OpenSSL EVP-based hashers verified against NIST known-answer vectors.
HashDetector auto-identifies hash type from hex length with validation.
15 new tests, all passing.
This commit is contained in:
CarterPerez-dev 2026-03-22 08:01:01 -04:00
parent 4771d29342
commit dd27eb2620
7 changed files with 205 additions and 14 deletions

View File

@ -2,7 +2,26 @@
// HashDetector.cpp
#include "src/hash/HashDetector.hpp"
#include "src/config/Config.hpp"
#include <algorithm>
#include <cctype>
std::expected<HashType, CrackError> HashDetector::detect(std::string_view) {
return std::unexpected(CrackError::InvalidHash);
std::expected<HashType, CrackError> HashDetector::detect(std::string_view hash) {
auto is_hex = [](char c) {
return (c >= '0' && c <= '9') ||
(c >= 'a' && c <= 'f') ||
(c >= 'A' && c <= 'F');
};
if (!std::ranges::all_of(hash, is_hex)) {
return std::unexpected(CrackError::InvalidHash);
}
switch (hash.size()) {
case config::MD5_HEX_LENGTH: return HashType::MD5;
case config::SHA1_HEX_LENGTH: return HashType::SHA1;
case config::SHA256_HEX_LENGTH: return HashType::SHA256;
case config::SHA512_HEX_LENGTH: return HashType::SHA512;
default: return std::unexpected(CrackError::InvalidHash);
}
}

View File

@ -2,7 +2,27 @@
// MD5Hasher.cpp
#include "src/hash/MD5Hasher.hpp"
#include <array>
#include <iomanip>
#include <memory>
#include <openssl/evp.h>
#include <sstream>
std::string MD5Hasher::hash(std::string_view) const {
return "";
std::string MD5Hasher::hash(std::string_view input) const {
auto ctx = std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)>(
EVP_MD_CTX_new(), EVP_MD_CTX_free);
EVP_DigestInit_ex(ctx.get(), EVP_md5(), nullptr);
EVP_DigestUpdate(ctx.get(), input.data(), input.size());
std::array<unsigned char, EVP_MAX_MD_SIZE> digest{};
unsigned int len = 0;
EVP_DigestFinal_ex(ctx.get(), digest.data(), &len);
std::ostringstream hex;
hex << std::hex << std::setfill('0');
for (unsigned int i = 0; i < len; ++i) {
hex << std::setw(2) << static_cast<int>(digest[i]);
}
return hex.str();
}

View File

@ -2,7 +2,27 @@
// SHA1Hasher.cpp
#include "src/hash/SHA1Hasher.hpp"
#include <array>
#include <iomanip>
#include <memory>
#include <openssl/evp.h>
#include <sstream>
std::string SHA1Hasher::hash(std::string_view) const {
return "";
std::string SHA1Hasher::hash(std::string_view input) const {
auto ctx = std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)>(
EVP_MD_CTX_new(), EVP_MD_CTX_free);
EVP_DigestInit_ex(ctx.get(), EVP_sha1(), nullptr);
EVP_DigestUpdate(ctx.get(), input.data(), input.size());
std::array<unsigned char, EVP_MAX_MD_SIZE> digest{};
unsigned int len = 0;
EVP_DigestFinal_ex(ctx.get(), digest.data(), &len);
std::ostringstream hex;
hex << std::hex << std::setfill('0');
for (unsigned int i = 0; i < len; ++i) {
hex << std::setw(2) << static_cast<int>(digest[i]);
}
return hex.str();
}

View File

@ -2,7 +2,27 @@
// SHA256Hasher.cpp
#include "src/hash/SHA256Hasher.hpp"
#include <array>
#include <iomanip>
#include <memory>
#include <openssl/evp.h>
#include <sstream>
std::string SHA256Hasher::hash(std::string_view) const {
return "";
std::string SHA256Hasher::hash(std::string_view input) const {
auto ctx = std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)>(
EVP_MD_CTX_new(), EVP_MD_CTX_free);
EVP_DigestInit_ex(ctx.get(), EVP_sha256(), nullptr);
EVP_DigestUpdate(ctx.get(), input.data(), input.size());
std::array<unsigned char, EVP_MAX_MD_SIZE> digest{};
unsigned int len = 0;
EVP_DigestFinal_ex(ctx.get(), digest.data(), &len);
std::ostringstream hex;
hex << std::hex << std::setfill('0');
for (unsigned int i = 0; i < len; ++i) {
hex << std::setw(2) << static_cast<int>(digest[i]);
}
return hex.str();
}

View File

@ -2,7 +2,27 @@
// SHA512Hasher.cpp
#include "src/hash/SHA512Hasher.hpp"
#include <array>
#include <iomanip>
#include <memory>
#include <openssl/evp.h>
#include <sstream>
std::string SHA512Hasher::hash(std::string_view) const {
return "";
std::string SHA512Hasher::hash(std::string_view input) const {
auto ctx = std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)>(
EVP_MD_CTX_new(), EVP_MD_CTX_free);
EVP_DigestInit_ex(ctx.get(), EVP_sha512(), nullptr);
EVP_DigestUpdate(ctx.get(), input.data(), input.size());
std::array<unsigned char, EVP_MAX_MD_SIZE> digest{};
unsigned int len = 0;
EVP_DigestFinal_ex(ctx.get(), digest.data(), &len);
std::ostringstream hex;
hex << std::hex << std::setfill('0');
for (unsigned int i = 0; i < len; ++i) {
hex << std::setw(2) << static_cast<int>(digest[i]);
}
return hex.str();
}

View File

@ -2,7 +2,41 @@
// test_hash_detector.cpp
#include <gtest/gtest.h>
#include "src/hash/HashDetector.hpp"
TEST(HashDetectorTest, Stub) {
EXPECT_TRUE(true);
TEST(HashDetectorTest, DetectsMD5) {
auto result = HashDetector::detect("d41d8cd98f00b204e9800998ecf8427e");
ASSERT_TRUE(result.has_value());
EXPECT_EQ(*result, HashType::MD5);
}
TEST(HashDetectorTest, DetectsSHA1) {
auto result = HashDetector::detect("da39a3ee5e6b4b0d3255bfef95601890afd80709");
ASSERT_TRUE(result.has_value());
EXPECT_EQ(*result, HashType::SHA1);
}
TEST(HashDetectorTest, DetectsSHA256) {
auto result = HashDetector::detect(
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
ASSERT_TRUE(result.has_value());
EXPECT_EQ(*result, HashType::SHA256);
}
TEST(HashDetectorTest, DetectsSHA512) {
auto result = HashDetector::detect(std::string(128, 'a'));
ASSERT_TRUE(result.has_value());
EXPECT_EQ(*result, HashType::SHA512);
}
TEST(HashDetectorTest, RejectsInvalidLength) {
auto result = HashDetector::detect("abc");
EXPECT_FALSE(result.has_value());
EXPECT_EQ(result.error(), CrackError::InvalidHash);
}
TEST(HashDetectorTest, RejectsNonHex) {
auto result = HashDetector::detect(std::string(64, 'z'));
EXPECT_FALSE(result.has_value());
EXPECT_EQ(result.error(), CrackError::InvalidHash);
}

View File

@ -2,8 +2,66 @@
// test_hashers.cpp
#include <gtest/gtest.h>
#include "src/hash/MD5Hasher.hpp"
#include "src/hash/SHA1Hasher.hpp"
#include "src/hash/SHA256Hasher.hpp"
#include "src/hash/SHA512Hasher.hpp"
TEST(SHA256HasherTest, Stub) {
EXPECT_TRUE(true);
TEST(SHA256HasherTest, KnownVectors) {
SHA256Hasher hasher;
EXPECT_EQ(hasher.hash(""),
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
EXPECT_EQ(hasher.hash("password"),
"5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8");
EXPECT_EQ(hasher.hash("hello"),
"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824");
}
TEST(SHA256HasherTest, StaticProperties) {
EXPECT_EQ(SHA256Hasher::name(), "SHA256");
EXPECT_EQ(SHA256Hasher::digest_length(), 64);
}
TEST(SHA256HasherTest, Deterministic) {
SHA256Hasher hasher;
auto h1 = hasher.hash("test");
auto h2 = hasher.hash("test");
EXPECT_EQ(h1, h2);
}
TEST(MD5HasherTest, KnownVectors) {
MD5Hasher hasher;
EXPECT_EQ(hasher.hash(""), "d41d8cd98f00b204e9800998ecf8427e");
EXPECT_EQ(hasher.hash("password"), "5f4dcc3b5aa765d61d8327deb882cf99");
}
TEST(MD5HasherTest, StaticProperties) {
EXPECT_EQ(MD5Hasher::name(), "MD5");
EXPECT_EQ(MD5Hasher::digest_length(), 32);
}
TEST(SHA1HasherTest, KnownVectors) {
SHA1Hasher hasher;
EXPECT_EQ(hasher.hash(""), "da39a3ee5e6b4b0d3255bfef95601890afd80709");
EXPECT_EQ(hasher.hash("password"), "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8");
}
TEST(SHA1HasherTest, StaticProperties) {
EXPECT_EQ(SHA1Hasher::name(), "SHA1");
EXPECT_EQ(SHA1Hasher::digest_length(), 40);
}
TEST(SHA512HasherTest, KnownVectors) {
SHA512Hasher hasher;
EXPECT_EQ(hasher.hash(""),
"cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce"
"47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e");
EXPECT_EQ(hasher.hash("password"),
"b109f3bbbc244eb82441917ed06d618b9008dd09b3befd1b5e07394c706a8bb9"
"80b1d7785e5976ec049b46df5f1326af5a2ea6d103fd07c95385ffab0cacbc86");
}
TEST(SHA512HasherTest, StaticProperties) {
EXPECT_EQ(SHA512Hasher::name(), "SHA512");
EXPECT_EQ(SHA512Hasher::digest_length(), 128);
}