Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ set(HMAC_HEADERS
include/hmac_cpp/sha1.hpp
include/hmac_cpp/sha256.hpp
include/hmac_cpp/sha512.hpp
include/hmac_cpp/secure_buffer.hpp
)

add_library(hmac_cpp STATIC ${HMAC_SOURCES})
Expand Down Expand Up @@ -68,8 +69,9 @@ if(BUILD_TESTS)
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
)
FetchContent_MakeAvailable(googletest)
find_package(OpenSSL REQUIRED)
add_executable(test_all test_all.cpp)
target_link_libraries(test_all PRIVATE hmac_cpp gtest_main)
target_link_libraries(test_all PRIVATE hmac_cpp gtest_main OpenSSL::Crypto)
target_include_directories(test_all PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
add_test(NAME test_all COMMAND test_all)

Expand Down
13 changes: 7 additions & 6 deletions README-RU.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- Поддержка `HMAC` на основе `SHA256`, `SHA512`, `SHA1`
- Прямая работа с бинарным или hex-форматом
- Поддержка **PBKDF2** (RFC 8018)
- Поддержка **HKDF** (RFC 5869) для извлечения и расширения ключей
- Поддержка **временных токенов**:
- **HOTP (RFC 4226)** — счётчики
- **TOTP (RFC 6238)** — временные токены
Expand Down Expand Up @@ -137,16 +138,16 @@ std::vector<uint8_t> get_hmac(
#include <hmac_cpp/hmac_utils.hpp>

std::string password = "password";
std::string salt = "salt";
std::vector<uint8_t> dk = hmac::pbkdf2(password, salt, 1000, 32, hmac::TypeHash::SHA256);
std::vector<uint8_t> salt(16, 0x01);
std::vector<uint8_t> dk = hmac::pbkdf2(password, salt, 1000, 32);
```

Параметры:

- `password`, `salt` — строки с паролем и солью
- `iterations` — число итераций
- `dk_len` — длина ключа в байтах
- `hash_type` — хеш-функция (`SHA1`, `SHA256`, `SHA512`)
- `password`, `salt` — байтовые строки; `salt` должна быть уникальной и не короче 16 байт
- `iterations` — число итераций (≥1), подбирается по [рекомендациям OWASP](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html)
- `dk_len` — длина ключа в байтах, не более `(2^32−1) * hLen`
- `prf` — выбор хеша (`Sha1`, `Sha256` по умолчанию, `Sha512`)

### 🕓 HOTP и TOTP токены

Expand Down
28 changes: 22 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ A lightweight `C++11` library for computing `HMAC` (hash-based message authentic
- Supports `HMAC` using `SHA256`, `SHA512`, `SHA1`
- Outputs in binary or hex format
- Provides **PBKDF2 key derivation** (RFC 8018)
- Implements **HKDF (RFC 5869)** for key extraction/expansion
- Support for **time-based tokens**:
- **HOTP (RFC 4226)** — counter-based one-time passwords
- **TOTP (RFC 6238)** — time-based one-time passwords
Expand Down Expand Up @@ -161,16 +162,31 @@ Returns: Binary digest as `std::vector<uint8_t>`
#include <hmac_cpp/hmac_utils.hpp>

std::string password = "password";
std::string salt = "salt";
std::vector<uint8_t> dk = hmac::pbkdf2(password, salt, 1000, 32, hmac::TypeHash::SHA256);
std::vector<uint8_t> salt(16, 0x01); // at least 16 bytes
std::vector<uint8_t> dk = hmac::pbkdf2(password, salt, 1000, 32);
```

Parameters:

- `password`, `salt` — Raw byte strings
- `iterations` — Number of iterations
- `dk_len` — Desired key length in bytes
- `hash_type` — Hash function (`SHA1`, `SHA256`, `SHA512`)
- `password`, `salt` — Raw byte arrays. `salt` must be **>=16 bytes** and unique.
- `iterations` — Number of iterations (>=1). Tune according to the
[OWASP Password Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html).
- `dk_len` — Desired key length in bytes, up to `(2^32-1) * hLen` (RFC 8018).
- `prf` — Optional hash (`Sha1`, `Sha256` default, `Sha512`).

⚠️ Low iteration counts or short salts reduce security.

For deployments with a server-side *pepper*, use `pbkdf2_with_pepper(password, salt, pepper, iters, dkLen)`.
The pepper is a secret key stored separately from the hashed password.

### HKDF (RFC 5869)

```cpp
std::vector<uint8_t> ikm = {/* secret material */};
std::vector<uint8_t> salt(16, 0x00);
auto prk = hmac::hkdf_extract_sha256(ikm, salt);
auto okm = hmac::hkdf_expand_sha256(prk, {}, 32); // derive 32 bytes
```

### 🕓 HOTP and TOTP Tokens

Expand Down
114 changes: 101 additions & 13 deletions include/hmac_cpp/hmac_utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,33 @@
#define _HMAC_UTILS_HPP_INCLUDED

#include "hmac.hpp"
#include <array>
#include <string>
#include <vector>

namespace hmac_cpp {

/// \brief Compares two strings in constant time
/// \param a First string
/// \param b Second string
/// \return true if both strings are equal
bool constant_time_equals(const std::string &a, const std::string &b);
/// \brief Compares two byte arrays in constant time
/// \param a Pointer to first array
/// \param a_len Length of the first array
/// \param b Pointer to second array
/// \param b_len Length of the second array
/// \return true if both arrays are equal
bool constant_time_equals(const uint8_t* a, size_t a_len,
const uint8_t* b, size_t b_len);

inline bool constant_time_equals(const std::vector<uint8_t>& a,
const std::vector<uint8_t>& b) {
return constant_time_equals(a.data(), a.size(), b.data(), b.size());
}

inline bool constant_time_equals(const std::string &a, const std::string &b) {
return constant_time_equals(reinterpret_cast<const uint8_t*>(a.data()), a.size(),
reinterpret_cast<const uint8_t*>(b.data()), b.size());
}

/// \brief Hash choices for PBKDF2
enum class Pbkdf2Hash { Sha1, Sha256, Sha512 };

/// \brief Derives a key from a password using PBKDF2 (RFC 8018)
/// \param password_ptr Pointer to the password buffer
Expand All @@ -25,32 +42,103 @@ namespace hmac_cpp {
std::vector<uint8_t> pbkdf2(
const void* password_ptr, size_t password_len,
const void* salt_ptr, size_t salt_len,
int iterations, size_t dk_len,
TypeHash hash_type);
uint32_t iterations, size_t dk_len,
Pbkdf2Hash prf = Pbkdf2Hash::Sha256);

/// \brief Derives a key using PBKDF2 from vector-based password and salt
template<typename T>
inline std::vector<uint8_t> pbkdf2(
const std::vector<T>& password,
const std::vector<T>& salt,
int iterations, size_t dk_len,
TypeHash hash_type) {
uint32_t iterations, size_t dk_len,
Pbkdf2Hash prf = Pbkdf2Hash::Sha256) {
static_assert(std::is_same<T, char>::value || std::is_same<T, uint8_t>::value,
"pbkdf2(vector<T>) supports only char or uint8_t");
return pbkdf2(password.data(), password.size(),
salt.data(), salt.size(),
iterations, dk_len, hash_type);
iterations, dk_len, prf);
}

/// \brief Derives a key using PBKDF2 from string-based password and salt
inline std::vector<uint8_t> pbkdf2(
const std::string& password,
const std::string& salt,
int iterations, size_t dk_len,
TypeHash hash_type) {
uint32_t iterations, size_t dk_len,
Pbkdf2Hash prf = Pbkdf2Hash::Sha256) {
return pbkdf2(password.data(), password.size(),
salt.data(), salt.size(),
iterations, dk_len, hash_type);
iterations, dk_len, prf);
}

std::vector<uint8_t> pbkdf2_with_pepper(
const void* password_ptr, size_t password_len,
const void* salt_ptr, size_t salt_len,
const void* pepper_ptr, size_t pepper_len,
uint32_t iterations, size_t dk_len,
Pbkdf2Hash prf = Pbkdf2Hash::Sha256);

template<typename T>
inline std::vector<uint8_t> pbkdf2_with_pepper(
const std::vector<T>& password,
const std::vector<T>& salt,
const std::vector<T>& pepper,
uint32_t iterations, size_t dk_len,
Pbkdf2Hash prf = Pbkdf2Hash::Sha256) {
static_assert(std::is_same<T, char>::value || std::is_same<T, uint8_t>::value,
"pbkdf2_with_pepper(vector<T>) supports only char or uint8_t");
return pbkdf2_with_pepper(password.data(), password.size(),
salt.data(), salt.size(),
pepper.data(), pepper.size(),
iterations, dk_len, prf);
}

inline std::vector<uint8_t> pbkdf2_with_pepper(
const std::string& password,
const std::string& salt,
const std::string& 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);
}

