Skip to content

Instantly share code, notes, and snippets.

@nongvantinh
Last active April 10, 2025 05:01
Show Gist options
  • Save nongvantinh/2a4ac54c92739b296cd275b1d6708966 to your computer and use it in GitHub Desktop.
Save nongvantinh/2a4ac54c92739b296cd275b1d6708966 to your computer and use it in GitHub Desktop.
C++17 implementation for one-time passwords. It provides HMAC-based and time-based implementations of one-time passwords.
// Compile: g++ generate-otp.cpp --std=c++17 -Wall -Wextra -o otp_generator
// Run: ./otp_generator
#ifndef ONE_TIME_PASSWORD_H
#define ONE_TIME_PASSWORD_H
#include <string>
#include <vector>
#include <ctime>
class OneTimePassword
{
private:
std::string secret_key;
static const std::string base32_chars;
static const uint32_t SHA1_K[];
static const size_t SHA1_BLOCK_SIZE;
protected:
std::vector<uint8_t> base32_decode(const std::string &input);
void sha1_transform(uint32_t *state, const uint8_t *data);
std::vector<uint8_t> sha1(const std::vector<uint8_t> &data);
std::vector<uint8_t> hmac_sha1(const std::vector<uint8_t> &key, const std::vector<uint8_t> &data);
uint32_t truncate(const std::vector<uint8_t> &hash);
OneTimePassword();
OneTimePassword(const std::string &key);
virtual ~OneTimePassword() = default;
public:
void set_secret_key(const std::string &key);
std::string get_secret_key() const;
std::vector<std::string> get38_timezones() const;
std::time_t get_current_time_in_timezone(const std::string &timezone) const;
};
#include <chrono>
#include <iomanip>
#include <sstream>
const std::string OneTimePassword::base32_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
const uint32_t OneTimePassword::SHA1_K[] = {0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6};
const size_t OneTimePassword::SHA1_BLOCK_SIZE = 64; // SHA-1 processes in 512-bit blocks (64 bytes)
// SHA-1 helper functions
#define ROTL(n, x) (((x) << (n)) | ((x) >> (32 - (n))))
#define F0(b, c, d) (((b) & (c)) | (~(b) & (d)))
#define F1(b, c, d) ((b) ^ (c) ^ (d))
#define F2(b, c, d) (((b) & (c)) | ((b) & (d)) | ((c) & (d)))
#define F3(b, c, d) ((b) ^ (c) ^ (d))
std::vector<uint8_t> OneTimePassword::base32_decode(const std::string &input)
{
std::vector<uint8_t> output;
size_t estimated_size = (input.length() * 5) / 8 + 1; // +1 for rounding up if necessary
output.reserve(estimated_size);
int val = 0;
int valb = -8;
for (unsigned char c : input)
{
if (base32_chars.find(c) == std::string::npos)
continue;
val = (val << 5) + base32_chars.find(c);
valb += 5;
if (valb >= 0)
{
output.push_back((val >> valb) & 0xFF);
valb -= 8;
}
}
return output;
}
void OneTimePassword::sha1_transform(uint32_t *state, const uint8_t *data)
{
uint32_t W[80];
uint32_t A, B, C, D, E;
uint32_t temp;
for (int i = 0; i < 16; ++i)
{
W[i] = (data[i * 4] << 24) | (data[i * 4 + 1] << 16) | (data[i * 4 + 2] << 8) | (data[i * 4 + 3]);
}
for (int i = 16; i < 80; ++i)
{
W[i] = ROTL(1, W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]);
}
A = state[0];
B = state[1];
C = state[2];
D = state[3];
E = state[4];
for (int i = 0; i < 80; ++i)
{
if (i < 20)
{
temp = ROTL(5, A) + F0(B, C, D) + E + W[i] + SHA1_K[0];
}
else if (i < 40)
{
temp = ROTL(5, A) + F1(B, C, D) + E + W[i] + SHA1_K[1];
}
else if (i < 60)
{
temp = ROTL(5, A) + F2(B, C, D) + E + W[i] + SHA1_K[2];
}
else
{
temp = ROTL(5, A) + F3(B, C, D) + E + W[i] + SHA1_K[3];
}
E = D;
D = C;
C = ROTL(30, B);
B = A;
A = temp;
}
state[0] += A;
state[1] += B;
state[2] += C;
state[3] += D;
state[4] += E;
}
std::vector<uint8_t> OneTimePassword::sha1(const std::vector<uint8_t> &data)
{
uint32_t state[5] = {0x67452301,
0xEFCDAB89,
0x98BADCFE,
0x10325476,
0xC3D2E1F0};
size_t original_size = data.size() * 8;
size_t padded_size = data.size() + 1;
if ((padded_size + 8) % SHA1_BLOCK_SIZE != 0)
{
padded_size += (SHA1_BLOCK_SIZE - ((padded_size + 8) % SHA1_BLOCK_SIZE));
}
std::vector<uint8_t> padded_data(padded_size + 8);
std::copy(data.begin(), data.end(), padded_data.begin());
padded_data[data.size()] = 0x80;
std::fill(padded_data.begin() + data.size() + 1, padded_data.end() - 8, 0x00);
for (int i = 0; i < 8; ++i)
{
padded_data[padded_size + i] = (original_size >> (56 - i * 8)) & 0xFF;
}
for (size_t i = 0; i < padded_data.size(); i += SHA1_BLOCK_SIZE)
{
sha1_transform(state, padded_data.data() + i);
}
std::vector<uint8_t> hash(20);
for (int i = 0; i < 5; ++i)
{
hash[i * 4] = (state[i] >> 24) & 0xFF;
hash[i * 4 + 1] = (state[i] >> 16) & 0xFF;
hash[i * 4 + 2] = (state[i] >> 8) & 0xFF;
hash[i * 4 + 3] = state[i] & 0xFF;
}
return hash;
}
std::vector<uint8_t> OneTimePassword::hmac_sha1(const std::vector<uint8_t> &key, const std::vector<uint8_t> &data)
{
std::vector<uint8_t> key_padded(SHA1_BLOCK_SIZE, 0x00);
if (key.size() > SHA1_BLOCK_SIZE)
{
std::vector<uint8_t> key_hash = sha1(key);
std::copy(key_hash.begin(), key_hash.end(), key_padded.begin());
}
else
{
std::copy(key.begin(), key.end(), key_padded.begin());
}
std::vector<uint8_t> o_key_pad(SHA1_BLOCK_SIZE);
std::vector<uint8_t> i_key_pad(SHA1_BLOCK_SIZE);
for (size_t i = 0; i < SHA1_BLOCK_SIZE; ++i)
{
o_key_pad[i] = key_padded[i] ^ 0x5C;
i_key_pad[i] = key_padded[i] ^ 0x36;
}
std::vector<uint8_t> inner_hash_input;
inner_hash_input.reserve(SHA1_BLOCK_SIZE + data.size());
inner_hash_input.insert(inner_hash_input.end(), i_key_pad.begin(), i_key_pad.end());
inner_hash_input.insert(inner_hash_input.end(), data.begin(), data.end());
std::vector<uint8_t> inner_hash = sha1(inner_hash_input);
std::vector<uint8_t> outer_hash_input;
outer_hash_input.reserve(SHA1_BLOCK_SIZE + inner_hash.size());
outer_hash_input.insert(outer_hash_input.end(), o_key_pad.begin(), o_key_pad.end());
outer_hash_input.insert(outer_hash_input.end(), inner_hash.begin(), inner_hash.end());
// Calculate final hash
return sha1(outer_hash_input);
}
uint32_t OneTimePassword::truncate(const std::vector<uint8_t> &hash)
{
int offset = hash.back() & 0x0F;
uint32_t binary = ((hash[offset] & 0x7F) << 24) |
((hash[offset + 1] & 0xFF) << 16) |
((hash[offset + 2] & 0xFF) << 8) |
(hash[offset + 3] & 0xFF);
return binary;
}
void OneTimePassword::set_secret_key(const std::string &key)
{
secret_key = key;
}
std::string OneTimePassword::get_secret_key() const
{
return secret_key;
}
// These timezones are cached because they have real different local times
std::vector<std::string> OneTimePassword::get38_timezones() const
{
return {
"Pacific/Marquesas",
"Etc/GMT-14",
"Etc/GMT-13",
"Etc/GMT+11",
"Etc/GMT+10",
"Australia/Lord_Howe",
"America/Nuuk",
"Africa/Algiers",
"America/Bahia_Banderas",
"America/Anchorage",
"Asia/Chita",
"America/Miquelon",
"America/Araguaina",
"America/Barbados",
"Pacific/Chatham",
"Asia/Kathmandu",
"America/Adak",
"Asia/Colombo",
"Africa/Cairo",
"America/Dawson",
"Australia/Adelaide",
"Africa/Abidjan",
"America/Bogota",
"Antarctica/Davis",
"Asia/Anadyr",
"Antarctica/Macquarie",
"Etc/GMT+12",
"Asia/Yangon",
"Asia/Baku",
"Australia/Eucla",
"Asia/Bishkek",
"Asia/Kabul",
"Africa/Nairobi",
"Asia/Magadan",
"Antarctica/Casey",
"America/St_Johns",
"Antarctica/Mawson",
"Asia/Tehran"};
}
std::time_t OneTimePassword::get_current_time_in_timezone(const std::string &timezone) const
{
std::time_t now = std::time(nullptr);
std::tm local_tm = *std::localtime(&now);
local_tm.tm_isdst = -1; // Let mktime figure DST
// Interpret it as a wall clock time
std::time_t local_wall = std::mktime(&local_tm);
setenv("TZ", timezone.c_str(), 1);
tzset();
std::tm *target_tm = std::localtime(&local_wall);
std::time_t target_t = std::mktime(target_tm);
std::tm *utc_tm = std::gmtime(&local_wall);
std::time_t utc_t = std::mktime(utc_tm);
int offset = static_cast<int>(difftime(target_t, utc_t));
unsetenv("TZ");
tzset();
std::time_t reinterpreted = local_wall - offset;
return reinterpreted;
}
OneTimePassword::OneTimePassword() : secret_key("") {}
OneTimePassword::OneTimePassword(const std::string &key) : secret_key(key) {}
#endif // !ONE_TIME_PASSWORD_H
#ifndef HMAC_BASED_ONE_TIME_PASSWORD_H
#define HMAC_BASED_ONE_TIME_PASSWORD_H
class HMACbasedOneTimePassword : public OneTimePassword
{
private:
uint64_t counter;
public:
int32_t generate_hotp();
void set_counter(uint64_t counter);
uint64_t get_counter() const;
HMACbasedOneTimePassword();
HMACbasedOneTimePassword(const std::string &key);
HMACbasedOneTimePassword(const std::string &key, uint64_t starting_point);
};
int32_t HMACbasedOneTimePassword::generate_hotp()
{
std::vector<uint8_t> key = base32_decode(get_secret_key());
uint64_t counter_value = get_counter();
std::vector<uint8_t> counter_data(8);
for (int i = 0; i < 8; ++i)
{
counter_data[7 - i] = counter_value & 0xFF;
counter_value >>= 8;
}
std::vector<uint8_t> hash = hmac_sha1(key, counter_data);
uint32_t code = truncate(hash) % 1000000;
set_counter(get_counter() + 1);
return code;
}
void HMACbasedOneTimePassword::set_counter(uint64_t counter)
{
this->counter = counter;
}
uint64_t HMACbasedOneTimePassword::get_counter() const
{
return counter;
}
HMACbasedOneTimePassword::HMACbasedOneTimePassword() : OneTimePassword(""), counter(0) {}
HMACbasedOneTimePassword::HMACbasedOneTimePassword(const std::string &key) : OneTimePassword(key), counter(0) {}
HMACbasedOneTimePassword::HMACbasedOneTimePassword(const std::string &key, uint64_t starting_point) : OneTimePassword(key), counter(starting_point) {}
#endif //! HMAC_BASED_ONE_TIME_PASSWORD_H
#ifndef TIME_BASE_ONE_TIME_PASSWORD_H
#define TIME_BASE_ONE_TIME_PASSWORD_H
class TimebasedOneTimePassword : public HMACbasedOneTimePassword
{
public:
int32_t generate_totp(const std::string &timezone);
std::vector<int32_t> generate_totps(const std::string &timezone, int window = 10);
TimebasedOneTimePassword();
TimebasedOneTimePassword(const std::string &key);
};
int32_t TimebasedOneTimePassword::generate_totp(const std::string &timezone)
{
std::time_t local_time = get_current_time_in_timezone(timezone);
long current_time = local_time / 30;
set_counter(current_time);
return generate_hotp();
}
std::vector<int32_t> TimebasedOneTimePassword::generate_totps(const std::string &timezone, int window)
{
std::time_t local_time = get_current_time_in_timezone(timezone);
long current_time = local_time / 30;
std::vector<int32_t> results;
results.reserve(window);
for (int i = -window; i <= window; ++i)
{
long time_interval = current_time + i;
set_counter(time_interval);
int32_t otp_str = generate_hotp();
results.push_back(otp_str);
}
return results;
}
TimebasedOneTimePassword::TimebasedOneTimePassword() : HMACbasedOneTimePassword("") {}
TimebasedOneTimePassword::TimebasedOneTimePassword(const std::string &key) : HMACbasedOneTimePassword(key) {}
#endif //! TIME_BASE_ONE_TIME_PASSWORD_H
#include <iostream>
int main()
{
// Example secret key, Use it with Google Authenticator, Microsoft Authenticator, Authy, ..
std::string secret = "BZ5NOX5OXOV2ABV6";
{
std::cout << "===============================================" << std::endl;
std::cout << "Time-based one-time password (TOTP)" << std::endl;
const int WINDOW = 10;
TimebasedOneTimePassword otp(secret);
std::vector<std::string> timezones = otp.get38_timezones();
for (size_t k = 0; k != timezones.size(); ++k)
{
std::vector<int32_t> passwords = otp.generate_totps(timezones[k], WINDOW);
for (int32_t password : passwords)
{
std::cout << "Generated OTP: " << password << std::endl;
}
}
}
{
std::cout << "===============================================" << std::endl;
std::cout << "HMAC-based One-Time Password (HOTP)" << std::endl;
const int MAX_OTP = 10;
const uint64_t COUNTER = 0;
HMACbasedOneTimePassword otp(secret, COUNTER);
for (size_t i = 0; i != MAX_OTP; ++i)
{
int32_t password = otp.generate_hotp();
std::cout << "Generated OTP: " << password << std::endl;
}
}
return 0;
}
@nongvantinh
Copy link
Author

C++17 implementation for one-time passwords. It provides HMAC-based and time-based implementations of one-time passwords. To use it, just compile it with:

// Compile: g++ generate-otp.cpp --std=c++17 -Wall -Wextra -o otp_generator
// Run: ./otp_generator

Insert the secret key into the authenticator apps (Google, Microsoft, Authy) to use it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment