git.fiddlerwoaroof.com
Raw Blame History
#include <cmath>
#include <ctime>
#include <functional>
#include <iomanip>
#include <iostream>
#include <unistd.h>

#include <cryptopp/base32.h>
#include <cryptopp/hex.h>
#include <cryptopp/hmac.h>
#include <cryptopp/osrng.h>

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<uint8_t>(*byte);
      result |= val << (byteCount * 8);
    }
    return result;
  }

  unsigned long truncate(const string &mac) const {
    uint8_t offset = static_cast<uint8_t >(mac[19]) & static_cast<uint8_t>(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<CryptoPP::SHA1> 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<time_t>(-1)) {
    std::cerr << "time() failed..." << endl;
  }
  return theTime;
}

typedef std::function<std::string(const CryptoPP::SecByteBlock&)> byte_encoder;
typedef std::function<time_t()> 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;
}