std::vector<uint8_t> hkdf_extract_sha256(
const void* ikm_ptr, size_t ikm_len,
const void* salt_ptr, size_t salt_len);

inline std::vector<uint8_t> hkdf_extract_sha256(
const std::vector<uint8_t>& ikm,
const std::vector<uint8_t>& salt) {
return hkdf_extract_sha256(ikm.data(), ikm.size(), salt.data(), salt.size());
}

std::vector<uint8_t> hkdf_expand_sha256(
const void* prk_ptr, size_t prk_len,
const void* info_ptr, size_t info_len,
size_t L);

inline std::vector<uint8_t> hkdf_expand_sha256(
const std::vector<uint8_t>& prk,
const std::vector<uint8_t>& info,
size_t L) {
return hkdf_expand_sha256(prk.data(), prk.size(), info.data(), info.size(), L);
}

struct KeyIv {
std::array<uint8_t,32> key;
std::array<uint8_t,12> 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);

inline KeyIv hkdf_key_iv_256(const std::vector<uint8_t>& ikm,
const std::vector<uint8_t>& salt,
const std::string& context) {
return hkdf_key_iv_256(ikm.data(), ikm.size(), salt.data(), salt.size(), context);
}

/// \brief Generates a time-based HMAC-SHA256 token
Expand Down
44 changes: 44 additions & 0 deletions include/hmac_cpp/secure_buffer.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#ifndef HMAC_CPP_SECURE_BUFFER_HPP
#define HMAC_CPP_SECURE_BUFFER_HPP

