Skip to content

Instantly share code, notes, and snippets.

@nongvantinh
Created September 22, 2024 09:21
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>
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;
};
#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;
}
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:
std::string 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);
};
std::string 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;
std::ostringstream otp;
otp << std::setw(6) << std::setfill('0') << code;
set_counter(get_counter() + 1);
return otp.str();
}
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 : protected HMACbasedOneTimePassword
{
public:
std::string generate_totp();
std::vector<std::string> generate_totps(int window = 10);
TimebasedOneTimePassword();
TimebasedOneTimePassword(const std::string &key);
};
std::string TimebasedOneTimePassword::generate_totp()
{
auto time_step = std::chrono::system_clock::now().time_since_epoch();
long current_time = std::chrono::duration_cast<std::chrono::seconds>(time_step).count() / 30;
set_counter(current_time);
return generate_hotp();
}
std::vector<std::string> TimebasedOneTimePassword::generate_totps(int window)
{
auto time_step = std::chrono::system_clock::now().time_since_epoch();
long current_time = std::chrono::duration_cast<std::chrono::seconds>(time_step).count() / 30;
std::vector<std::string> results;
results.reserve(window);
for (int i = -window; i <= window; ++i)
{
long time_interval = current_time + i;
set_counter(time_interval);
std::string 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 = "JBSWY3DPEHPK3PXP";
{
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> passwords = otp.generate_totps(WINDOW);
for (auto 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)
{
std::string 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