#include #include #include #include #include #include #include #include #include #include using namespace std; // CLASSES class OTPGenerator { public: virtual unsigned long generate(const CryptoPP::SecByteBlock &key, const CryptoPP::Integer &input) const = 0; }; int ipow(int base, int exp) { int result = 1; while (exp) { if (exp & 1) result *= base; exp >>= 1; base *= base; } return result; } class HOTP : public OTPGenerator { private: unsigned int code_digits; unsigned long bytesToInt(const string &bytes) const { unsigned long result = 0; auto byteCount = bytes.size() - 1; for (auto byte = bytes.cbegin(); byte < bytes.cend(); byte++, byteCount--) { const uint8_t val = static_cast(*byte); result |= val << (byteCount * 8); } return result; } unsigned long truncate(const string &mac) const { uint8_t offset = static_cast(mac[19]) & static_cast(0x0f); string offsetBytes = mac.substr(offset, 4); return bytesToInt(offsetBytes) & 0x7fffffff; } public: HOTP(unsigned int digits): code_digits(digits) {} virtual unsigned long generate(const CryptoPP::SecByteBlock &key, const CryptoPP::Integer &counter) const override { string mac; CryptoPP::SecByteBlock counter_bytes(8); // Do I know that 8 is sufficient here? . . . counter.Encode(counter_bytes.BytePtr(), 8, CryptoPP::Integer::UNSIGNED); CryptoPP::HMAC hmac(key, key.size()); CryptoPP::StringSink *stringSink = new CryptoPP::StringSink(mac); CryptoPP::HashFilter *hashFilter = new CryptoPP::HashFilter(hmac, stringSink); CryptoPP::StringSource ss2(counter_bytes, counter_bytes.size(), true, hashFilter); unsigned long result = truncate(mac); result = result % ipow(10, code_digits); return result; } }; class TOTP : public HOTP { private: unsigned int time_step_size; time_t time_step(const time_t time, const int step = 30) const { // Time is > 0 so division produces the result we want. return time / step; } public: TOTP(unsigned int step, unsigned int digits): time_step_size(step), HOTP(digits) {} virtual unsigned long generate(const CryptoPP::SecByteBlock &key, const CryptoPP::Integer &time) const override { CryptoPP::Integer current_step = time_step(time.ConvertToLong(), time_step_size); return HOTP::generate(key, current_step); } }; // Frontend CryptoPP::SecByteBlock generate_key(unsigned int size) { CryptoPP::AutoSeededRandomPool prng; CryptoPP::SecByteBlock key(size); prng.GenerateBlock(key, key.size()); return key; } time_t get_time() { time_t theTime = time(0); if (theTime == static_cast(-1)) { std::cerr << "time() failed..." << endl; } return theTime; } typedef std::function byte_encoder; typedef std::function time_cb; std::string base32_encode(const CryptoPP::SecByteBlock string) { std::string encoded; CryptoPP::StringSink *stringSink = new CryptoPP::StringSink(encoded); CryptoPP::Base32Encoder *base32Encoder = new CryptoPP::Base32Encoder(stringSink); const byte ALPHABET[32+1] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; CryptoPP::AlgorithmParameters params = CryptoPP::MakeParameters(CryptoPP::Name::EncodingLookupArray(),(const byte *)ALPHABET); base32Encoder->IsolatedInitialize(params); CryptoPP::StringSource ss1(string, string.size(), true, base32Encoder); return encoded; } void deterministic_main( const CryptoPP::SecByteBlock& key, const time_t theTime, const byte_encoder key_encoder, const OTPGenerator& code_generator ) { try { std::cout << "key: " << std::setw(41) << key_encoder(key) << std::endl; unsigned long result = code_generator.generate(key, theTime); cout << setw(46) << setfill('-') << "-" << endl << setfill(' '); cout << "code: " << setw(34) << " " << setw(6) << setfill('0') << result << endl << setfill(' '); } catch (const CryptoPP::Exception &e) { std::cerr << e.what() << endl; exit(1); } } int main() { unsigned int digits = 6; unsigned int time_step = 30; unsigned int key_size = 16; CryptoPP::SecByteBlock key = generate_key(key_size); time_t theTime = get_time(); TOTP code_generator = TOTP(time_step, digits); deterministic_main(key, theTime, base32_encode, code_generator); return 0; }