#include <cstddef>
#include <vector>
#include <type_traits>

namespace hmac_cpp {

inline void secure_zero(void* ptr, size_t len) {
volatile unsigned char* p = static_cast<volatile unsigned char*>(ptr);
while (len--) {
*p++ = 0;
}
}

template<class T = uint8_t>
struct secure_buffer {
static_assert(std::is_trivial<T>::value, "secure_buffer requires trivial type");

secure_buffer() = default;
explicit secure_buffer(size_t n) : buf(n) {}
explicit secure_buffer(std::vector<T>&& v) : buf(std::move(v)) {}
~secure_buffer() { secure_zero(buf.data(), buf.size() * sizeof(T)); }

T* data() { return buf.data(); }
const T* data() const { return buf.data(); }
size_t size() const { return buf.size(); }

T& operator[](size_t i) { return buf[i]; }
const T& operator[](size_t i) const { return buf[i]; }

typename std::vector<T>::iterator begin() { return buf.begin(); }
typename std::vector<T>::iterator end() { return buf.end(); }
typename std::vector<T>::const_iterator begin() const { return buf.begin(); }
typename std::vector<T>::const_iterator end() const { return buf.end(); }

private:
std::vector<T> buf;
};

} // namespace hmac_cpp

#endif // HMAC_CPP_SECURE_BUFFER_HPP
Loading
Loading