#include <iostream>
#include <iomanip>
#include <vector>
#include <string>
#include <chrono>
#include <cmath>
#include <sstream>
#include <algorithm>
#include <cstring>
#include <cstdint>
class TOTP {
private:
// Вспомогательные функции для SHA1
static uint32_t leftRotate(uint32_t value, uint32_t bits) {
return (value << bits) | (value >> (32 - bits));
}
// SHA1 реализация
static std::vector<uint8_t> sha1(const std::vector<uint8_t>& message) {
uint32_t h0 = 0x67452301;
uint32_t h1 = 0xEFCDAB89;
uint32_t h2 = 0x98BADCFE;
uint32_t h3 = 0x10325476;
uint32_t h4 = 0xC3D2E1F0;
// Предварительная обработка
uint64_t ml = message.size() * 8;
std::vector<uint8_t> msg = message;
// Добавляем бит 1
msg.push_back(0x80);
// Добавляем нули до длины ≡ 448 mod 512
while ((msg.size() * 8) % 512 != 448) {
msg.push_back(0x00);
}
// Добавляем длину сообщения (64 бита, big-endian)
for (int i = 7; i >= 0; i--) {
msg.push_back((ml >> (i * 8)) & 0xFF);
}
// Обработка по 512-битным блокам
for (size_t i = 0; i < msg.size(); i += 64) {
uint32_t w[80];
// Разбиваем блок на 16 слов
for (int j = 0; j < 16; j++) {
w[j] = (msg[i + j * 4] << 24) |
(msg[i + j * 4 + 1] << 16) |
(msg[i + j * 4 + 2] << 8) |
msg[i + j * 4 + 3];
}
// Расширяем 16 слов в 80 слов
for (int j = 16; j < 80; j++) {
w[j] = leftRotate(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
}
// Инициализация рабочих переменных
uint32_t a = h0;
uint32_t b = h1;
uint32_t c = h2;
uint32_t d = h3;
uint32_t e = h4;
// Основной цикл
for (int j = 0; j < 80; j++) {
uint32_t f, k;
if (j < 20) {
f = (b & c) | ((~b) & d);
k = 0x5A827999;
} else if (j < 40) {
f = b ^ c ^ d;
k = 0x6ED9EBA1;
} else if (j < 60) {
f = (b & c) | (b & d) | (c & d);
k = 0x8F1BBCDC;
} else {
f = b ^ c ^ d;
k = 0xCA62C1D6;
}
uint32_t temp = leftRotate(a, 5) + f + e + k + w[j];
e = d;
d = c;
c = leftRotate(b, 30);
b = a;
a = temp;
}
// Добавляем к результату
h0 += a;
h1 += b;
h2 += c;
h3 += d;
h4 += e;
}
// Формируем итоговый хэш
std::vector<uint8_t> hash(20);
for (int i = 0; i < 4; i++) {
hash[i] = (h0 >> (24 - i * 8)) & 0xFF;
hash[i + 4] = (h1 >> (24 - i * 8)) & 0xFF;
hash[i + 8] = (h2 >> (24 - i * 8)) & 0xFF;
hash[i + 12] = (h3 >> (24 - i * 8)) & 0xFF;
hash[i + 16] = (h4 >> (24 - i * 8)) & 0xFF;
}
return hash;
}
// HMAC-SHA1 реализация
static std::vector<uint8_t> hmacSha1(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& message) {
const size_t blockSize = 64; // Размер блока для SHA1
std::vector<uint8_t> keyBlock(blockSize, 0);
// Если ключ длиннее блока, хэшируем его
if (key.size() > blockSize) {
auto hashedKey = sha1(key);
std::copy(hashedKey.begin(), hashedKey.end(), keyBlock.begin());
} else {
std::copy(key.begin(), key.end(), keyBlock.begin());
}
// Создаем inner и outer padding
std::vector<uint8_t> oPad(blockSize, 0x5C);
std::vector<uint8_t> iPad(blockSize, 0x36);
// Выполняем XOR ключа с padding
std::vector<uint8_t> innerKey(blockSize);
std::vector<uint8_t> outerKey(blockSize);
for (size_t i = 0; i < blockSize; i++) {
innerKey[i] = keyBlock[i] ^ iPad[i];
outerKey[i] = keyBlock[i] ^ oPad[i];
}
// inner = hash((key ^ iPad) || message)
std::vector<uint8_t> inner = innerKey;
inner.insert(inner.end(), message.begin(), message.end());
auto innerHash = sha1(inner);
// outer = hash((key ^ oPad) || innerHash)
std::vector<uint8_t> outer = outerKey;
outer.insert(outer.end(), innerHash.begin(), innerHash.end());
return sha1(outer);
}
// Декодирование Base32 строки
static std::vector<uint8_t> base32Decode(const std::string& encoded) {
const std::string base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
std::vector<uint8_t> result;
int buffer = 0;
int bitsLeft = 0;
for (char c : encoded) {
if (c == '=') break; // Игнорируем padding
size_t index = base32Chars.find(c);
if (index == std::string::npos) {
continue; // Пропускаем невалидные символы
}
buffer = (buffer << 5) | static_cast<uint8_t>(index);
bitsLeft += 5;
if (bitsLeft >= 8) {
result.push_back(static_cast<uint8_t>((buffer >> (bitsLeft - 8)) & 0xFF));
bitsLeft -= 8;
}
}
return result;
}
// Проверка, является ли строка Base32
static bool isBase32(const std::string& str) {
const std::string base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
for (char c : str) {
if (base32Chars.find(c) == std::string::npos && c != '=') {
return false;
}
}
return true;
}
public:
// Генерация TOTP кода
static std::string generateTOTP(const std::string& key,
int timeStep = 30,
int digits = 6) {
// Получаем текущее время в секундах
auto now = std::chrono::system_clock::now();
auto epoch = now.time_since_epoch();
uint64_t time = std::chrono::duration_cast<std::chrono::seconds>(epoch).count();
// Вычисляем счетчик времени
uint64_t counter = time / timeStep;
// Конвертируем счетчик в 8-байтовое представление (big-endian)
std::vector<uint8_t> counterBytes(8);
for (int i = 7; i >= 0; i--) {
counterBytes[i] = (counter >> (i * 8)) & 0xFF;
}
// Подготавливаем ключ
std::vector<uint8_t> secretKey;
if (isBase32(key)) {
secretKey = base32Decode(key);
} else {
secretKey.assign(key.begin(), key.end());
}
// Вычисляем HMAC-SHA1
std::vector<uint8_t> hmacResult = hmacSha1(secretKey, counterBytes);
// Динамический обрез (Dynamic Truncation) - RFC 4226
int offset = hmacResult[hmacResult.size() - 1] & 0x0F;
// Получаем 4-байтовое число из результата HMAC
uint32_t binary = 0;
for (int i = 0; i < 4; i++) {
binary = (binary << 8) | hmacResult[offset + i];
}
// Оставляем только 31 бит (убираем старший бит)
binary = binary & 0x7FFFFFFF;
// Приводим к нужному количеству цифр
uint32_t otp = binary % static_cast<uint32_t>(std::pow(10, digits));
// Форматируем результат с ведущими нулями
std::ostringstream oss;
oss << std::setw(digits) << std::setfill('0') << otp;
return oss.str();
}
// Проверка TOTP кода (с учетом временного окна)
static bool verifyTOTP(const std::string& key,
const std::string& token,
int timeStep = 30,
int digits = 6,
int window = 1) {
// Получаем текущее время в секундах
auto now = std::chrono::system_clock::now();
auto epoch = now.time_since_epoch();
uint64_t time = std::chrono::duration_cast<std::chrono::seconds>(epoch).count();
// Подготавливаем ключ
std::vector<uint8_t> secretKey;
if (isBase32(key)) {
secretKey = base32Decode(key);
} else {
secretKey.assign(key.begin(), key.end());
}
// Проверяем в окне window
for (int i = -window; i <= window; i++) {
// Корректируем счетчик на i шагов
uint64_t counter = time / timeStep + i;
// Конвертируем счетчик в 8-байтовое представление (big-endian)
std::vector<uint8_t> counterBytes(8);
for (int j = 7; j >= 0; j--) {
counterBytes[j] = (counter >> (j * 8)) & 0xFF;
}
// Вычисляем HMAC-SHA1
std::vector<uint8_t> hmacResult = hmacSha1(secretKey, counterBytes);
// Динамический обрез
int offset = hmacResult[hmacResult.size() - 1] & 0x0F;
// Получаем 4-байтовое число
uint32_t binary = 0;
for (int j = 0; j < 4; j++) {
binary = (binary << 8) | hmacResult[offset + j];
}
// Оставляем только 31 бит
binary = binary & 0x7FFFFFFF;
// Приводим к нужному количеству цифр
uint32_t otp = binary % static_cast<uint32_t>(std::pow(10, digits));
// Форматируем
std::ostringstream oss;
oss << std::setw(digits) << std::setfill('0') << otp;
if (oss.str() == token) {
return true;
}
}
return false;
}
// Генерация Base32 ключа (совместимого с Google Authenticator)
static std::string generateBase32Key(int length = 16) {
const std::string base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
std::string key;
// Используем текущее время как seed для простоты
auto seed = std::chrono::system_clock::now().time_since_epoch().count();
srand(static_cast<unsigned int>(seed));
for (int i = 0; i < length; i++) {
key += base32Chars[rand() % base32Chars.size()];
}
return key;
}
// Кодирование в Base32 (дополнительная функция)
static std::string base32Encode(const std::vector<uint8_t>& data) {
const std::string base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
std::string result;
int buffer = 0;
int bitsLeft = 0;
for (uint8_t byte : data) {
buffer = (buffer << 8) | byte;
bitsLeft += 8;
while (bitsLeft >= 5) {
int index = (buffer >> (bitsLeft - 5)) & 0x1F;
result += base32Chars[index];
bitsLeft -= 5;
}
}
if (bitsLeft > 0) {
int index = (buffer << (5 - bitsLeft)) & 0x1F;
result += base32Chars[index];
}
// Добавляем padding если нужно
while (result.size() % 8 != 0) {
result += '=';
}
return result;
}
};
// Демонстрация работы
int main() {
std::cout << "=== TOTP Алгоритм на чистом C++ ===\n" << std::endl;
// Пример 1: Генерация и проверка с Base32 ключом
{
std::string secretKey = "JBSWY3DPEHPK3PXP"; // Пример из RFC 6238
std::cout << "Пример 1: Проверка с известным ключом" << std::endl;
std::cout << "Секретный ключ (Base32): " << secretKey << std::endl;
std::string totpCode = TOTP::generateTOTP(secretKey);
std::cout << "Текущий TOTP код: " << totpCode << std::endl;
// Демонстрация проверки
std::cout << "\nПроверка правильного кода: ";
if (TOTP::verifyTOTP(secretKey, totpCode)) {
std::cout << "УСПЕХ" << std::endl;
} else {
std::cout << "ОШИБКА" << std::endl;
}
std::cout << "Проверка неправильного кода (123456): ";
if (TOTP::verifyTOTP(secretKey, "123456")) {
std::cout << "УСПЕХ (это не должно случиться!)" << std::endl;
} else {
std::cout << "ОТКЛОНЕНО (как и ожидалось)" << std::endl;
}
}
std::cout << "\n" << std::string(50, '-') << "\n" << std::endl;
// Пример 2: Генерация случайного ключа
{
std::string randomKey = TOTP::generateBase32Key();
std::cout << "Пример 2: Случайный ключ" << std::endl;
std::cout << "Сгенерированный ключ: " << randomKey << std::endl;
std::string totpCode = TOTP::generateTOTP(randomKey, 30, 8);
std::cout << "8-значный TOTP код: " << totpCode << std::endl;
}
std::cout << "\n" << std::string(50, '-') << "\n" << std::endl;
// Пример 3: Интерактивная проверка
{
std::cout << "Пример 3: Интерактивная проверка" << std::endl;
// Фиксированный ключ для демонстрации
std::string demoKey = "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ";
std::cout << "Демонстрационный ключ: " << demoKey << std::endl;
std::cout << "Текущий код: " << TOTP::generateTOTP(demoKey) << std::endl;
std::cout << "\nВведите код для проверки: ";
std::string userCode;
std::cin >> userCode;
if (TOTP::verifyTOTP(demoKey, userCode)) {
std::cout << "✓ Код верный!" << std::endl;
} else {
std::cout << "✗ Неверный код." << std::endl;
std::cout << "Правильный код был: " << TOTP::generateTOTP(demoKey) << std::endl;
}
}
std::cout << "\n" << std::string(50, '-') << "\n" << std::endl;
// Пример 4: Работа с raw ключом (не Base32)
{
std::cout << "Пример 4: Raw ключ (не Base32)" << std::endl;
std::string rawKey = "MySecretKey123!";
std::cout << "Raw ключ: " << rawKey << std::endl;
std::string totpCode = TOTP::generateTOTP(rawKey);
std::cout << "TOTP код: " << totpCode << std::endl;
}
return 0;
}