diff --git a/include/hash/hash.hpp b/include/hash/hash.hpp new file mode 100644 index 0000000..fcc029b --- /dev/null +++ b/include/hash/hash.hpp @@ -0,0 +1,10 @@ +#ifndef HASH_HPP +#define HASH_HPP + +#include +#include + +auto generate_hash(const std::string_view password, std::size_t rounds) -> std::string; +auto validate_password(const std::string_view password, const std::string_view hash) -> bool; + +#endif // HASH_HPP \ No newline at end of file diff --git a/include/jwt/jwt.h b/include/jwt/jwt.h index 657b141..dfd7933 100644 --- a/include/jwt/jwt.h +++ b/include/jwt/jwt.h @@ -6,11 +6,11 @@ extern "C" { #endif - char* create_token(const char* user, const char* secret); + auto create_token(const char* user_id, const char* secret) noexcept -> char*; - char* get_payload(const char* token); + auto get_payload(const char* token) noexcept -> char*; - int verify_token(const char* token, const char* secret); + auto verify_token(const char* token, const char* secret) noexcept -> int; #ifdef __cplusplus } diff --git a/src/hash/hash.cpp b/src/hash/hash.cpp new file mode 100644 index 0000000..59510c2 --- /dev/null +++ b/src/hash/hash.cpp @@ -0,0 +1,91 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace CryptoPP; + +auto to_byte_ptr(auto* p) noexcept -> const byte* +{ + return reinterpret_cast(p); +} + +auto generate_salt(size_t length = 16) -> std::string +{ + auto prng = AutoSeededRandomPool{}; + auto salt = std::vector(length); + prng.GenerateBlock(salt.data(), length); + auto salt_str = std::string{}; + auto encoder = HexEncoder{new StringSink(salt_str)}; + encoder.Put(salt.data(), length); + encoder.MessageEnd(); + return salt_str; +} + +auto hash_password_with_pbkdf2(const std::string_view password, const std::string_view salt, std::size_t iterations) + -> std::string +{ + auto decoded_salt = std::string{}; + auto decoder = HexDecoder{}; + decoder.Attach(new StringSink(decoded_salt)); + decoder.Put(to_byte_ptr(salt.data()), salt.size()); + decoder.MessageEnd(); + auto pbkdf2 = PKCS5_PBKDF2_HMAC{}; + auto derived_key = std::array{}; + pbkdf2.DeriveKey(derived_key.data(), + derived_key.size(), + 0, + to_byte_ptr(password.data()), + password.size(), + to_byte_ptr(decoded_salt.data()), + decoded_salt.size(), + iterations, + 0); + auto hashed_password = std::string{}; + auto encoder = HexEncoder{new StringSink(hashed_password)}; + encoder.Put(derived_key.data(), derived_key.size()); + encoder.MessageEnd(); + return hashed_password; +} + +auto generate_hash(const std::string_view password, std::size_t rounds) -> std::string +{ + auto salt = generate_salt(); + auto hashed_password = hash_password_with_pbkdf2(password, salt, rounds); + return std::format("{}${}${}", salt, rounds, hashed_password); +} + +auto validate_password(const std::string_view password, const std::string_view hash) -> bool +{ + auto first_delimiter = hash.find('$'); + auto second_delimiter = hash.find('$', first_delimiter + 1); + + if (first_delimiter == std::string::npos || second_delimiter == std::string::npos) { + return false; + } + + auto stored_salt = hash.substr(0, first_delimiter); + auto stored_rounds = std::size_t{}; + { + auto first = hash.data() + first_delimiter + 1; + auto last = hash.data() + second_delimiter; + auto [ptr, ec] = std::from_chars(first, last, stored_rounds); + if (ec != std::errc{}) return false; + } + auto stored_hashed_password = hash.substr(second_delimiter + 1); + + auto entered_hashed_password = hash_password_with_pbkdf2(password, stored_salt, stored_rounds); + + return entered_hashed_password == stored_hashed_password; +} + diff --git a/src/jwt/jwt.cpp b/src/jwt/jwt.cpp index 788e621..43485f7 100644 --- a/src/jwt/jwt.cpp +++ b/src/jwt/jwt.cpp @@ -8,7 +8,7 @@ using namespace std::literals::chrono_literals; extern "C" { - char* create_token(const char* user_id, const char* secret) + auto create_token(const char* user_id, const char* secret) noexcept -> char* { auto token = jwt::create() .set_type("JWS") @@ -21,14 +21,14 @@ extern "C" return strdup(token.c_str()); } - char* get_payload(const char* token) + auto get_payload(const char* token) noexcept -> char* { auto decoded_token = jwt::decode(token); auto payload = decoded_token.get_payload_claim("user_id").as_string(); return strdup(payload.c_str()); } - int verify_token(const char* token, const char* secret) + auto verify_token(const char* token, const char* secret) noexcept -> int { try { auto decoded_token = jwt::decode(token);