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: 13 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,19 @@ if(NOT TARGET hmac_cpp::hmac_cpp)
add_library(hmac_cpp::hmac_cpp ALIAS hmac_cpp)
endif()

if(HMACCPP_BUILD_EXAMPLES)
add_executable(example example.cpp)
target_link_libraries(example PRIVATE hmac_cpp)
target_include_directories(example PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
endif()
if(HMACCPP_BUILD_EXAMPLES)
add_executable(example example.cpp)
target_link_libraries(example PRIVATE hmac_cpp)
target_include_directories(example PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)

add_executable(example_streaming example_streaming.cpp)
target_link_libraries(example_streaming PRIVATE hmac_cpp)
target_include_directories(example_streaming PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)

add_executable(example_pbkdf2 example_pbkdf2.cpp)
target_link_libraries(example_pbkdf2 PRIVATE hmac_cpp)
target_include_directories(example_pbkdf2 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
endif()

include(CMakePackageConfigHelpers)
install(TARGETS hmac_cpp EXPORT hmac_cppTargets DESTINATION lib)
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,14 @@ int main() {
**Note:** avoid checking input lengths before calling `constant_time_equal`.
Early length comparisons can leak information through timing side channels.

## PBKDF2 Recommended Parameters

| Target | Iterations | Derived key length | PRF |
|---------|-----------:|------------------:|-----|
| Desktop | 600000 | 32 bytes | HMAC-SHA256 |
| Laptop | 300000 | 32 bytes | HMAC-SHA256 |
| Mobile | 150000 | 32 bytes | HMAC-SHA256 |

## Security Notes

- PBKDF2 is CPU-bound and vulnerable to massive GPU/ASIC brute force. Choose high iteration counts or stronger KDFs.
Expand Down
77 changes: 77 additions & 0 deletions example_pbkdf2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

#include "hmac_cpp/hmac.hpp"
#include "hmac_cpp/hmac_utils.hpp"

static std::vector<uint8_t> from_hex(const std::string& hex) {
std::vector<uint8_t> out;
out.reserve(hex.size() / 2);
for (size_t i = 0; i < hex.size(); i += 2) {
unsigned int byte;
std::stringstream ss;
ss << std::hex << hex.substr(i, 2);
ss >> byte;
out.push_back(static_cast<uint8_t>(byte));
}
return out;
}

static std::string serialize(const std::vector<uint8_t>& salt,
uint32_t iters,
size_t dk_len,
hmac::Pbkdf2Hash prf) {
std::string salt_hex = hmac::to_hex(
std::string(reinterpret_cast<const char*>(salt.data()), salt.size()));
std::ostringstream oss;
oss << salt_hex << '|' << iters << '|' << dk_len << '|' << static_cast<int>(prf);
return oss.str();
}

static bool deserialize(const std::string& s,
std::vector<uint8_t>& salt,
uint32_t& iters,
size_t& dk_len,
hmac::Pbkdf2Hash& prf) {
std::istringstream iss(s);
std::string salt_hex, iters_str, dk_len_str, prf_str;
if (!std::getline(iss, salt_hex, '|')) return false;
if (!std::getline(iss, iters_str, '|')) return false;
if (!std::getline(iss, dk_len_str, '|')) return false;
if (!std::getline(iss, prf_str, '|')) return false;
salt = from_hex(salt_hex);
iters = static_cast<uint32_t>(std::stoul(iters_str));
dk_len = static_cast<size_t>(std::stoul(dk_len_str));
prf = static_cast<hmac::Pbkdf2Hash>(std::stoi(prf_str));
return true;
}

int main() {
std::vector<uint8_t> password{'s','e','c','r','e','t'};
std::vector<uint8_t> salt{0,1,2,3,4,5,6,7};
uint32_t iters = 100000;
size_t dk_len = 32;
hmac::Pbkdf2Hash prf = hmac::Pbkdf2Hash::Sha256;

auto dk = hmac::pbkdf2(password, salt, iters, dk_len, prf);

std::string header = serialize(salt, iters, dk_len, prf);
std::cout << "serialized params: " << header << '\n';

std::vector<uint8_t> salt2; uint32_t iters2; size_t dk_len2; hmac::Pbkdf2Hash prf2;
if (!deserialize(header, salt2, iters2, dk_len2, prf2)) {
std::cerr << "failed to parse header" << std::endl;
return 1;
}
auto dk2 = hmac::pbkdf2(password, salt2, iters2, dk_len2, prf2);

bool same = hmac::constant_time_equal(
std::string(reinterpret_cast<char*>(dk.data()), dk.size()),
std::string(reinterpret_cast<char*>(dk2.data()), dk2.size()));
std::cout << "keys match? " << (same ? "yes" : "no") << std::endl;

// dk would be used to decrypt your configuration here
return same ? 0 : 1;
}
42 changes: 42 additions & 0 deletions example_streaming.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include <array>
#include <fstream>
#include <iostream>
#include <string>

// stream large files in chunks and verify
#include "hmac_cpp/hmac.hpp"
#include "hmac_cpp/hmac_utils.hpp"

int main() {
const std::string key = "supersecret";
const std::string path = "large.bin"; // path to large file

hmac::HmacContext ctx(hmac::TypeHash::SHA256);
ctx.init(key.data(), key.size());

std::ifstream in(path, std::ios::binary);
if (!in) {
std::cerr << "cannot open " << path << "\n";
return 1;
}

std::array<char, 4096> buf{};
while (in.good()) {
in.read(buf.data(), buf.size());
std::streamsize got = in.gcount();
if (got > 0) {
ctx.update(buf.data(), static_cast<size_t>(got));
}
}

std::array<uint8_t, 32> mac{};
ctx.final(mac.data(), mac.size());

std::string mac_hex = hmac::to_hex(
std::string(reinterpret_cast<char*>(mac.data()), mac.size()));

const std::string expected_hex = "<expected hmac>"; // known good value
bool ok = hmac::constant_time_equal(mac_hex, expected_hex);
std::cout << "HMAC valid? " << (ok ? "yes" : "no") << std::endl;
return ok ? 0 : 1;
}
Loading