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
33 changes: 26 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,15 +255,34 @@ bool v2 = hmac::is_token_valid(t2, secret_key, fingerprint, 60);

`hmac_cpp::encoding` provides simple conversions:

* **Base64** — standard `+/` and URL-safe `-_` alphabets; optional `strict` mode
(rejects whitespace and mixed padding) and ability to decode without `=`.
* **Base32** — RFC 4648 alphabet `A–Z2–7`; encoder outputs upper-case, decoder
accepts lower-case and ignores spaces/CR/LF when `strict=false`.
* **Base36** — non-standard human-readable IDs using `0–9A–Z`; keeps leading
zero bytes by prefixing `'0'` and maps a single `\x00` to "0".
* **Base64** — standard `+/` and URL-safe `-_` alphabets; `pad=true/false` toggles
`=` padding. `strict=true` rejects whitespace, mixed padding and `+`/`/` when
using the URL alphabet; `strict=false` ignores ASCII spaces, accepts these
aliases and tolerates missing padding.
* **Base32** — RFC 4648 alphabet `A–Z2–7`; encoder emits upper-case. Decoder
with `strict=true` requires `=` padding and upper-case; with `strict=false`
it tolerates lower-case and whitespace.
* **Base36** — human‑friendly IDs using `0–9A–Z`; not a cryptographic format.
Leading zero bytes are preserved (e.g. `{0,0,1}` → `"001"`).

#### Encoding

```cpp
std::string b64 = hmac_cpp::base64_encode(buf.data(), buf.size(),
hmac_cpp::Base64Alphabet::Url, /*pad=*/false);
hmac_cpp::base64_decode(b64, raw, hmac_cpp::Base64Alphabet::Url,
/*require_padding=*/false, /*strict=*/true);

std::string b32 = hmac_cpp::base32_encode(buf.data(), buf.size(), /*pad=*/true);
hmac_cpp::base32_decode(b32, raw, /*require_padding=*/true, /*strict=*/false);

std::string b36 = hmac_cpp::base36_encode(buf.data(), buf.size());
hmac_cpp::base36_decode(b36, raw);
```

Returned strings and buffers are not zeroized; if you store secrets, prefer
`secure_buffer` and wipe explicitly.
`secure_buffer` and wipe explicitly. Zeroization is a best‑effort and may be
removed by optimizations or the C++ runtime allocator.

---

Expand Down
12 changes: 6 additions & 6 deletions include/hmac_cpp/encoding.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ namespace hmac_cpp {
bool pad = true);

