From 9e70b1bf0b511c6194865d7575b1c6ad5c98bd22 Mon Sep 17 00:00:00 2001 From: Aster Seker Date: Fri, 5 Sep 2025 23:24:09 +0300 Subject: [PATCH] docs: show secure buffer for string keys --- README-RU.md | 20 ++- README.md | 17 +++ include/hmac_cpp/hmac.hpp | 18 ++- include/hmac_cpp/hmac_utils.hpp | 228 ++++++++++++++++++++++++++++- include/hmac_cpp/secure_buffer.hpp | 25 ++++ src/hmac.cpp | 99 +++++-------- src/hmac_utils.cpp | 33 +++-- 7 files changed, 350 insertions(+), 90 deletions(-) diff --git a/README-RU.md b/README-RU.md index 53bdeef..ad35559 100644 --- a/README-RU.md +++ b/README-RU.md @@ -85,10 +85,26 @@ std::string get_hmac( - `is_hex` — Возвращать hex-строку (`true`) или бинарные данные (`false`) [по умолчанию: true] - `is_upper` — Использовать верхний регистр (только для `hex`) [по умолчанию: false] -Возвращает: -Если `is_hex == true`, возвращает HMAC в виде hex-строки (`std::string`). +Возвращает: +Если `is_hex == true`, возвращает HMAC в виде hex-строки (`std::string`). Если `is_hex == false`, возвращает HMAC в виде бинарной строки (`std::string`, не предназначена для вывода). +#### Безопасная работа со строковыми ключами + +Если секретный ключ получен в виде `std::string` (например, API‑ключ биржи), +переместите его в `secure_buffer`, чтобы исходная строка сразу очистилась: + +```cpp +#include +#include + +std::string api_key = std::getenv("API_KEY"); +secure_buffer key(std::move(api_key)); // api_key очищена + +auto sig = hmac::get_hmac(key, payload, hmac::TypeHash::SHA256); +secure_zero(key); // при необходимости: очистить после использования +``` + ### HMAC (сырые бинарные данные) ```cpp diff --git a/README.md b/README.md index 9dee38a..2ffd551 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,23 @@ Returns: If `is_hex == true`, returns a hexadecimal string (`std::string`) of the HMAC. If `is_hex == false`, returns a raw binary HMAC as a `std::string` (not human-readable). +#### Secure handling of string keys + +When a secret key is obtained as a `std::string` (e.g. an API key from an exchange), +move it into a `secure_buffer` to erase the original string immediately: + +```cpp +#include +#include + +std::string api_key = std::getenv("API_KEY"); +secure_buffer key(std::move(api_key)); // api_key is zeroized + +std::vector sig = + hmac::get_hmac(key, payload, hmac::TypeHash::SHA256); +secure_zero(key); // optional: wipe after use +``` + ### HMAC (binary data: raw buffer) ```cpp diff --git a/include/hmac_cpp/hmac.hpp b/include/hmac_cpp/hmac.hpp index 2f6dbf1..7938d6d 100644 --- a/include/hmac_cpp/hmac.hpp +++ b/include/hmac_cpp/hmac.hpp @@ -2,9 +2,12 @@ #define _HMAC_HPP_INCLUDED #include +#include +#include #include "sha1.hpp" #include "sha256.hpp" #include "sha512.hpp" +#include "secure_buffer.hpp" namespace hmac_cpp { @@ -72,13 +75,24 @@ namespace hmac_cpp { } /// \brief Computes HMAC - /// \param key Secret key + /// \param key Secret key as byte vector /// \param msg Message /// \param type Hash function type /// \param is_hex Return result in hex format /// \param is_upper Use uppercase hex /// \return HMAC result - std::string get_hmac(const std::string& key_input, const std::string &msg, TypeHash type, bool is_hex = true, bool is_upper = false); + std::string get_hmac(const std::vector& key, const std::string &msg, TypeHash type, bool is_hex = true, bool is_upper = false); + + /// \brief Computes HMAC from secure_buffer key + inline std::string get_hmac(const secure_buffer& key, const std::string &msg, TypeHash type, bool is_hex = true, bool is_upper = false) { + return get_hmac(std::vector(key.begin(), key.end()), msg, type, is_hex, is_upper); + } + + /// \deprecated Prefer overloads that accept std::vector or secure_buffer. + HMACCPP_DEPRECATED("use std::vector or secure_buffer overload") + inline std::string get_hmac(const std::string& key_input, const std::string &msg, TypeHash type, bool is_hex = true, bool is_upper = false) { + return get_hmac(std::vector(key_input.begin(), key_input.end()), msg, type, is_hex, is_upper); + } } namespace hmac = hmac_cpp; diff --git a/include/hmac_cpp/hmac_utils.hpp b/include/hmac_cpp/hmac_utils.hpp index 1539904..20ab63d 100644 --- a/include/hmac_cpp/hmac_utils.hpp +++ b/include/hmac_cpp/hmac_utils.hpp @@ -2,6 +2,7 @@ #define _HMAC_UTILS_HPP_INCLUDED #include "hmac.hpp" +#include "secure_buffer.hpp" #include #include #include @@ -60,6 +61,8 @@ namespace hmac_cpp { } /// \brief Derives a key using PBKDF2 from string-based password and salt + /// \deprecated Use overloads that accept std::vector or secure_buffer. + HMACCPP_DEPRECATED("use std::vector or secure_buffer overload") inline std::vector pbkdf2( const std::string& password, const std::string& salt, @@ -70,6 +73,16 @@ namespace hmac_cpp { iterations, dk_len, prf); } + inline std::vector pbkdf2( + const secure_buffer& password, + const secure_buffer& salt, + uint32_t iterations, size_t dk_len, + Pbkdf2Hash prf = Pbkdf2Hash::Sha256) { + return pbkdf2(password.data(), password.size(), + salt.data(), salt.size(), + iterations, dk_len, prf); + } + /// \brief Derives PBKDF2-HMAC-SHA256 into caller-provided buffer /// \param password_ptr Pointer to the password buffer /// \param password_len Length of the password in bytes @@ -83,7 +96,9 @@ namespace hmac_cpp { const void* salt_ptr, size_t salt_len, uint32_t iterations, uint8_t* out_ptr, size_t dk_len) noexcept; + /// \deprecated Use overloads that accept std::vector or secure_buffer. template + HMACCPP_DEPRECATED("use std::vector or secure_buffer overload") inline bool pbkdf2_hmac_sha256(const std::string& password, const std::string& salt, uint32_t iterations, @@ -93,6 +108,50 @@ namespace hmac_cpp { iterations, out.data(), out.size()); } + /// \brief PBKDF2-HMAC-SHA256 with secure_buffer inputs. + /// \param password Password bytes. + /// \param salt Salt bytes. + /// \param iterations Number of iterations. + /// \param out_ptr Output buffer for derived key. + /// \param dk_len Length of output buffer in bytes. + /// \return true on success, false on invalid parameters. + inline bool pbkdf2_hmac_sha256(const secure_buffer& password, + const secure_buffer& salt, + uint32_t iterations, + uint8_t* out_ptr, size_t dk_len) noexcept { + return pbkdf2_hmac_sha256(password.data(), password.size(), + salt.data(), salt.size(), + iterations, out_ptr, dk_len); + } + + /// \brief PBKDF2-HMAC-SHA256 with secure_buffer inputs. + /// \tparam N Length of the output array. + /// \param password Password bytes. + /// \param salt Salt bytes. + /// \param iterations Number of iterations. + /// \param out Output array for derived key. + /// \return true on success, false on invalid parameters. + template + inline bool pbkdf2_hmac_sha256(const secure_buffer& password, + const secure_buffer& salt, + uint32_t iterations, + std::array& out) noexcept { + return pbkdf2_hmac_sha256(password.data(), password.size(), + salt.data(), salt.size(), + iterations, out.data(), out.size()); + } + + /// \brief Derives a key using PBKDF2 with an additional pepper value. + /// \param password_ptr Pointer to the password buffer. + /// \param password_len Length of the password in bytes. + /// \param salt_ptr Pointer to the salt buffer. + /// \param salt_len Length of the salt in bytes. + /// \param pepper_ptr Pointer to the pepper buffer. + /// \param pepper_len Length of the pepper in bytes. + /// \param iterations Number of iterations, must be positive. + /// \param dk_len Desired length of the derived key in bytes, must be positive. + /// \param prf Hash function to use (SHA1, SHA256, SHA512). + /// \return Derived key as a vector of bytes. std::vector pbkdf2_with_pepper( const void* password_ptr, size_t password_len, const void* salt_ptr, size_t salt_len, @@ -100,6 +159,15 @@ namespace hmac_cpp { uint32_t iterations, size_t dk_len, Pbkdf2Hash prf = Pbkdf2Hash::Sha256); + /// \brief PBKDF2 with pepper using vector-based inputs. + /// \tparam T Byte type; must be char or uint8_t. + /// \param password Password bytes. + /// \param salt Salt bytes. + /// \param pepper Pepper bytes. + /// \param iterations Number of iterations. + /// \param dk_len Desired length of the derived key in bytes. + /// \param prf Hash function to use. + /// \return Derived key as a vector of bytes. template inline std::vector pbkdf2_with_pepper( const std::vector& password, @@ -115,6 +183,9 @@ namespace hmac_cpp { iterations, dk_len, prf); } + /// \brief Derives a key using PBKDF2 with pepper from string inputs + /// \deprecated Use overloads that accept std::vector or secure_buffer. + HMACCPP_DEPRECATED("use std::vector or secure_buffer overload") inline std::vector pbkdf2_with_pepper( const std::string& password, const std::string& salt, @@ -127,6 +198,32 @@ namespace hmac_cpp { iterations, dk_len, prf); } + /// \brief Derives a key using PBKDF2 with pepper from secure buffers. + /// \param password Password bytes. + /// \param salt Salt bytes. + /// \param pepper Pepper bytes. + /// \param iterations Number of iterations. + /// \param dk_len Desired length of the derived key in bytes. + /// \param prf Hash function to use. + /// \return Derived key as a vector of bytes. + inline std::vector pbkdf2_with_pepper( + const secure_buffer& password, + const secure_buffer& salt, + const secure_buffer& pepper, + uint32_t iterations, size_t dk_len, + Pbkdf2Hash prf = Pbkdf2Hash::Sha256) { + return pbkdf2_with_pepper(password.data(), password.size(), + salt.data(), salt.size(), + pepper.data(), pepper.size(), + iterations, dk_len, prf); + } + + /// \brief HKDF extract step using SHA-256. + /// \param ikm_ptr Pointer to input keying material. + /// \param ikm_len Length of the input keying material. + /// \param salt_ptr Pointer to optional salt buffer (may be null when salt_len is 0). + /// \param salt_len Length of the salt in bytes. + /// \return Pseudorandom key (PRK) as a byte vector. std::vector hkdf_extract_sha256( const void* ikm_ptr, size_t ikm_len, const void* salt_ptr, size_t salt_len); @@ -137,6 +234,13 @@ namespace hmac_cpp { return hkdf_extract_sha256(ikm.data(), ikm.size(), salt.data(), salt.size()); } + /// \brief HKDF expand step using SHA-256. + /// \param prk_ptr Pointer to the pseudorandom key. + /// \param prk_len Length of the pseudorandom key. + /// \param info_ptr Optional context and application specific information (can be null). + /// \param info_len Length of the info buffer in bytes. + /// \param L Length of output keying material in bytes. + /// \return Output keying material as a byte vector. std::vector hkdf_expand_sha256( const void* prk_ptr, size_t prk_len, const void* info_ptr, size_t info_len, @@ -149,11 +253,19 @@ namespace hmac_cpp { return hkdf_expand_sha256(prk.data(), prk.size(), info.data(), info.size(), L); } + /// \brief Holds a 32-byte key and 12-byte IV produced by HKDF. struct KeyIv { - std::array key; - std::array iv; + std::array key; ///< Derived symmetric key + std::array iv; ///< Derived initialization vector }; + /// \brief Derives a 32-byte key and 12-byte IV using HKDF-SHA256. + /// \param ikm_ptr Pointer to input keying material. + /// \param ikm_len Length of the input keying material. + /// \param salt_ptr Pointer to the salt buffer. + /// \param salt_len Length of the salt in bytes. + /// \param context Application-specific context string. + /// \return Struct containing the derived key and IV. KeyIv hkdf_key_iv_256(const void* ikm_ptr, size_t ikm_len, const void* salt_ptr, size_t salt_len, const std::string& context); @@ -170,7 +282,15 @@ namespace hmac_cpp { /// \param hash_type Hash function to use (SHA1, SHA256, SHA512). Default is SHA256 /// \return Hex-encoded HMAC-SHA256 of the rounded time value /// \throws std::runtime_error if the system time cannot be retrieved - std::string generate_time_token(const std::string &key, int interval_sec = 60, TypeHash hash_type = TypeHash::SHA256); + std::string generate_time_token(const std::vector& key, int interval_sec = 60, TypeHash hash_type = TypeHash::SHA256); + inline std::string generate_time_token(const secure_buffer& key, int interval_sec = 60, TypeHash hash_type = TypeHash::SHA256) { + return generate_time_token(std::vector(key.begin(), key.end()), interval_sec, hash_type); + } + /// \deprecated Prefer overloads that accept std::vector or secure_buffer. + HMACCPP_DEPRECATED("use std::vector or secure_buffer overload") + inline std::string generate_time_token(const std::string &key, int interval_sec = 60, TypeHash hash_type = TypeHash::SHA256) { + return generate_time_token(std::vector(key.begin(), key.end()), interval_sec, hash_type); + } /// \brief Validates a time-based HMAC-SHA256 token with ±1 interval tolerance /// \param token Token received from the client @@ -179,7 +299,15 @@ namespace hmac_cpp { /// \param hash_type Hash function to use (SHA1, SHA256, SHA512). Default is SHA256 /// \return true if the token is valid within the ±1 interval range; false otherwise /// \throws std::runtime_error if the system time cannot be retrieved - bool is_token_valid(const std::string &token, const std::string &key, int interval_sec = 60, TypeHash hash_type = TypeHash::SHA256); + bool is_token_valid(const std::string &token, const std::vector& key, int interval_sec = 60, TypeHash hash_type = TypeHash::SHA256); + inline bool is_token_valid(const std::string &token, const secure_buffer& key, int interval_sec = 60, TypeHash hash_type = TypeHash::SHA256) { + return is_token_valid(token, std::vector(key.begin(), key.end()), interval_sec, hash_type); + } + /// \deprecated Prefer overloads that accept std::vector or secure_buffer. + HMACCPP_DEPRECATED("use std::vector or secure_buffer overload") + inline bool is_token_valid(const std::string &token, const std::string &key, int interval_sec = 60, TypeHash hash_type = TypeHash::SHA256) { + return is_token_valid(token, std::vector(key.begin(), key.end()), interval_sec, hash_type); + } /// \brief Generates a time-based HMAC-SHA256 token with fingerprint binding /// \param key Secret key used for HMAC @@ -188,7 +316,15 @@ namespace hmac_cpp { /// \param hash_type Hash function to use (SHA1, SHA256, SHA512). Default is SHA256 /// \return Hex-encoded HMAC-SHA256 of the concatenated timestamp and fingerprint /// \throws std::runtime_error if the system time cannot be retrieved - std::string generate_time_token(const std::string &key, const std::string &fingerprint, int interval_sec = 60, TypeHash hash_type = TypeHash::SHA256); + std::string generate_time_token(const std::vector& key, const std::string &fingerprint, int interval_sec = 60, TypeHash hash_type = TypeHash::SHA256); + inline std::string generate_time_token(const secure_buffer& key, const std::string &fingerprint, int interval_sec = 60, TypeHash hash_type = TypeHash::SHA256) { + return generate_time_token(std::vector(key.begin(), key.end()), fingerprint, interval_sec, hash_type); + } + /// \deprecated Prefer overloads that accept std::vector or secure_buffer. + HMACCPP_DEPRECATED("use std::vector or secure_buffer overload") + inline std::string generate_time_token(const std::string &key, const std::string &fingerprint, int interval_sec = 60, TypeHash hash_type = TypeHash::SHA256) { + return generate_time_token(std::vector(key.begin(), key.end()), fingerprint, interval_sec, hash_type); + } /// \brief Validates a fingerprint-bound HMAC-SHA256 token with ±1 interval tolerance /// \param token Token received from the client @@ -198,7 +334,15 @@ namespace hmac_cpp { /// \param hash_type Hash function to use (SHA1, SHA256, SHA512). Default is SHA256 /// \return true if the token is valid within the ±1 interval range; false otherwise /// \throws std::runtime_error if the system time cannot be retrieved - bool is_token_valid(const std::string &token, const std::string &key, const std::string &fingerprint, int interval_sec = 60, TypeHash hash_type = TypeHash::SHA256); + bool is_token_valid(const std::string &token, const std::vector& key, const std::string &fingerprint, int interval_sec = 60, TypeHash hash_type = TypeHash::SHA256); + inline bool is_token_valid(const std::string &token, const secure_buffer& key, const std::string &fingerprint, int interval_sec = 60, TypeHash hash_type = TypeHash::SHA256) { + return is_token_valid(token, std::vector(key.begin(), key.end()), fingerprint, interval_sec, hash_type); + } + /// \deprecated Prefer overloads that accept std::vector or secure_buffer. + HMACCPP_DEPRECATED("use std::vector or secure_buffer overload") + inline bool is_token_valid(const std::string &token, const std::string &key, const std::string &fingerprint, int interval_sec = 60, TypeHash hash_type = TypeHash::SHA256) { + return is_token_valid(token, std::vector(key.begin(), key.end()), fingerprint, interval_sec, hash_type); + } /// \brief Computes HOTP code based on HMAC as defined in RFC 4226 /// \param key_ptr Pointer to the secret key (raw byte buffer) @@ -223,12 +367,18 @@ namespace hmac_cpp { return get_hotp_code(key.data(), key.size(), counter, digits, hash_type); } + inline int get_hotp_code(const secure_buffer& key, uint64_t counter, int digits = 6, TypeHash hash_type = TypeHash::SHA1) { + return get_hotp_code(key.data(), key.size(), counter, digits, hash_type); + } + /// \brief Computes HOTP code from a std::string key interpreted as raw bytes /// \param key Secret key as a binary string (each character is a byte) /// \param counter 64-bit moving counter (monotonically increasing) /// \param digits Desired number of digits in the OTP (typically 6–8, max 9) /// \param hash_type Hash function to use (SHA1, SHA256, SHA512). Default is SHA1 /// \return One-Time Password (OTP) as an integer in the range [0, 10^digits) + /// \deprecated Use overloads that accept std::vector or secure_buffer. + HMACCPP_DEPRECATED("use std::vector or secure_buffer overload") 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); } @@ -274,10 +424,19 @@ namespace hmac_cpp { const std::vector& key, uint64_t timestamp, int period = 30, - int digits = 6, + int digits = 6, TypeHash hash_type = TypeHash::SHA1) { static_assert(std::is_same::value || std::is_same::value, - "get_totp_code_at(vector) supports only char or uint8_t"); + "get_totp_code_at(vector) supports only char or uint8_t"); + return get_totp_code_at(key.data(), key.size(), timestamp, period, digits, hash_type); + } + + inline int get_totp_code_at( + const secure_buffer& key, + uint64_t timestamp, + int period = 30, + int digits = 6, + TypeHash hash_type = TypeHash::SHA1) { return get_totp_code_at(key.data(), key.size(), timestamp, period, digits, hash_type); } @@ -289,6 +448,8 @@ namespace hmac_cpp { /// \param hash_type Hash function to use (default: SHA1) /// \return TOTP code as an integer /// \throws std::invalid_argument if period <= 0 or digits not in [1,9] + /// \deprecated Use overloads that accept std::vector or secure_buffer. + HMACCPP_DEPRECATED("use std::vector or secure_buffer overload") inline int get_totp_code_at( const std::string& key, uint64_t timestamp, @@ -328,6 +489,10 @@ namespace hmac_cpp { return get_totp_code(key.data(), key.size(), period, digits, hash_type); } + inline int get_totp_code(const secure_buffer& key, int period = 30, int digits = 6, TypeHash hash_type = TypeHash::SHA1) { + return get_totp_code(key.data(), key.size(), period, digits, hash_type); + } + /// \brief Computes current TOTP code from a string-based key using system time (UTC) /// \param key Secret key as a binary string /// \param period Time step in seconds (default: 30) @@ -336,6 +501,8 @@ namespace hmac_cpp { /// \return TOTP code as an integer /// \throws std::invalid_argument if period <= 0 or digits not in [1,9] /// \throws std::runtime_error if the system time cannot be retrieved + /// \deprecated Use overloads that accept std::vector or secure_buffer. + HMACCPP_DEPRECATED("use std::vector or secure_buffer overload") inline int get_totp_code(const std::string& key, int period = 30, int digits = 6, TypeHash hash_type = TypeHash::SHA1) { return get_totp_code(key.data(), key.size(), period, digits, hash_type); } @@ -385,6 +552,27 @@ namespace hmac_cpp { "is_totp_token_valid(vector) only supports vector or vector"); return is_totp_token_valid(token, key.data(), key.size(), timestamp, period, digits, hash_type); } + + /// \brief Validates a TOTP token with ±1 time step tolerance + /// \param token OTP code to validate + /// \param key Secret key bytes + /// \param timestamp Unix timestamp in seconds + /// \param period Time step in seconds (default: 30) + /// \param digits Number of digits in the OTP (default: 6) + /// \param hash_type Hash function to use (default: SHA1) + /// \return true if the token is valid within [-1, 0, +1] time step range; false otherwise. + /// The +1 step check is skipped when the computed counter equals + /// std::numeric_limits::max(). + /// \throws std::invalid_argument if period <= 0 or digits not in [1,9] + inline bool is_totp_token_valid( + int token, + const secure_buffer& key, + uint64_t timestamp, + int period = 30, + int digits = 6, + TypeHash hash_type = TypeHash::SHA1) { + return is_totp_token_valid(token, key.data(), key.size(), timestamp, period, digits, hash_type); + } /// \brief Validates a TOTP token with ±1 time step tolerance /// \param token OTP code to validate @@ -397,6 +585,8 @@ namespace hmac_cpp { /// The +1 step check is skipped when the computed counter equals /// std::numeric_limits::max(). /// \throws std::invalid_argument if period <= 0 or digits not in [1,9] + /// \deprecated Use overloads that accept std::vector or secure_buffer. + HMACCPP_DEPRECATED("use std::vector or secure_buffer overload") inline bool is_totp_token_valid( int token, const std::string& key, @@ -450,6 +640,26 @@ namespace hmac_cpp { "is_totp_token_valid(vector) only supports vector or vector"); return is_totp_token_valid(token, key.data(), key.size(), period, digits, hash_type); } + + /// \brief Validates a TOTP token with ±1 time step tolerance using current system time + /// \param token OTP code to validate + /// \param key Secret key bytes + /// \param period Time step in seconds (default: 30) + /// \param digits Number of digits in the OTP (default: 6) + /// \param hash_type Hash function to use (default: SHA1) + /// \return true if the token is valid within [-1, 0, +1] time step range; false otherwise. + /// The +1 step check is skipped when the computed counter equals + /// std::numeric_limits::max(). + /// \throws std::invalid_argument if period <= 0 or digits not in [1,9] + /// \throws std::runtime_error if the system time cannot be retrieved + inline bool is_totp_token_valid( + int token, + const secure_buffer& key, + int period = 30, + int digits = 6, + TypeHash hash_type = TypeHash::SHA1) { + return is_totp_token_valid(token, key.data(), key.size(), period, digits, hash_type); + } /// \brief Validates a TOTP token with ±1 time step tolerance using current system time /// \param token OTP code to validate @@ -462,6 +672,8 @@ namespace hmac_cpp { /// std::numeric_limits::max(). /// \throws std::invalid_argument if period <= 0 or digits not in [1,9] /// \throws std::runtime_error if the system time cannot be retrieved + /// \deprecated Use overloads that accept std::vector or secure_buffer. + HMACCPP_DEPRECATED("use std::vector or secure_buffer overload") inline bool is_totp_token_valid( int token, const std::string& key, diff --git a/include/hmac_cpp/secure_buffer.hpp b/include/hmac_cpp/secure_buffer.hpp index 6df07f7..09ee259 100644 --- a/include/hmac_cpp/secure_buffer.hpp +++ b/include/hmac_cpp/secure_buffer.hpp @@ -4,9 +4,24 @@ #include #include #include +#include + +// Macro to mark deprecated APIs in a compiler-portable way +#ifndef HMACCPP_DEPRECATED +#if defined(__clang__) || defined(__GNUC__) +#define HMACCPP_DEPRECATED(msg) __attribute__((deprecated(msg))) +#elif defined(_MSC_VER) +#define HMACCPP_DEPRECATED(msg) __declspec(deprecated(msg)) +#else +#define HMACCPP_DEPRECATED(msg) +#endif +#endif namespace hmac_cpp { +/// \brief Securely zeroes a memory region. +/// \param ptr Pointer to the memory to wipe. +/// \param len Number of bytes to set to zero. inline void secure_zero(void* ptr, size_t len) { volatile unsigned char* p = static_cast(ptr); while (len--) { @@ -14,6 +29,8 @@ inline void secure_zero(void* ptr, size_t len) { } } +/// \brief Vector-like buffer that zeroizes its contents on destruction. +/// \tparam T Trivial value type stored in the buffer (defaults to uint8_t). template struct secure_buffer { static_assert(std::is_trivial::value, "secure_buffer requires trivial type"); @@ -21,6 +38,14 @@ struct secure_buffer { secure_buffer() = default; explicit secure_buffer(size_t n) : buf(n) {} explicit secure_buffer(std::vector&& v) : buf(std::move(v)) {} + /// \brief Constructs from std::string rvalue and zeroizes the source. + template::value, int>::type = 0> + explicit secure_buffer(std::string&& s) : buf(s.begin(), s.end()) { + if (!s.empty()) { + secure_zero(&s[0], s.size()); + s.clear(); + } + } ~secure_buffer() { secure_zero(buf.data(), buf.size() * sizeof(T)); } T* data() { return buf.data(); } diff --git a/src/hmac.cpp b/src/hmac.cpp index f672a0a..72e4409 100644 --- a/src/hmac.cpp +++ b/src/hmac.cpp @@ -1,7 +1,9 @@ #include #include #include +#include #include "hmac_cpp/hmac.hpp" +#include "hmac_cpp/secure_buffer.hpp" namespace hmac_cpp { @@ -107,83 +109,56 @@ namespace hmac_cpp { throw std::invalid_argument("Unsupported hash type"); } - // Step 1: Normalize key - std::vector key(reinterpret_cast(key_ptr), reinterpret_cast(key_ptr) + key_len); - if (key.size() > block_size) - key = get_hash(key.data(), key.size(), type); - if (key.size() < block_size) - key.resize(block_size, 0); + secure_buffer key(block_size); + if (key_len > block_size) { + auto hashed = get_hash(key_ptr, key_len, type); + std::copy(hashed.begin(), hashed.end(), key.begin()); + if (hashed.size() < block_size) + std::fill(key.begin() + hashed.size(), key.end(), 0); + secure_zero(hashed.data(), hashed.size()); + } else { + std::memcpy(key.data(), key_ptr, key_len); + if (key_len < block_size) + std::fill(key.begin() + key_len, key.end(), 0); + } - // Step 2: Create ipad and opad in one pass - std::vector ikeypad(block_size); - std::vector okeypad(block_size); + secure_buffer ikeypad(block_size); + secure_buffer okeypad(block_size); for (size_t i = 0; i < block_size; ++i) { const uint8_t k = key[i]; ikeypad[i] = k ^ 0x36; okeypad[i] = k ^ 0x5c; } - // Step 3: Compute inner hash if (msg_len > SIZE_MAX - block_size) throw std::overflow_error("msg_len + block_size overflow"); - std::vector inner_data; - inner_data.reserve(block_size + msg_len); - inner_data.insert(inner_data.end(), ikeypad.begin(), ikeypad.end()); - inner_data.insert(inner_data.end(), reinterpret_cast(msg_ptr), reinterpret_cast(msg_ptr) + msg_len); - std::vector inner_hash = get_hash(inner_data.data(), inner_data.size(), type); + secure_buffer inner_data(block_size + msg_len); + std::copy(ikeypad.begin(), ikeypad.end(), inner_data.begin()); + std::memcpy(inner_data.data() + block_size, msg_ptr, msg_len); + secure_buffer inner_hash(std::move(get_hash(inner_data.data(), inner_data.size(), type))); - // Step 4: Compute final HMAC if (digest_size > SIZE_MAX - block_size) throw std::overflow_error("digest_size + block_size overflow"); - std::vector outer_data; - outer_data.reserve(block_size + digest_size); - outer_data.insert(outer_data.end(), okeypad.begin(), okeypad.end()); - outer_data.insert(outer_data.end(), inner_hash.begin(), inner_hash.end()); + secure_buffer outer_data(block_size + digest_size); + std::copy(okeypad.begin(), okeypad.end(), outer_data.begin()); + std::copy(inner_hash.begin(), inner_hash.end(), outer_data.begin() + block_size); - return get_hash(outer_data.data(), outer_data.size(), type); - } + auto result = get_hash(outer_data.data(), outer_data.size(), type); - std::string get_hmac(const std::string& key_input, const std::string &msg, TypeHash type, bool is_hex, bool is_upper) { - size_t block_size = 0; - switch(type) { - case TypeHash::SHA1: - block_size = hmac_hash::SHA1::BLOCK_SIZE; - break; - case TypeHash::SHA256: - block_size = hmac_hash::SHA256::SHA224_256_BLOCK_SIZE; - break; - case TypeHash::SHA512: - block_size = hmac_hash::SHA512::SHA384_512_BLOCK_SIZE; - break; - default: - throw std::invalid_argument("Unsupported hash type"); - }; + secure_zero(key.data(), key.size()); + secure_zero(ikeypad.data(), ikeypad.size()); + secure_zero(okeypad.data(), okeypad.size()); + secure_zero(inner_data.data(), inner_data.size()); + secure_zero(inner_hash.data(), inner_hash.size()); + secure_zero(outer_data.data(), outer_data.size()); - std::string key = key_input; - - if(key.size() > block_size) { - /* If key length > block size, hash it and pad with zeros to block size */ - key = get_hash(key, type); - } - if(key.size() < block_size) { - /* Pad key with zeros if it's shorter than block size */ - key.resize(block_size, '\0'); - } - - /* If key length == block size, use it as is */ - std::string ikeypad; - std::string okeypad; - - ikeypad.reserve(block_size); - okeypad.reserve(block_size); - - for(size_t i = 0; i < block_size; ++i) { - ikeypad.push_back(0x36 ^ key[i]); - okeypad.push_back(0x5c ^ key[i]); - } + return result; + } - return is_hex - ? to_hex(get_hash(okeypad + get_hash(ikeypad + msg, type), type), is_upper) - : get_hash(okeypad + get_hash(ikeypad + msg, type), type); + std::string get_hmac(const std::vector& key_input, const std::string &msg, TypeHash type, bool is_hex, bool is_upper) { + auto hmac_vec = get_hmac(key_input.data(), key_input.size(), msg.data(), msg.size(), type); + std::string out(reinterpret_cast(hmac_vec.data()), hmac_vec.size()); + secure_zero(hmac_vec.data(), hmac_vec.size()); + return is_hex ? to_hex(out, is_upper) : out; } } diff --git a/src/hmac_utils.cpp b/src/hmac_utils.cpp index b864e0a..c01032f 100644 --- a/src/hmac_utils.cpp +++ b/src/hmac_utils.cpp @@ -85,13 +85,13 @@ namespace hmac_cpp { salt_block[salt_len + 2] = static_cast((i >> 8) & 0xFF); salt_block[salt_len + 3] = static_cast(i & 0xFF); - std::vector u = get_hmac(password_ptr, password_len, - salt_block.data(), salt_block.size(), - hash_type); - std::vector t = u; + secure_buffer u(std::move(get_hmac(password_ptr, password_len, + salt_block.data(), salt_block.size(), + hash_type))); + secure_buffer t = u; for (uint32_t j = 1; j < iterations; ++j) { - u = get_hmac(password_ptr, password_len, - u.data(), u.size(), hash_type); + u = secure_buffer(get_hmac(password_ptr, password_len, + u.data(), u.size(), hash_type)); for (size_t k = 0; k < t.size(); ++k) { t[k] ^= u[k]; } @@ -141,13 +141,13 @@ namespace hmac_cpp { salt_block[salt_len + 2] = static_cast((i >> 8) & 0xFF); salt_block[salt_len + 3] = static_cast(i & 0xFF); - std::vector u = get_hmac(password_ptr, password_len, - salt_block.data(), salt_block.size(), - TypeHash::SHA256); - std::vector t = u; + secure_buffer u(std::move(get_hmac(password_ptr, password_len, + salt_block.data(), salt_block.size(), + TypeHash::SHA256))); + secure_buffer t = u; for (uint32_t j = 1; j < iterations; ++j) { - u = get_hmac(password_ptr, password_len, - u.data(), u.size(), TypeHash::SHA256); + u = secure_buffer(get_hmac(password_ptr, password_len, + u.data(), u.size(), TypeHash::SHA256)); for (size_t k = 0; k < t.size(); ++k) { t[k] ^= u[k]; } @@ -172,6 +172,7 @@ namespace hmac_cpp { auto pwd_prime = get_hmac(pepper_ptr, pepper_len, password_ptr, password_len, hash_type); secure_buffer tmp(std::move(pwd_prime)); auto dk = pbkdf2(tmp.data(), tmp.size(), salt_ptr, salt_len, iterations, dk_len, prf); + secure_zero(tmp.data(), tmp.size()); return dk; } @@ -234,7 +235,7 @@ namespace hmac_cpp { return out; } - std::string generate_time_token(const std::string &key, int interval_sec, TypeHash hash_type) { + std::string generate_time_token(const std::vector &key, int interval_sec, TypeHash hash_type) { if (interval_sec <= 0) { throw std::invalid_argument("interval_sec must be positive"); } @@ -247,7 +248,7 @@ namespace hmac_cpp { return get_hmac(key, std::to_string(rounded), hash_type); } - bool is_token_valid(const std::string &token, const std::string &key, int interval_sec, TypeHash hash_type) { + bool is_token_valid(const std::string &token, const std::vector &key, int interval_sec, TypeHash hash_type) { if (interval_sec <= 0) { throw std::invalid_argument("interval_sec must be positive"); } @@ -267,7 +268,7 @@ namespace hmac_cpp { return false; } - std::string generate_time_token(const std::string &key, const std::string &fingerprint, int interval_sec, TypeHash hash_type) { + std::string generate_time_token(const std::vector &key, const std::string &fingerprint, int interval_sec, TypeHash hash_type) { if (interval_sec <= 0) { throw std::invalid_argument("interval_sec must be positive"); } @@ -281,7 +282,7 @@ namespace hmac_cpp { return get_hmac(key, payload, hash_type); } - bool is_token_valid(const std::string &token, const std::string &key, const std::string &fingerprint, int interval_sec, TypeHash hash_type) { + bool is_token_valid(const std::string &token, const std::vector &key, const std::string &fingerprint, int interval_sec, TypeHash hash_type) { if (interval_sec <= 0) { throw std::invalid_argument("interval_sec must be positive"); }