diff --git a/hmac_utils.cpp b/hmac_utils.cpp index da3fc2d..a3db0c7 100644 --- a/hmac_utils.cpp +++ b/hmac_utils.cpp @@ -88,6 +88,29 @@ namespace hmac { return false; } + namespace detail { + int hotp_from_digest(const std::vector& hmac_result, int digits) { + if (hmac_result.empty()) { + throw std::runtime_error("HOTP: HMAC result too short"); + } + int offset = hmac_result.back() & 0x0F; + if (hmac_result.size() < static_cast(offset) + 4) { + throw std::runtime_error("HOTP: HMAC result too short"); + } + uint32_t bin_code = + ((hmac_result[offset] & 0x7F) << 24) | + ((hmac_result[offset + 1] & 0xFF) << 16) | + ((hmac_result[offset + 2] & 0xFF) << 8) | + ((hmac_result[offset + 3] & 0xFF)); + static const uint64_t divisor[] = { + 10UL, 100UL, 1000UL, 10000UL, + 100000UL, 1000000UL, 10000000UL, + 100000000UL, 1000000000UL + }; + return bin_code % divisor[digits - 1]; + } + } + int get_hotp_code(const void* key_ptr, size_t key_len, uint64_t counter, int digits, TypeHash hash_type) { if (digits < 1 || digits > 9) throw std::invalid_argument("HOTP: digits must be in range [1, 9]"); @@ -101,21 +124,8 @@ namespace hmac { // Step 2: Compute HMAC std::vector hmac_result = hmac::get_hmac(key_ptr, key_len, counter_bytes, 8, hash_type); - // Step 3: Dynamic truncation - int offset = hmac_result.back() & 0x0F; - uint32_t bin_code = - ((hmac_result[offset] & 0x7F) << 24) | - ((hmac_result[offset + 1] & 0xFF) << 16) | - ((hmac_result[offset + 2] & 0xFF) << 8) | - ((hmac_result[offset + 3] & 0xFF)); - - // Step 4: Modulo to get N-digit code - static const uint64_t divisor[] = { - 10UL, 100UL, 1000UL, 10000UL, - 100000UL, 1000000UL, 10000000UL, - 100000000UL, 1000000000UL - }; - return bin_code % divisor[digits - 1]; + // Step 3: Dynamic truncation and modulo + return detail::hotp_from_digest(hmac_result, digits); } int get_totp_code_at( diff --git a/hmac_utils.hpp b/hmac_utils.hpp index 78d6811..f99e3b8 100644 --- a/hmac_utils.hpp +++ b/hmac_utils.hpp @@ -81,6 +81,15 @@ namespace hmac { inline int get_hotp_code(const std::string& key, uint64_t counter, int digits = 6, TypeHash hash_type = TypeHash::SHA1) { return get_hotp_code(key.data(), key.size(), counter, digits, hash_type); } + + namespace detail { + /// \brief Computes HOTP code from a precomputed HMAC digest + /// \param hmac_result HMAC digest bytes + /// \param digits Desired number of digits in the OTP (1-9) + /// \return One-Time Password (OTP) as an integer + /// \throws std::runtime_error if the digest is too short for dynamic truncation + int hotp_from_digest(const std::vector& hmac_result, int digits); + } /// \brief Computes TOTP (Time-Based One-Time Password) code for a specific timestamp /// Implements RFC 6238 diff --git a/test_all.cpp b/test_all.cpp index 0ca6002..73e35f1 100644 --- a/test_all.cpp +++ b/test_all.cpp @@ -131,6 +131,11 @@ TEST(HMACTest, InvalidTypeThrowsString) { EXPECT_THROW(hmac::get_hmac(key, msg, invalid), std::invalid_argument); } +TEST(HOTPTest, ShortDigestThrows) { + std::vector short_digest = {0x00, 0x01, 0x02}; + EXPECT_THROW(hmac::detail::hotp_from_digest(short_digest, 6), std::runtime_error); +} + TEST(TOTPTest, AtTime) { const std::string totp_key = "12345678901234567890"; uint64_t test_time = 1234567890;