Содержание

Зашифрованная информация об игре (Task API)

Зачем это нужно?

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

В этом случае технически подкованные игроки не смогут увидеть, какие именно данные передаются в URL или параметрах скрипта, и не смогут подделать их самостоятельно. Это особенно критично на играх длинного формата (Long).

Как это настроить?

1. Генерация секретного ключа

2. Использование плейсхолдера в задании

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

!api:поле1,поле2!

Доступные поля для шифрования (указываются через запятую):

Пример генерации

Если вы пропишете в задании:

!api:user_id,task_id!

Движок сгенерирует зашифрованную строку вида:

v2.vyJ42f20mXmdKS5bAr6GDoqqUP2pw4f4vbM7mX1t3SvY1LMZ_Jr80XjEaXkB6iB2l3eN3g

Ее можно расшифровать на вашем сервере с помощью ключа игры (см. примеры ниже). В результате вы получите JSON:

{"u": 162862, "tid": 42719}

Пример задания в игре

Формат строки

Из-за случайного IV одни и те же данные каждый раз дают разную строку — это нормально и сделано специально. Не сравнивайте строки между собой и не используйте их как ключ кеша: сначала расшифруйте, сравнивайте уже JSON.

Тег аутентификации означает, что исправленную в ссылке строку расшифровать не удастся — подделать данные игрок не сможет.

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

Примеры расшифровки на сервере

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}