Содержание

Encrypted Game Information (Task API)

Why?

If you need to pass some information about the game during the game to third-party services (Telegram bots, third-party scripts, custom auth setups etc.), it is recommended to use the generation of an encrypted string. In this case, even tech-savvy players will not be able to see what information is being passed or modify it themselves (which is especially relevant for long formats).

How?

Key Generation

You need to create a key in the game. To do this, click the “Generate” button in the game editor under the API for external websites/Telegram bots” section. This will create a key (e.g. XgHxzCvUSXwPfR7wBvdY4xHC6tSFLewx).

Use in Tasks

In tasks and game descriptions, you can use the construction:

!api:data!

The data fields needed by the external server are specified separated by commas:

Example

To get an encrypted string in the game:

!api:user_id,task_id!

This will generate the encrypted string:

v2.vyJ42f20mXmdKS5bAr6GDoqqUP2pw4f4vbM7mX1t3SvY1LMZ_Jr80XjEaXkB6iB2l3eN3g

which can be decrypted on your server using the game key (see the examples below) into:

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

Example task in game

String Format

Because the IV is random, the same data yields a different string every time — that is intended. Do not compare the strings to each other and do not use one as a cache key: decrypt first, compare the JSON.

The authentication tag means a string edited in the link will fail to decrypt, so a player cannot forge the data.

<note important>The string used to be hex-encoded Blowfish (ECB). That format is no longer generated: Blowfish is gone from current OpenSSL builds and the encryption was breaking. If you have a decryption script already, replace it with one of the examples below.</note>

Decryption

PHP

$data = 'v2.vyJ42f20mXmdKS5bAr6GDoqqUP2pw4f4vbM7mX1t3SvY1LMZ_Jr80XjEaXkB6iB2l3eN3g';
$key  = 'XgHxzCvUSXwPfR7wBvdY4xHC6tSFLewx';
 
$raw = base64_decode(strtr(substr($data, 3), '-_', '+/'));
$decoded = json_decode(
  openssl_decrypt(
    substr($raw, 28),           // the ciphertext
    'aes-256-gcm',
    hash('sha256', $key, true), // the game key -> 32 bytes
    OPENSSL_RAW_DATA,
    substr($raw, 0, 12),        // IV
    substr($raw, 12, 16)        // authentication tag
  ),
  true
);
// $decoded => array('u' => 162862, 'tid' => 42719)

Python

*(Requires the cryptography library: 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 comes unpadded
    raw = base64.urlsafe_b64decode(body)
    iv, tag, encrypted = raw[:12], raw[12:28], raw[28:]
    aes_key = hashlib.sha256(key.encode()).digest()
    # cryptography expects the tag at the end of the ciphertext
    return json.loads(AESGCM(aes_key).decrypt(iv, encrypted + tag, None))
 
# Usage
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) # the game key -> 32 bytes
  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
 
# Usage
data = 'v2.vyJ42f20mXmdKS5bAr6GDoqqUP2pw4f4vbM7mX1t3SvY1LMZ_Jr80XjEaXkB6iB2l3eN3g'
key = 'XgHxzCvUSXwPfR7wBvdY4xHC6tSFLewx'
puts qeng_data_decrypt(data, key) # => {:u=>162862, :tid=>42719}