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
18 changes: 18 additions & 0 deletions README-RU.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- Совместимость с **C++11**
- Поддержка `HMAC` на основе `SHA256`, `SHA512`, `SHA1`
- Прямая работа с бинарным или hex-форматом
- Поддержка **PBKDF2** (RFC 8018)
- Поддержка **временных токенов**:
- **HOTP (RFC 4226)** — счётчики
- **TOTP (RFC 6238)** — временные токены
Expand Down Expand Up @@ -130,6 +131,23 @@ std::vector<uint8_t> get_hmac(

Возвращает: Бинарный HMAC в виде `std::vector<uint8_t>`

### PBKDF2 (RFC 8018)

```cpp
#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);
```

Параметры:

- `password`, `salt` — строки с паролем и солью
- `iterations` — число итераций
- `dk_len` — длина ключа в байтах
- `hash_type` — хеш-функция (`SHA1`, `SHA256`, `SHA512`)

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

Библиотека поддерживает генерацию одноразовых паролей по RFC 4226 и RFC 6238.
Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ A lightweight `C++11` library for computing `HMAC` (hash-based message authentic
- Compatible with **C++11**
- Supports `HMAC` using `SHA256`, `SHA512`, `SHA1`
- Outputs in binary or hex format
- Provides **PBKDF2 key derivation** (RFC 8018)
- 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 @@ -154,6 +155,23 @@ Parameters:

Returns: Binary digest as `std::vector<uint8_t>`

### PBKDF2 Key Derivation

```cpp
#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);
```

Parameters:

- `password`, `salt` — Raw byte strings
- `iterations` — Number of iterations
- `dk_len` — Desired key length in bytes
- `hash_type` — Hash function (`SHA1`, `SHA256`, `SHA512`)

### 🕓 HOTP and TOTP Tokens

The library supports generating one-time passwords based on RFC 4226 and RFC 6238.
Expand Down
40 changes: 40 additions & 0 deletions include/hmac_cpp/hmac_utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,46 @@ namespace hmac_cpp {
/// \return true if both strings are equal
bool constant_time_equals(const std::string &a, const std::string &b);

/// \brief Derives a key from a password using PBKDF2 (RFC 8018)
/// \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 iterations Number of iterations, must be positive
/// \param dk_len Desired length of the derived key in bytes, must be positive
/// \param hash_type Hash function to use (SHA1, SHA256, SHA512)
/// \return Derived key as a vector of bytes
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);

/// \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) {
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);
}

/// \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) {
return pbkdf2(password.data(), password.size(),
salt.data(), salt.size(),
iterations, dk_len, hash_type);
}

/// \brief Generates a time-based HMAC-SHA256 token
/// \param key Secret key used for HMAC
/// \param interval_sec Interval in seconds that defines token rotation. Must be positive. Default is 60 seconds
Expand Down
68 changes: 68 additions & 0 deletions src/hmac_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,74 @@ namespace hmac_cpp {
return diff == 0;
}

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) {
if ((password_len > 0 && password_ptr == nullptr) ||
(salt_len > 0 && salt_ptr == nullptr))
throw std::invalid_argument("Null pointer with non-zero length");
if (iterations <= 0)
throw std::invalid_argument("PBKDF2: iterations must be positive");
if (dk_len == 0)
throw std::invalid_argument("PBKDF2: dk_len must be positive");

size_t hlen = 0;
switch (hash_type) {
case TypeHash::SHA1:
hlen = hmac_hash::SHA1::DIGEST_SIZE;
break;
case TypeHash::SHA256:
hlen = hmac_hash::SHA256::DIGEST_SIZE;
break;
case TypeHash::SHA512:
hlen = hmac_hash::SHA512::DIGEST_SIZE;
break;
default:
throw std::invalid_argument("Unsupported hash type");
}

size_t l = (dk_len + hlen - 1) / hlen;
size_t r = dk_len - (l - 1) * hlen;

std::vector<uint8_t> derived;
derived.reserve(dk_len);

std::vector<uint8_t> salt_block;
salt_block.reserve(salt_len + 4);
salt_block.insert(salt_block.end(),
reinterpret_cast<const uint8_t*>(salt_ptr),
reinterpret_cast<const uint8_t*>(salt_ptr) + salt_len);
salt_block.resize(salt_len + 4);

for (size_t i = 1; i <= l; ++i) {
salt_block[salt_len ] = static_cast<uint8_t>((i >> 24) & 0xFF);
salt_block[salt_len + 1] = static_cast<uint8_t>((i >> 16) & 0xFF);
salt_block[salt_len + 2] = static_cast<uint8_t>((i >> 8) & 0xFF);
salt_block[salt_len + 3] = static_cast<uint8_t>(i & 0xFF);

std::vector<uint8_t> u = get_hmac(password_ptr, password_len,
salt_block.data(), salt_block.size(),
hash_type);
std::vector<uint8_t> t = u;
for (int j = 1; j < iterations; ++j) {
u = 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];
}
}
if (i == l) {
derived.insert(derived.end(), t.begin(), t.begin() + r);
} else {
derived.insert(derived.end(), t.begin(), t.end());
}
}

return derived;
}

std::string generate_time_token(const std::string &key, int interval_sec, TypeHash hash_type) {
if (interval_sec <= 0) {
throw std::invalid_argument("interval_sec must be positive");
Expand Down
16 changes: 16 additions & 0 deletions test_all.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,22 @@ TEST(TokenBoundaryFingerprintTest, MinTime) {
EXPECT_TRUE(hmac::is_token_valid(token_next, key, fingerprint, interval));
}

TEST(PBKDF2Test, SHA1) {
const std::string password = "password";
const std::string salt = "salt";
std::vector<uint8_t> dk = hmac::pbkdf2(password, salt, 2, 20, hmac::TypeHash::SHA1);
std::string hex = hmac::to_hex(std::string(dk.begin(), dk.end()));
EXPECT_EQ(hex, "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957");
}

TEST(PBKDF2Test, SHA256) {
const std::string password = "password";
const std::string salt = "salt";
std::vector<uint8_t> dk = hmac::pbkdf2(password, salt, 2, 32, hmac::TypeHash::SHA256);
std::string hex = hmac::to_hex(std::string(dk.begin(), dk.end()));
EXPECT_EQ(hex, "ae4d0c95af6b46d32d0adff928f06dd02a303f8ef3c251dfd6e2d85a95474c43");
}

TEST(TimeErrorTest, MinusOneNoErrno) {
const std::string key = "12345";
mock_time_value = static_cast<std::time_t>(-1);
Expand Down
Loading