Skip to content

Automatic login through banner

To automatically log in to a website that has Hive Web Login applied by clicking a promotion banner, the game server must decrypt the banner parameters passed to the website URL.

This guide explains how to decrypt the promotion banner parameter (hivepromotion_p) value.

What is banner parameter decryption

The banner parameter (hivepromotion_p) is a value encrypted by the automatic login key set in the console. When a user clicks a promotion banner in-game to navigate to a website, it is included in the website URL and transmitted via GET method (e.g., https://your-website-url.com?hivepromotion_p={encrypted account information}).

To handle automatic login on the target website, you must decrypt the promotion banner parameter hivepromotion_p key value.

Prerequisites

Before performing parameter decryption, a URL containing the hivepromotion_p key value must be provided. For details on registering website domains for automatic login, refer to Console Guide > Promotion > Automatic login key management.

Decryption process

The process for decrypting the promotion banner parameter hivepromotion_p is as follows:

  1. Safe Base64 decoding
    Decode the received string using Safe Base64 method.

  2. AES-256-CBC decryption
    Decrypt the decoded data using AES-256-CBC algorithm.

  3. Zero padding removal Remove zero padding from the decrypted result.

  4. Gzip decompression
    Decompress the padding-removed data in Gzip format.

  5. JSON conversion
    Finally convert the decompressed string to JSON format.

Decryption example code

The prerequisites for the hivepromotion_p parameter input value when running the decryption code are as follows:

  • Delivered encoded in URL-safe Base64
  • Ciphertext is in IV || CIPHERTEXT format (IV is prepended)
  • Key is a 64-character hex string (32 bytes)
<?php

function decodeSecure($encoded, $opensslKey = "")
{
    $key = hex2bin($opensslKey);
    $decoded = safeBase64Decode($encoded);
    $ivLength = openssl_cipher_iv_length("AES-256-CBC");
    $iv = substr($decoded, 0, $ivLength);
    $encrypted = substr($decoded, $ivLength);
    $decrypted = openssl_decrypt($encrypted, "AES-256-CBC", $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
    return json_decode(gzuncompress(zeroUnpadding($decrypted)), true);
}

function safeBase64Decode(string $encoded): string
{
    $base64 = str_replace(['-', '_'], ['+', '/'], $encoded);
    $remainder = strlen($base64) % 4;
    if ($remainder) {
        $base64 .= str_repeat('=', 4 - $remainder);
    }
    return base64_decode($base64);
}

function zeroUnpadding(string $paddedData): string
{
    $padChar = chr(0);
    $padLength = 0;
    $dataLength = strlen($paddedData);
    for ($i = $dataLength - 1; $i >= 0; $i--) {
        if ($paddedData[$i] === $padChar) {
            $padLength++;
        } else {
            break;
        }
    }
    return substr($paddedData, 0, $dataLength - $padLength);
}

// TEST
$sampleEncryptedData = "pn126XOrtRWEt8maRZtapHzAIHNWSdD45abmOkHQ4-wx4PqPRYjYNnhzHe_Mv5gqpXeNcrFgkvihRGo6fSN2ZSWyVGrocK2LxfYHtPJ8XRU5SZ_LDG0Mvquebusurpix0_iiOHn5bmMaxlSDeEVHTM5CoRQpPMDY8j9D44QJL9tw5R_2h-utzs244r0OcAJRkFyHggZDRnhC5rqUQgyRu1mEYVhXvmiX0wIjEpnPapkbmngEm2f-IPWIsdhunBXoCyf1OcpVutpGZ4ARZRbuhQ";
$sampleEncryptionKey = "5725f257285ac6a56e6ec94b0cac84d565e8d7dc8ea4828446b04e8d2d3f0e2d";

print_r(decodeSecure($sampleEncryptedData, $sampleEncryptionKey));
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.zip.Inflater;

public class Main {

    public static void main(String[] args) throws Exception {
        String sampleEncryptedData = "pn126XOrtRWEt8maRZtapHzAIHNWSdD45abmOkHQ4-wx4PqPRYjYNnhzHe_Mv5gqpXeNcrFgkvihRGo6fSN2ZSWyVGrocK2LxfYHtPJ8XRU5SZ_LDG0Mvquebusurpix0_iiOHn5bmMaxlSDeEVHTM5CoRQpPMDY8j9D44QJL9tw5R_2h-utzs244r0OcAJRkFyHggZDRnhC5rqUQgyRu1mEYVhXvmiX0wIjEpnPapkbmngEm2f-IPWIsdhunBXoCyf1OcpVutpGZ4ARZRbuhQ";
        String sampleEncryptionKey = "5725f257285ac6a56e6ec94b0cac84d565e8d7dc8ea4828446b04e8d2d3f0e2d";
        String json = decodeSecure(sampleEncryptedData, sampleEncryptionKey);
        System.out.println(json);
    }

    public static String decodeSecure(String encoded, String hexKey) throws Exception {
        byte[] key = hexToBytes(hexKey);
        byte[] decoded = safeBase64Decode(encoded);
        int ivLength = 16; // AES block size
        byte[] iv = Arrays.copyOfRange(decoded, 0, ivLength);
        byte[] encrypted = Arrays.copyOfRange(decoded, ivLength, decoded.length);

        byte[] decryptedNoPad = aes256CbcNoPadding(encrypted, key, iv);
        byte[] unpadded = zeroUnpad(decryptedNoPad);
        byte[] inflated = zlibInflate(unpadded);
        return new String(inflated, StandardCharsets.UTF_8);
    }

    private static byte[] hexToBytes(String hex) {
        int len = hex.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
                    + Character.digit(hex.charAt(i + 1), 16));
        }
        return data;
    }

    private static byte[] safeBase64Decode(String input) {
        String base64 = input.replace('-', '+').replace('_', '/');
        int mod = base64.length() % 4;
        if (mod != 0) base64 += "====".substring(mod);
        return Base64.getDecoder().decode(base64);
    }

    private static byte[] aes256CbcNoPadding(byte[] ciphertext, byte[] key, byte[] iv) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        return cipher.doFinal(ciphertext);
    }

    private static byte[] zeroUnpad(byte[] data) {
        int i = data.length - 1;
        while (i >= 0 && data[i] == 0) i--;
        return Arrays.copyOfRange(data, 0, i + 1);
    }

    // Uses zlib (RFC1950) format. Set nowrap=false in Inflater.
    private static byte[] zlibInflate(byte[] data) throws Exception {
        Inflater inflater = new Inflater(false);
        inflater.setInput(data);
        byte[] buffer = new byte[4096];
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            while (!inflater.finished()) {
                int count = inflater.inflate(buffer);
                if (count == 0 && inflater.needsInput()) break;
                if (count > 0) baos.write(buffer, 0, count);
            }
        } finally {
            inflater.end();
        }
        return baos.toByteArray();
    }
}
const crypto = require('crypto');
const zlib = require('zlib');

