Skip to content

Commit 95390be

Browse files
authored
docs(readme): document PBKDF2 key derivation
1 parent 1c76b18 commit 95390be

5 files changed

Lines changed: 160 additions & 0 deletions

File tree

README-RU.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- Совместимость с **C++11**
1111
- Поддержка `HMAC` на основе `SHA256`, `SHA512`, `SHA1`
1212
- Прямая работа с бинарным или hex-форматом
13+
- Поддержка **PBKDF2** (RFC 8018)
1314
- Поддержка **временных токенов**:
1415
- **HOTP (RFC 4226)** — счётчики
1516
- **TOTP (RFC 6238)** — временные токены
@@ -130,6 +131,23 @@ std::vector<uint8_t> get_hmac(
130131

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

134+
### PBKDF2 (RFC 8018)
135+
136+
```cpp
137+
#include <hmac_cpp/hmac_utils.hpp>
138+
139+
std::string password = "password";
140+
std::string salt = "salt";
141+
std::vector<uint8_t> dk = hmac::pbkdf2(password, salt, 1000, 32, hmac::TypeHash::SHA256);
142+
```
143+
144+
Параметры:
145+
146+
- `password`, `salt` — строки с паролем и солью
147+
- `iterations` — число итераций
148+
- `dk_len` — длина ключа в байтах
149+
- `hash_type` — хеш-функция (`SHA1`, `SHA256`, `SHA512`)
150+
133151
### 🕓 HOTP и TOTP токены
134152

135153
Библиотека поддерживает генерацию одноразовых паролей по RFC 4226 и RFC 6238.

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ A lightweight `C++11` library for computing `HMAC` (hash-based message authentic
1010
- Compatible with **C++11**
1111
- Supports `HMAC` using `SHA256`, `SHA512`, `SHA1`
1212
- Outputs in binary or hex format
13+
- Provides **PBKDF2 key derivation** (RFC 8018)
1314
- Support for **time-based tokens**:
1415
- **HOTP (RFC 4226)** — counter-based one-time passwords
1516
- **TOTP (RFC 6238)** — time-based one-time passwords
@@ -154,6 +155,23 @@ Parameters:
154155

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

158+
### PBKDF2 Key Derivation
159+
160+
```cpp
161+
#include <hmac_cpp/hmac_utils.hpp>
162+
163+
std::string password = "password";
164+
std::string salt = "salt";
165+
std::vector<uint8_t> dk = hmac::pbkdf2(password, salt, 1000, 32, hmac::TypeHash::SHA256);
166+
```
167+
168+
Parameters:
169+
170+
- `password`, `salt` — Raw byte strings
171+
- `iterations` — Number of iterations
172+
- `dk_len` — Desired key length in bytes
173+
- `hash_type` — Hash function (`SHA1`, `SHA256`, `SHA512`)
174+
157175
### 🕓 HOTP and TOTP Tokens
158176

159177
The library supports generating one-time passwords based on RFC 4226 and RFC 6238.

include/hmac_cpp/hmac_utils.hpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,46 @@ namespace hmac_cpp {
1313
/// \return true if both strings are equal
1414
bool constant_time_equals(const std::string &a, const std::string &b);
1515

16+
/// \brief Derives a key from a password using PBKDF2 (RFC 8018)
17+
/// \param password_ptr Pointer to the password buffer
18+
/// \param password_len Length of the password in bytes
19+
/// \param salt_ptr Pointer to the salt buffer
20+
/// \param salt_len Length of the salt in bytes
21+
/// \param iterations Number of iterations, must be positive
22+
/// \param dk_len Desired length of the derived key in bytes, must be positive
23+
/// \param hash_type Hash function to use (SHA1, SHA256, SHA512)
24+
/// \return Derived key as a vector of bytes
25+
std::vector<uint8_t> pbkdf2(
26+
const void* password_ptr, size_t password_len,
27+
const void* salt_ptr, size_t salt_len,
28+
int iterations, size_t dk_len,
29+
TypeHash hash_type);
30+
31+
/// \brief Derives a key using PBKDF2 from vector-based password and salt
32+
template<typename T>
33+
inline std::vector<uint8_t> pbkdf2(
34+
const std::vector<T>& password,
35+
const std::vector<T>& salt,
36+
int iterations, size_t dk_len,
37+
TypeHash hash_type) {
38+
static_assert(std::is_same<T, char>::value || std::is_same<T, uint8_t>::value,
39+
"pbkdf2(vector<T>) supports only char or uint8_t");
40+
return pbkdf2(password.data(), password.size(),
41+
salt.data(), salt.size(),
42+
iterations, dk_len, hash_type);
43+
}
44+
45+
/// \brief Derives a key using PBKDF2 from string-based password and salt
46+
inline std::vector<uint8_t> pbkdf2(
47+
const std::string& password,
48+
const std::string& salt,
49+
int iterations, size_t dk_len,
50+
TypeHash hash_type) {
51+
return pbkdf2(password.data(), password.size(),
52+
salt.data(), salt.size(),
53+
iterations, dk_len, hash_type);
54+
}
55+
1656
/// \brief Generates a time-based HMAC-SHA256 token
1757
/// \param key Secret key used for HMAC
1858
/// \param interval_sec Interval in seconds that defines token rotation. Must be positive. Default is 60 seconds

src/hmac_utils.cpp

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,74 @@ namespace hmac_cpp {
1717
return diff == 0;
1818
}
1919

20+
std::vector<uint8_t> pbkdf2(
21+
const void* password_ptr, size_t password_len,
22+
const void* salt_ptr, size_t salt_len,
23+
int iterations, size_t dk_len,
24+
TypeHash hash_type) {
25+
if ((password_len > 0 && password_ptr == nullptr) ||
26+
(salt_len > 0 && salt_ptr == nullptr))
27+
throw std::invalid_argument("Null pointer with non-zero length");
28+
if (iterations <= 0)
29+
throw std::invalid_argument("PBKDF2: iterations must be positive");
30+
if (dk_len == 0)
31+
throw std::invalid_argument("PBKDF2: dk_len must be positive");
32+
33+
size_t hlen = 0;
34+
switch (hash_type) {
35+
case TypeHash::SHA1:
36+
hlen = hmac_hash::SHA1::DIGEST_SIZE;
37+
break;
38+
case TypeHash::SHA256:
39+
hlen = hmac_hash::SHA256::DIGEST_SIZE;
40+
break;
41+
case TypeHash::SHA512:
42+
hlen = hmac_hash::SHA512::DIGEST_SIZE;
43+
break;
44+
default:
45+
throw std::invalid_argument("Unsupported hash type");
46+
}
47+
48+
size_t l = (dk_len + hlen - 1) / hlen;
49+
size_t r = dk_len - (l - 1) * hlen;
50+
51+
std::vector<uint8_t> derived;
52+
derived.reserve(dk_len);
53+
54+
std::vector<uint8_t> salt_block;
55+
salt_block.reserve(salt_len + 4);
56+
salt_block.insert(salt_block.end(),
57+
reinterpret_cast<const uint8_t*>(salt_ptr),
58+
reinterpret_cast<const uint8_t*>(salt_ptr) + salt_len);
59+
salt_block.resize(salt_len + 4);
60+
61+
for (size_t i = 1; i <= l; ++i) {
62+
salt_block[salt_len ] = static_cast<uint8_t>((i >> 24) & 0xFF);
63+
salt_block[salt_len + 1] = static_cast<uint8_t>((i >> 16) & 0xFF);
64+
salt_block[salt_len + 2] = static_cast<uint8_t>((i >> 8) & 0xFF);
65+
salt_block[salt_len + 3] = static_cast<uint8_t>(i & 0xFF);
66+
67+
std::vector<uint8_t> u = get_hmac(password_ptr, password_len,
68+
salt_block.data(), salt_block.size(),
69+
hash_type);
70+
std::vector<uint8_t> t = u;
71+
for (int j = 1; j < iterations; ++j) {
72+
u = get_hmac(password_ptr, password_len,
73+
u.data(), u.size(), hash_type);
74+
for (size_t k = 0; k < t.size(); ++k) {
75+
t[k] ^= u[k];
76+
}
77+
}
78+
if (i == l) {
79+
derived.insert(derived.end(), t.begin(), t.begin() + r);
80+
} else {
81+
derived.insert(derived.end(), t.begin(), t.end());
82+
}
83+
}
84+
85+
return derived;
86+
}
87+
2088
std::string generate_time_token(const std::string &key, int interval_sec, TypeHash hash_type) {
2189
if (interval_sec <= 0) {
2290
throw std::invalid_argument("interval_sec must be positive");

test_all.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,22 @@ TEST(TokenBoundaryFingerprintTest, MinTime) {
200200
EXPECT_TRUE(hmac::is_token_valid(token_next, key, fingerprint, interval));
201201
}
202202

203+
TEST(PBKDF2Test, SHA1) {
204+
const std::string password = "password";
205+
const std::string salt = "salt";
206+
std::vector<uint8_t> dk = hmac::pbkdf2(password, salt, 2, 20, hmac::TypeHash::SHA1);
207+
std::string hex = hmac::to_hex(std::string(dk.begin(), dk.end()));
208+
EXPECT_EQ(hex, "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957");
209+
}
210+
211+
TEST(PBKDF2Test, SHA256) {
212+
const std::string password = "password";
213+
const std::string salt = "salt";
214+
std::vector<uint8_t> dk = hmac::pbkdf2(password, salt, 2, 32, hmac::TypeHash::SHA256);
215+
std::string hex = hmac::to_hex(std::string(dk.begin(), dk.end()));
216+
EXPECT_EQ(hex, "ae4d0c95af6b46d32d0adff928f06dd02a303f8ef3c251dfd6e2d85a95474c43");
217+
}
218+
203219
TEST(TimeErrorTest, MinusOneNoErrno) {
204220
const std::string key = "12345";
205221
mock_time_value = static_cast<std::time_t>(-1);

0 commit comments

Comments
 (0)