Зашифрована інформація про гру (Task API)

Якщо вам необхідно безпечно передати дані про поточного гравця або команду під час гри стороннім зовнішнім сервісам (наприклад, Telegram-боту, скрипту авторизації або кастомному сайту з флеш-грою), використовуйте генерацію зашифрованого рядка.

У цьому випадку навіть технічно підковані гравці не зможуть побачити, які саме дані передаються в URL або параметрах скрипта, і не зможуть підмінити їх самостійно. Це особливо критично на іграх довгого формату (Long).

  • Перейдіть до редактора вашої гри.
  • Знайдіть розділ “API для зовнішніх сайтів / телеграм ботів”.
  • Натисніть кнопку “Згенерувати”. Движок створить унікальний ключ (наприклад, XgHxzCvUSXwPfR7wBvdY4xHC6tSFLewx).

У тексті завдання, підказки або в параметрах посилання на зовнішній сервіс пропишіть конструкцію виду:

!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}

Приклад завдання в грі

  • v2. — версія формату. Перевіряйте цей префікс: якщо він зміниться, ваш скрипт має повідомити про помилку, а не мовчки видати сміття.
  • Далі — base64url (алфавіт -_, без вирівнювальних =) від склейки трьох частин: IV (12 байтів) + тег автентифікації (16 байтів) + сам шифротекст.
  • Алгоритм — aes-256-gcm. Ключ шифрування — sha256 від ключа гри (сирі 32 байти, не hex).

Через випадковий IV ті самі дані щоразу дають різний рядок — це нормально і зроблено навмисно. Не порівнюйте рядки між собою і не використовуйте їх як ключ кешу: спочатку розшифруйте, порівнюйте вже JSON.

Тег автентифікації означає, що виправлений у посиланні рядок розшифрувати не вдасться — підробити дані гравець не зможе.

<note important>Раніше рядок був hex-кодованим Blowfish (ECB). Цей формат більше не видається: Blowfish зник із сучасних збірок OpenSSL, і шифрування ламалося. Якщо у вас є готовий скрипт розшифрування, замініть його на один із прикладів нижче.</note>

$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)

*(Потрібна бібліотека 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}
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}
  • uk/authors_main/task_editor/advanced/api_main.txt
  • Востаннє змінено: 2026/08/16 23:05
  • (зовнішнє редагування)