{{indexmenu_n>40}} ====== Зашифрованная информация об игре (Task API) ====== ===== Зачем это нужно? ===== Если вам необходимо безопасно передать данные о текущем игроке или команде во время игры сторонним внешним сервисам (например, Telegram-боту, скрипту авторизации или кастомному сайту с флеш-игрой), используйте генерацию зашифрованной строки. В этом случае технически подкованные игроки не смогут увидеть, какие именно данные передаются в URL или параметрах скрипта, и не смогут подделать их самостоятельно. Это особенно критично на играх длинного формата (Long). ===== Как это настроить? ===== ==== 1. Генерация секретного ключа ==== * Перейдите в редактор вашей игры. * Найдите раздел **"API для внешних сайтов / телеграм ботов"**. * Нажмите кнопку **"Сгенерировать"**. Движок создаст уникальный ключ (например, ''XgHxzCvUSXwPfR7wBvdY4xHC6tSFLewx''). ==== 2. Использование плейсхолдера в задании ==== В тексте задания, подсказки или в параметрах ссылки на внешний сервис пропишите конструкцию вида: !api:поле1,поле2! Доступные поля для шифрования (указываются через запятую): * **''user_id''** — ID игрока. В расшифрованном JSON преобразуется в ключ **''u''**. * **''user_name''** — логин игрока. В JSON — **''un''**. * **''team_id''** — ID команды. В JSON — **''tm''**. * **''team_name''** — название команды. В JSON — **''tmn''**. * **''task_n''** — порядковый номер текущего уровня. В JSON — **''t''**. * **''task_id''** — ID текущего уровня. В JSON — **''tid''**. Уникален внутри игры, а не по всему сайту: одно и то же число в разных играх означает разные уровни. Ключом во внешней базе должна быть пара **''game_id''** + **''task_id''**. * **''game_id''** — ID игры. В JSON — **''gid''**. ==== Пример генерации ==== Если вы пропишете в задании: !api:user_id,task_id! Движок сгенерирует зашифрованную строку вида: v2.vyJ42f20mXmdKS5bAr6GDoqqUP2pw4f4vbM7mX1t3SvY1LMZ_Jr80XjEaXkB6iB2l3eN3g Ее можно расшифровать на вашем сервере с помощью ключа игры (см. примеры ниже). В результате вы получите JSON: {"u": 162862, "tid": 42719} [[https://qeng.org/game.php?jump_to&gid=3493&task_id=42719|Пример задания в игре]] ==== Формат строки ==== * ''v2.'' — версия формата. Проверяйте этот префикс: если он изменится, ваш скрипт должен сообщить об ошибке, а не молча выдать мусор. * Дальше — **base64url** (алфавит ''-_'', без выравнивающих ''='') от склейки трех частей: **IV** (12 байт) + **тег аутентификации** (16 байт) + сам шифротекст. * Алгоритм — **aes-256-gcm**. Ключ шифрования — **''sha256''** от ключа игры (сырые 32 байта, не hex). Из-за случайного IV **одни и те же данные каждый раз дают разную строку** — это нормально и сделано специально. Не сравнивайте строки между собой и не используйте их как ключ кеша: сначала расшифруйте, сравнивайте уже JSON. Тег аутентификации означает, что исправленную в ссылке строку расшифровать не удастся — подделать данные игрок не сможет. Раньше строка была hex-кодированным Blowfish (ECB). Этот формат больше не выдается: Blowfish исчез из современных сборок OpenSSL, и шифрование ломалось. Если у вас есть готовый скрипт расшифровки, замените его на один из примеров ниже. ===== Примеры расшифровки на сервере ===== ==== PHP ==== $data = 'v2.vyJ42f20mXmdKS5bAr6GDoqqUP2pw4f4vbM7mX1t3SvY1LMZ_Jr80XjEaXkB6iB2l3eN3g'; $key = 'XgHxzCvUSXwPfR7wBvdY4xHC6tSFLewx'; $raw = base64_decode(strtr(substr($data, 3), '-_', '+/')); $decoded = json_decode( openssl_decrypt( substr($raw, 28), // шифротекст 'aes-256-gcm', hash('sha256', $key, true), // ключ игры -> 32 байта OPENSSL_RAW_DATA, substr($raw, 0, 12), // IV substr($raw, 12, 16) // тег аутентификации ), true ); // $decoded => array('u' => 162862, 'tid' => 42719) ==== Python ==== *(Требуется библиотека cryptography: ''pip install cryptography'')* import base64 import hashlib import json import typing from cryptography.hazmat.primitives.ciphers.aead import AESGCM def decrypt_qeng_data(data: str, key: str) -> typing.Dict[str, typing.Union[str, int]]: if not data.startswith('v2.'): raise ValueError('unknown payload format: ' + data[:3]) body = data[3:] body += '=' * (-len(body) % 4) # base64url приходит без выравнивания raw = base64.urlsafe_b64decode(body) iv, tag, encrypted = raw[:12], raw[12:28], raw[28:] aes_key = hashlib.sha256(key.encode()).digest() # cryptography ждет тег в конце шифротекста return json.loads(AESGCM(aes_key).decrypt(iv, encrypted + tag, None)) # Использование data = 'v2.vyJ42f20mXmdKS5bAr6GDoqqUP2pw4f4vbM7mX1t3SvY1LMZ_Jr80XjEaXkB6iB2l3eN3g' key = 'XgHxzCvUSXwPfR7wBvdY4xHC6tSFLewx' print(decrypt_qeng_data(data, key)) # => {'u': 162862, 'tid': 42719} ==== Ruby ==== require 'base64' require 'digest' require 'json' require 'openssl' def qeng_data_decrypt(data, key) raise "unknown payload format: #{data[0, 3]}" unless data.start_with?('v2.') body = data[3..] raw = Base64.urlsafe_decode64(body + '=' * (-body.length % 4)) crypto = OpenSSL::Cipher.new('aes-256-gcm').decrypt crypto.key = Digest::SHA256.digest(key) # ключ игры -> 32 байта crypto.iv = raw[0, 12] crypto.auth_tag = raw[12, 16] crypto.auth_data = '' JSON.parse(crypto.update(raw[28..]) + crypto.final, symbolize_names: true) end # Использование data = 'v2.vyJ42f20mXmdKS5bAr6GDoqqUP2pw4f4vbM7mX1t3SvY1LMZ_Jr80XjEaXkB6iB2l3eN3g' key = 'XgHxzCvUSXwPfR7wBvdY4xHC6tSFLewx' puts qeng_data_decrypt(data, key) # => {:u=>162862, :tid=>42719}