Created
September 22, 2024 09:21
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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:
Insert the secret key into the authenticator apps (Google, Microsoft, Authy) to use it.