fork download
  1. #include <iostream>
  2. #include <iomanip>
  3. #include <vector>
  4. #include <string>
  5. #include <chrono>
  6. #include <cmath>
  7. #include <sstream>
  8. #include <algorithm>
  9. #include <cstring>
  10. #include <cstdint>
  11.  
  12. class TOTP {
  13. private:
  14. // Вспомогательные функции для SHA1
  15. static uint32_t leftRotate(uint32_t value, uint32_t bits) {
  16. return (value << bits) | (value >> (32 - bits));
  17. }
  18.  
  19. // SHA1 реализация
  20. static std::vector<uint8_t> sha1(const std::vector<uint8_t>& message) {
  21. uint32_t h0 = 0x67452301;
  22. uint32_t h1 = 0xEFCDAB89;
  23. uint32_t h2 = 0x98BADCFE;
  24. uint32_t h3 = 0x10325476;
  25. uint32_t h4 = 0xC3D2E1F0;
  26.  
  27. // Предварительная обработка
  28. uint64_t ml = message.size() * 8;
  29. std::vector<uint8_t> msg = message;
  30.  
  31. // Добавляем бит 1
  32. msg.push_back(0x80);
  33.  
  34. // Добавляем нули до длины ≡ 448 mod 512
  35. while ((msg.size() * 8) % 512 != 448) {
  36. msg.push_back(0x00);
  37. }
  38.  
  39. // Добавляем длину сообщения (64 бита, big-endian)
  40. for (int i = 7; i >= 0; i--) {
  41. msg.push_back((ml >> (i * 8)) & 0xFF);
  42. }
  43.  
  44. // Обработка по 512-битным блокам
  45. for (size_t i = 0; i < msg.size(); i += 64) {
  46. uint32_t w[80];
  47.  
  48. // Разбиваем блок на 16 слов
  49. for (int j = 0; j < 16; j++) {
  50. w[j] = (msg[i + j * 4] << 24) |
  51. (msg[i + j * 4 + 1] << 16) |
  52. (msg[i + j * 4 + 2] << 8) |
  53. msg[i + j * 4 + 3];
  54. }
  55.  
  56. // Расширяем 16 слов в 80 слов
  57. for (int j = 16; j < 80; j++) {
  58. w[j] = leftRotate(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
  59. }
  60.  
  61. // Инициализация рабочих переменных
  62. uint32_t a = h0;
  63. uint32_t b = h1;
  64. uint32_t c = h2;
  65. uint32_t d = h3;
  66. uint32_t e = h4;
  67.  
  68. // Основной цикл
  69. for (int j = 0; j < 80; j++) {
  70. uint32_t f, k;
  71.  
  72. if (j < 20) {
  73. f = (b & c) | ((~b) & d);
  74. k = 0x5A827999;
  75. } else if (j < 40) {
  76. f = b ^ c ^ d;
  77. k = 0x6ED9EBA1;
  78. } else if (j < 60) {
  79. f = (b & c) | (b & d) | (c & d);
  80. k = 0x8F1BBCDC;
  81. } else {
  82. f = b ^ c ^ d;
  83. k = 0xCA62C1D6;
  84. }
  85.  
  86. uint32_t temp = leftRotate(a, 5) + f + e + k + w[j];
  87. e = d;
  88. d = c;
  89. c = leftRotate(b, 30);
  90. b = a;
  91. a = temp;
  92. }
  93.  
  94. // Добавляем к результату
  95. h0 += a;
  96. h1 += b;
  97. h2 += c;
  98. h3 += d;
  99. h4 += e;
  100. }
  101.  
  102. // Формируем итоговый хэш
  103. std::vector<uint8_t> hash(20);
  104. for (int i = 0; i < 4; i++) {
  105. hash[i] = (h0 >> (24 - i * 8)) & 0xFF;
  106. hash[i + 4] = (h1 >> (24 - i * 8)) & 0xFF;
  107. hash[i + 8] = (h2 >> (24 - i * 8)) & 0xFF;
  108. hash[i + 12] = (h3 >> (24 - i * 8)) & 0xFF;
  109. hash[i + 16] = (h4 >> (24 - i * 8)) & 0xFF;
  110. }
  111.  
  112. return hash;
  113. }
  114.  
  115. // HMAC-SHA1 реализация
  116. static std::vector<uint8_t> hmacSha1(const std::vector<uint8_t>& key,
  117. const std::vector<uint8_t>& message) {
  118. const size_t blockSize = 64; // Размер блока для SHA1
  119.  
  120. std::vector<uint8_t> keyBlock(blockSize, 0);
  121.  
  122. // Если ключ длиннее блока, хэшируем его
  123. if (key.size() > blockSize) {
  124. auto hashedKey = sha1(key);
  125. std::copy(hashedKey.begin(), hashedKey.end(), keyBlock.begin());
  126. } else {
  127. std::copy(key.begin(), key.end(), keyBlock.begin());
  128. }
  129.  
  130. // Создаем inner и outer padding
  131. std::vector<uint8_t> oPad(blockSize, 0x5C);
  132. std::vector<uint8_t> iPad(blockSize, 0x36);
  133.  
  134. // Выполняем XOR ключа с padding
  135. std::vector<uint8_t> innerKey(blockSize);
  136. std::vector<uint8_t> outerKey(blockSize);
  137.  
  138. for (size_t i = 0; i < blockSize; i++) {
  139. innerKey[i] = keyBlock[i] ^ iPad[i];
  140. outerKey[i] = keyBlock[i] ^ oPad[i];
  141. }
  142.  
  143. // inner = hash((key ^ iPad) || message)
  144. std::vector<uint8_t> inner = innerKey;
  145. inner.insert(inner.end(), message.begin(), message.end());
  146. auto innerHash = sha1(inner);
  147.  
  148. // outer = hash((key ^ oPad) || innerHash)
  149. std::vector<uint8_t> outer = outerKey;
  150. outer.insert(outer.end(), innerHash.begin(), innerHash.end());
  151.  
  152. return sha1(outer);
  153. }
  154.  
  155. // Декодирование Base32 строки
  156. static std::vector<uint8_t> base32Decode(const std::string& encoded) {
  157. const std::string base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
  158. std::vector<uint8_t> result;
  159.  
  160. int buffer = 0;
  161. int bitsLeft = 0;
  162.  
  163. for (char c : encoded) {
  164. if (c == '=') break; // Игнорируем padding
  165.  
  166. size_t index = base32Chars.find(c);
  167. if (index == std::string::npos) {
  168. continue; // Пропускаем невалидные символы
  169. }
  170.  
  171. buffer = (buffer << 5) | static_cast<uint8_t>(index);
  172. bitsLeft += 5;
  173.  
  174. if (bitsLeft >= 8) {
  175. result.push_back(static_cast<uint8_t>((buffer >> (bitsLeft - 8)) & 0xFF));
  176. bitsLeft -= 8;
  177. }
  178. }
  179.  
  180. return result;
  181. }
  182.  
  183. // Проверка, является ли строка Base32
  184. static bool isBase32(const std::string& str) {
  185. const std::string base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
  186. for (char c : str) {
  187. if (base32Chars.find(c) == std::string::npos && c != '=') {
  188. return false;
  189. }
  190. }
  191. return true;
  192. }
  193.  
  194. public:
  195. // Генерация TOTP кода
  196. static std::string generateTOTP(const std::string& key,
  197. int timeStep = 30,
  198. int digits = 6) {
  199. // Получаем текущее время в секундах
  200. auto now = std::chrono::system_clock::now();
  201. auto epoch = now.time_since_epoch();
  202. uint64_t time = std::chrono::duration_cast<std::chrono::seconds>(epoch).count();
  203.  
  204. // Вычисляем счетчик времени
  205. uint64_t counter = time / timeStep;
  206.  
  207. // Конвертируем счетчик в 8-байтовое представление (big-endian)
  208. std::vector<uint8_t> counterBytes(8);
  209. for (int i = 7; i >= 0; i--) {
  210. counterBytes[i] = (counter >> (i * 8)) & 0xFF;
  211. }
  212.  
  213. // Подготавливаем ключ
  214. std::vector<uint8_t> secretKey;
  215. if (isBase32(key)) {
  216. secretKey = base32Decode(key);
  217. } else {
  218. secretKey.assign(key.begin(), key.end());
  219. }
  220.  
  221. // Вычисляем HMAC-SHA1
  222. std::vector<uint8_t> hmacResult = hmacSha1(secretKey, counterBytes);
  223.  
  224. // Динамический обрез (Dynamic Truncation) - RFC 4226
  225. int offset = hmacResult[hmacResult.size() - 1] & 0x0F;
  226.  
  227. // Получаем 4-байтовое число из результата HMAC
  228. uint32_t binary = 0;
  229. for (int i = 0; i < 4; i++) {
  230. binary = (binary << 8) | hmacResult[offset + i];
  231. }
  232.  
  233. // Оставляем только 31 бит (убираем старший бит)
  234. binary = binary & 0x7FFFFFFF;
  235.  
  236. // Приводим к нужному количеству цифр
  237. uint32_t otp = binary % static_cast<uint32_t>(std::pow(10, digits));
  238.  
  239. // Форматируем результат с ведущими нулями
  240. std::ostringstream oss;
  241. oss << std::setw(digits) << std::setfill('0') << otp;
  242. return oss.str();
  243. }
  244.  
  245. // Проверка TOTP кода (с учетом временного окна)
  246. static bool verifyTOTP(const std::string& key,
  247. const std::string& token,
  248. int timeStep = 30,
  249. int digits = 6,
  250. int window = 1) {
  251. // Получаем текущее время в секундах
  252. auto now = std::chrono::system_clock::now();
  253. auto epoch = now.time_since_epoch();
  254. uint64_t time = std::chrono::duration_cast<std::chrono::seconds>(epoch).count();
  255.  
  256. // Подготавливаем ключ
  257. std::vector<uint8_t> secretKey;
  258. if (isBase32(key)) {
  259. secretKey = base32Decode(key);
  260. } else {
  261. secretKey.assign(key.begin(), key.end());
  262. }
  263.  
  264. // Проверяем в окне window
  265. for (int i = -window; i <= window; i++) {
  266. // Корректируем счетчик на i шагов
  267. uint64_t counter = time / timeStep + i;
  268.  
  269. // Конвертируем счетчик в 8-байтовое представление (big-endian)
  270. std::vector<uint8_t> counterBytes(8);
  271. for (int j = 7; j >= 0; j--) {
  272. counterBytes[j] = (counter >> (j * 8)) & 0xFF;
  273. }
  274.  
  275. // Вычисляем HMAC-SHA1
  276. std::vector<uint8_t> hmacResult = hmacSha1(secretKey, counterBytes);
  277.  
  278. // Динамический обрез
  279. int offset = hmacResult[hmacResult.size() - 1] & 0x0F;
  280.  
  281. // Получаем 4-байтовое число
  282. uint32_t binary = 0;
  283. for (int j = 0; j < 4; j++) {
  284. binary = (binary << 8) | hmacResult[offset + j];
  285. }
  286.  
  287. // Оставляем только 31 бит
  288. binary = binary & 0x7FFFFFFF;
  289.  
  290. // Приводим к нужному количеству цифр
  291. uint32_t otp = binary % static_cast<uint32_t>(std::pow(10, digits));
  292.  
  293. // Форматируем
  294. std::ostringstream oss;
  295. oss << std::setw(digits) << std::setfill('0') << otp;
  296.  
  297. if (oss.str() == token) {
  298. return true;
  299. }
  300. }
  301. return false;
  302. }
  303.  
  304. // Генерация Base32 ключа (совместимого с Google Authenticator)
  305. static std::string generateBase32Key(int length = 16) {
  306. const std::string base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
  307. std::string key;
  308.  
  309. // Используем текущее время как seed для простоты
  310. auto seed = std::chrono::system_clock::now().time_since_epoch().count();
  311. srand(static_cast<unsigned int>(seed));
  312.  
  313. for (int i = 0; i < length; i++) {
  314. key += base32Chars[rand() % base32Chars.size()];
  315. }
  316.  
  317. return key;
  318. }
  319.  
  320. // Кодирование в Base32 (дополнительная функция)
  321. static std::string base32Encode(const std::vector<uint8_t>& data) {
  322. const std::string base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
  323. std::string result;
  324.  
  325. int buffer = 0;
  326. int bitsLeft = 0;
  327.  
  328. for (uint8_t byte : data) {
  329. buffer = (buffer << 8) | byte;
  330. bitsLeft += 8;
  331.  
  332. while (bitsLeft >= 5) {
  333. int index = (buffer >> (bitsLeft - 5)) & 0x1F;
  334. result += base32Chars[index];
  335. bitsLeft -= 5;
  336. }
  337. }
  338.  
  339. if (bitsLeft > 0) {
  340. int index = (buffer << (5 - bitsLeft)) & 0x1F;
  341. result += base32Chars[index];
  342. }
  343.  
  344. // Добавляем padding если нужно
  345. while (result.size() % 8 != 0) {
  346. result += '=';
  347. }
  348.  
  349. return result;
  350. }
  351. };
  352.  
  353. // Демонстрация работы
  354. int main() {
  355. std::cout << "=== TOTP Алгоритм на чистом C++ ===\n" << std::endl;
  356.  
  357. // Пример 1: Генерация и проверка с Base32 ключом
  358. {
  359. std::string secretKey = "JBSWY3DPEHPK3PXP"; // Пример из RFC 6238
  360.  
  361. std::cout << "Пример 1: Проверка с известным ключом" << std::endl;
  362. std::cout << "Секретный ключ (Base32): " << secretKey << std::endl;
  363.  
  364. std::string totpCode = TOTP::generateTOTP(secretKey);
  365. std::cout << "Текущий TOTP код: " << totpCode << std::endl;
  366.  
  367. // Демонстрация проверки
  368. std::cout << "\nПроверка правильного кода: ";
  369. if (TOTP::verifyTOTP(secretKey, totpCode)) {
  370. std::cout << "УСПЕХ" << std::endl;
  371. } else {
  372. std::cout << "ОШИБКА" << std::endl;
  373. }
  374.  
  375. std::cout << "Проверка неправильного кода (123456): ";
  376. if (TOTP::verifyTOTP(secretKey, "123456")) {
  377. std::cout << "УСПЕХ (это не должно случиться!)" << std::endl;
  378. } else {
  379. std::cout << "ОТКЛОНЕНО (как и ожидалось)" << std::endl;
  380. }
  381. }
  382.  
  383. std::cout << "\n" << std::string(50, '-') << "\n" << std::endl;
  384.  
  385. // Пример 2: Генерация случайного ключа
  386. {
  387. std::string randomKey = TOTP::generateBase32Key();
  388. std::cout << "Пример 2: Случайный ключ" << std::endl;
  389. std::cout << "Сгенерированный ключ: " << randomKey << std::endl;
  390.  
  391. std::string totpCode = TOTP::generateTOTP(randomKey, 30, 8);
  392. std::cout << "8-значный TOTP код: " << totpCode << std::endl;
  393. }
  394.  
  395. std::cout << "\n" << std::string(50, '-') << "\n" << std::endl;
  396.  
  397. // Пример 3: Интерактивная проверка
  398. {
  399. std::cout << "Пример 3: Интерактивная проверка" << std::endl;
  400.  
  401. // Фиксированный ключ для демонстрации
  402. std::string demoKey = "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ";
  403.  
  404. std::cout << "Демонстрационный ключ: " << demoKey << std::endl;
  405. std::cout << "Текущий код: " << TOTP::generateTOTP(demoKey) << std::endl;
  406.  
  407. std::cout << "\nВведите код для проверки: ";
  408. std::string userCode;
  409. std::cin >> userCode;
  410.  
  411. if (TOTP::verifyTOTP(demoKey, userCode)) {
  412. std::cout << "✓ Код верный!" << std::endl;
  413. } else {
  414. std::cout << "✗ Неверный код." << std::endl;
  415. std::cout << "Правильный код был: " << TOTP::generateTOTP(demoKey) << std::endl;
  416. }
  417. }
  418.  
  419. std::cout << "\n" << std::string(50, '-') << "\n" << std::endl;
  420.  
  421. // Пример 4: Работа с raw ключом (не Base32)
  422. {
  423. std::cout << "Пример 4: Raw ключ (не Base32)" << std::endl;
  424.  
  425. std::string rawKey = "MySecretKey123!";
  426. std::cout << "Raw ключ: " << rawKey << std::endl;
  427.  
  428. std::string totpCode = TOTP::generateTOTP(rawKey);
  429. std::cout << "TOTP код: " << totpCode << std::endl;
  430. }
  431.  
  432. return 0;
  433. }
Success #stdin #stdout 0.01s 5320KB
stdin
Standard input is empty
stdout
=== TOTP Алгоритм на чистом C++ ===

Пример 1: Проверка с известным ключом
Секретный ключ (Base32): JBSWY3DPEHPK3PXP
Текущий TOTP код: 782045

Проверка правильного кода: УСПЕХ
Проверка неправильного кода (123456): ОТКЛОНЕНО (как и ожидалось)

--------------------------------------------------

Пример 2: Случайный ключ
Сгенерированный ключ: IFQLGUU4VJ7FK7TH
8-значный TOTP код: 16593261

--------------------------------------------------

Пример 3: Интерактивная проверка
Демонстрационный ключ: GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ
Текущий код: 369264

Введите код для проверки: ✗ Неверный код.
Правильный код был: 369264

--------------------------------------------------

Пример 4: Raw ключ (не Base32)
Raw ключ: MySecretKey123!
TOTP код: 270906