Сижу я за пк, захожу уже не помню зачем на гитхаб и вижу сообщение, что мне нужно подключить 2Fa, который будет выдавать мне коды по времени (TOTP):

qrcode
qrcode

Те кто не знают, это одноразовый генератор кодов каждое n (обычно 30) секунд. Бесплатного аутентификатора на пк я не нашел, честно, мне лень скачивать аутентификатор на телефон и каждый раз логиниться через него, тем более у меня будет опыт если я сам сделаю менеджер TOTP (Time-based One Time Password)

Сканирование QR-кода

Скорее всего вам будет выдан QR код, чтобы узнать код его можно заскринить и отдать QR сканеру, но мы хотим автоматизации поэтому пишем следующий код на библиотеках OpenCV и libqrencode:

QRCodeDetector detector = QRCodeDetector::QRCodeDetector();
Mat points, rectImage;

try {
    string result = detector.detectAndDecode(image, points, rectImage);
    if (result.empty())
        cerr << "Can't scan qr code :(" << endl;
    else
        return result;
} catch (...) {
    cerr << "Invalid error, can't read qr code :(" << endl;
}

return "";

Проверяем и получаем результат:

qr-scan-result
qr-scan-result

Но далее я столкнулся с проблемой, что QRDetector не распознает qr коды прям от начала фотографии и просто кидает ексепшн. Для этого нам необходимо расширить наше фото на несколько пикселей, я взял 25:

if (!detector.detect(image, points)) {
        int indent = 25;
        int newSize = image.rows + indent * 2;
        Mat newImage(newSize, newSize, image.type(), Scalar(255, 255, 255));
        image.copyTo(newImage(Rect(indent, indent, image.cols, image.rows)));

        image = newImage;
    }

И теперь фото расширится и QR-код будет распознаваться верно

Создание TOTP кодов

Далее переходим непосредственно к TOTP менеджеру. В результате чтения qr-кода мы получили строку содержащую секретный ключ, и чтобы с помощью него получить код нужно сделать следующее:

  1. Получить время:

auto clock = chrono::system_clock::now().time_since_epoch();
auto currentTime = chrono::duration_cast<chrono::milliseconds>(clock).count();
string time = Hex::timeInHex(currentTime, 30);
  1. Вычислить HMAC-SHA1 хеш от времени и ключа:

auto timeBytes = Hex::hexStringToBytes(time);
auto keyBytes = Hex::hexStringToBytes(key);
auto hash = Hex::hmac("SHA1", keyBytes, timeBytes);
  1. Получить последние 4 байта хеша и преобразовать в целое число:

int offset = hash[hash.size() - 1] & 0xF; // эквивалентно сдвигу вправо на 4 бита
int binary = ((hash[offset] & 0x7F) << 24)
    | ((hash[offset + 1] & 0xFF) << 16)
    | ((hash[offset + 2] & 0xFF) << 8)
    | (hash[offset + 3] & 0xFF); // Значение из четырёх байтов
  1. Разделить его на 10 в степени длины кода (обычно это 10⁶) и получить остаток:

int otp = binary % ((int) pow(10, digits));

Проверяем и получаем на выходе наш долгожданный код:

code
code