function safeBase64Decode(input) {
    let base64 = input.replace(/-/g, '+').replace(/_/g, '/');
    const mod = base64.length % 4;
    if (mod !== 0) base64 += '='.repeat(4 - mod);
    return Buffer.from(base64, 'base64');
}

function zeroUnpad(buf) {
    let end = buf.length;
    while (end > 0 && buf[end - 1] === 0x00) end--;
    return buf.subarray(0, end);
}

function decodeSecure(encoded, hexKey) {
    const key = Buffer.from(hexKey, 'hex'); // 32 bytes
    const decoded = safeBase64Decode(encoded);
    const ivLength = 16; // AES block size
    const iv = decoded.subarray(0, ivLength);
    const encrypted = decoded.subarray(ivLength);

    const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
    decipher.setAutoPadding(false); // we will handle zero padding ourselves
    const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
    const unpadded = zeroUnpad(decrypted);

    // zlib inflate in Node.js
    const inflated = zlib.inflateSync(unpadded);
    return JSON.parse(inflated.toString('utf8'));
}

// TEST
const sampleEncryptedData = 'pn126XOrtRWEt8maRZtapHzAIHNWSdD45abmOkHQ4-wx4PqPRYjYNnhzHe_Mv5gqpXeNcrFgkvihRGo6fSN2ZSWyVGrocK2LxfYHtPJ8XRU5SZ_LDG0Mvquebusurpix0_iiOHn5bmMaxlSDeEVHTM5CoRQpPMDY8j9D44QJL9tw5R_2h-utzs244r0OcAJRkFyHggZDRnhC5rqUQgyRu1mEYVhXvmiX0wIjEpnPapkbmngEm2f-IPWIsdhunBXoCyf1OcpVutpGZ4ARZRbuhQ';
const sampleEncryptionKey = '5725f257285ac6a56e6ec94b0cac84d565e8d7dc8ea4828446b04e8d2d3f0e2d';