/// \brief Base64-encode a vector.
inline std::string base64_encode(const std::vector<uint8_t>& v,
inline HMAC_CPP_API std::string base64_encode(const std::vector<uint8_t>& v,
Base64Alphabet alphabet = Base64Alphabet::Standard,
bool pad = true) {
return base64_encode(v.data(), v.size(), alphabet, pad);
}

/// \brief Base64-encode a secure_buffer.
inline std::string base64_encode(const secure_buffer<uint8_t>& v,
inline HMAC_CPP_API std::string base64_encode(const secure_buffer<uint8_t>& v,
Base64Alphabet alphabet = Base64Alphabet::Standard,
bool pad = true) {
return base64_encode(v.data(), v.size(), alphabet, pad);
Expand Down Expand Up @@ -73,12 +73,12 @@ namespace hmac_cpp {
bool pad = true);

/// \brief Base32-encode a vector.
inline std::string base32_encode(const std::vector<uint8_t>& v, bool pad = true) {
inline HMAC_CPP_API std::string base32_encode(const std::vector<uint8_t>& v, bool pad = true) {
return base32_encode(v.data(), v.size(), pad);
}

/// \brief Base32-encode a secure_buffer.
inline std::string base32_encode(const secure_buffer<uint8_t>& v, bool pad = true) {
inline HMAC_CPP_API std::string base32_encode(const secure_buffer<uint8_t>& v, bool pad = true) {
return base32_encode(v.data(), v.size(), pad);
}

Expand Down Expand Up @@ -111,12 +111,12 @@ namespace hmac_cpp {
HMAC_CPP_API std::string base36_encode(const uint8_t* data, size_t len);

/// \brief Base36-encode a vector.
inline std::string base36_encode(const std::vector<uint8_t>& v) {
inline HMAC_CPP_API std::string base36_encode(const std::vector<uint8_t>& v) {
return base36_encode(v.data(), v.size());
}

/// \brief Base36-encode a secure_buffer.
inline std::string base36_encode(const secure_buffer<uint8_t>& v) {
inline HMAC_CPP_API std::string base36_encode(const secure_buffer<uint8_t>& v) {
return base36_encode(v.data(), v.size());
}

Expand Down
10 changes: 6 additions & 4 deletions src/encoding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@

namespace hmac_cpp {

struct _wipe_string_guard {
namespace {
struct _wipe_string_guard final {
std::string& s;
explicit _wipe_string_guard(std::string& ref) : s(ref) {}
~_wipe_string_guard() {
if (!s.empty()) std::fill(s.begin(), s.end(), '\0');
}
};
} // anonymous namespace

// ======================
// Helpers (Base64)
Expand All @@ -23,13 +25,13 @@ static inline const char* b64_alphabet(Base64Alphabet a) {
: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
}

static inline void b64_build_reverse(Base64Alphabet a, int8_t rev[256]) {
static inline void b64_build_reverse(Base64Alphabet a, int8_t rev[256], bool allow_alias) {
for (int i = 0; i < 256; ++i) rev[i] = -1;
const char* alpha = b64_alphabet(a);
for (int i = 0; i < 64; ++i) {
rev[ static_cast<unsigned char>(alpha[i]) ] = static_cast<int8_t>(i);
}
if (a == Base64Alphabet::Url) {
if (a == Base64Alphabet::Url && allow_alias) {
rev[ static_cast<unsigned char>('+') ] = 62;
rev[ static_cast<unsigned char>('/') ] = 63;
}
Expand Down Expand Up @@ -115,7 +117,7 @@ bool base64_decode(const std::string& in, std::vector<uint8_t>& out,
}

int8_t rev[256];
b64_build_reverse(alphabet, rev);
b64_build_reverse(alphabet, rev, !strict);

size_t approx = (L / 4) * 3 + 3;
out.reserve(approx);
Expand Down
69 changes: 68 additions & 1 deletion test_all.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,7 @@ TEST(EncodingTest, Base64Vectors) {
std::vector<uint8_t> out;
EXPECT_TRUE(hmac_cpp::base64_decode("Zg", out, hmac_cpp::Base64Alphabet::Standard, false, false));
EXPECT_EQ(out, std::vector<uint8_t>({'f'}));
EXPECT_TRUE(hmac_cpp::base64_decode("+__/", out, hmac_cpp::Base64Alphabet::Url));
EXPECT_TRUE(hmac_cpp::base64_decode("+__/", out, hmac_cpp::Base64Alphabet::Url, false, false));
EXPECT_EQ(out, std::vector<uint8_t>({0xfb,0xff,0xff}));
}

Expand Down Expand Up @@ -682,6 +682,73 @@ TEST(EncodingTest, Base36Vectors) {
}
}

TEST(EncodingPropertyTest, Base64RoundTrip) {
std::mt19937 rng(123);
std::uniform_int_distribution<int> len_dist(0,64);
for (int i = 0; i < 50; ++i) {
int len = len_dist(rng);
std::vector<uint8_t> buf(len);
for (int j = 0; j < len; ++j) buf[j] = static_cast<uint8_t>(rng());
for (auto alpha : {hmac_cpp::Base64Alphabet::Standard, hmac_cpp::Base64Alphabet::Url}) {
for (bool pad : {true, false}) {
std::string enc = hmac_cpp::base64_encode(buf.data(), buf.size(), alpha, pad);
std::vector<uint8_t> dec;
EXPECT_TRUE(hmac_cpp::base64_decode(enc, dec, alpha, pad, true));
EXPECT_EQ(dec, buf);
std::string re = hmac_cpp::base64_encode(dec.data(), dec.size(), alpha, pad);
EXPECT_EQ(re, enc);
}
}
}
}

TEST(EncodingPropertyTest, Base64StrictRejects) {
std::vector<uint8_t> out;
EXPECT_FALSE(hmac_cpp::base64_decode("+__/", out, hmac_cpp::Base64Alphabet::Url, false, true));
EXPECT_FALSE(hmac_cpp::base64_decode("AAAAA", out, hmac_cpp::Base64Alphabet::Standard, false, true));
}

TEST(EncodingPropertyTest, Base32RoundTrip) {
std::mt19937 rng(321);
std::uniform_int_distribution<int> len_dist(0,40);
for (int i = 0; i < 50; ++i) {
int len = len_dist(rng);
std::vector<uint8_t> buf(len);
for (int j = 0; j < len; ++j) buf[j] = static_cast<uint8_t>(rng());
for (bool pad : {true, false}) {
std::string enc = hmac_cpp::base32_encode(buf.data(), buf.size(), pad);
std::vector<uint8_t> dec;
EXPECT_TRUE(hmac_cpp::base32_decode(enc, dec, pad, true));
EXPECT_EQ(dec, buf);
std::string re = hmac_cpp::base32_encode(dec.data(), dec.size(), pad);
EXPECT_EQ(re, enc);
}
}
}

TEST(EncodingPropertyTest, Base32RejectsInvalid) {
std::vector<uint8_t> out;
EXPECT_FALSE(hmac_cpp::base32_decode("A", out));
EXPECT_FALSE(hmac_cpp::base32_decode("AAA", out));
EXPECT_FALSE(hmac_cpp::base32_decode("AAAAAA", out));
EXPECT_FALSE(hmac_cpp::base32_decode("MZX=====MZXW6===", out));
}

TEST(EncodingPropertyTest, Base36LeadingZeros) {
std::mt19937 rng(777);
std::uniform_int_distribution<int> len_dist(0,10);
for (int i = 0; i < 20; ++i) {
int zeros = len_dist(rng) % 5;
int data_len = len_dist(rng);
std::vector<uint8_t> buf(zeros + data_len);
for (int j = 0; j < data_len; ++j) buf[zeros + j] = static_cast<uint8_t>(rng());
std::string enc = hmac_cpp::base36_encode(buf.data(), buf.size());
std::vector<uint8_t> dec;
EXPECT_TRUE(hmac_cpp::base36_decode(enc, dec));
EXPECT_EQ(dec, buf);
}
}

int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
Expand Down
Loading