try {
    const json = decodeSecure(sampleEncryptedData, sampleEncryptionKey);
    console.log(json);
} catch (e) {
    console.error('Decrypt error:', e);
}

Decryption result example and field description

An example of the decryption result JSON object is as follows.

{
  "player_id": "20000033086",
  "player_token": "bbaeb710f7e5f54645469f44cd651b",
  "appid": "com.com2us.hivesdk.normal.freefull.apple.global.ios.universal",
  "did": "104079054",
  "server_id": "server_002",
  "game_language": "ko",
  "device_type": null,
  "is_webview": 0,
  "os": "I"
}

Details of each field that constitutes the decryption result JSON object are as follows.

Field name Description Type Required
player_id HIVE Player ID (identifier for the user who clicked the banner) String Y
player_token Security verification token (used for request validity confirmation) String Y
appid Package/bundle ID of the game app String Y
did Device ID String Y
server_id Game server identifier for connection (or recommendation) String Y
game_language Language code used in the game (e.g., ko, en) String Y
device_type Device type/model information. May not be provided and can be null String or null N
is_webview WebView call status (0: No, 1: WebView) Integer N
os Operating system code (I: iOS, A: Android, etc.) String N

Login processing using decryption results

The decryption result data corresponding to the response value can be used to process login (or automatic login) more securely.

The process for using hivepromotion_p decryption result data for login (or automatic login) is as follows.

1. Basic self-validation (optional)

  • Check existence of required fields: player_id, player_token, appid, did, server_id
  • Cross-validate app identifier: Confirm that the decrypted appid matches the currently serviced app (or allowed app list)
  • Server/environment validation (optional): Check if server_id belongs to the allowed server list

2. Token validation (required)

  • Call the HIVE authentication API with the player_token from the decrypted value to verify token validity.
  • Reference document: Auth v4 VerifyToken
  • Recommended items to check during validation:
    • Token validity (expiration/tampering status)
    • Whether the identification information player_id in the response matches the decrypted player_id
    • If necessary, consistency of additional information such as channel/platform

3. Login/session processing

  • On validation success
    • Perform login processing independently.
  • On validation failure
    • Perform login failure processing independently.

4. Example flow (pseudo code)

try {
    data = decrypt(hivepromotion_p)

    // Self validation
    assert has(data.player_id, data.player_token, data.appid)
    assert isAllowedApp(data.appid)

    // Token validation (required)
    verify = callAuthV4VerifyToken(player_token = data.player_token)
    assert verify.isValid
    assert verify.player_id == data.player_id

    // Session issuance
    session = issueSession(verify.player_id)
    return success(session)
} catch (e) {
    return error(401 or 403)
}
Warning
  • Since player_token corresponds to sensitive information, be careful not to leave it in logs.
  • Set retry policies and timeouts to prepare for network errors.
  • The VerifyToken response specification must be designed in compliance with the related reference document.
  • VerifyToken 응답 스펙은 반드시 관련 참고 문서를 준수하여 설계하세요.