ข้ามไปที่เนื้อหา

การเข้าสู่ระบบอัตโนมัติผ่านแบนเนอร์

หากต้องการเข้าสู่ระบบเว็บไซต์ที่ใช้ Hive Web Login อัตโนมัติโดยคลิกแบนเนอร์โปรโมชั่น เกมเซิร์ฟเวอร์จะต้องถอดรหัสพารามิเตอร์แบนเนอร์ที่ส่งผ่านไปยัง URL ของเว็บไซต์

คู่มือนี้อธิบายวิธีการถอดรหัสค่าพารามิเตอร์แบนเนอร์โปรโมชั่น (hivepromotion_p)

การถอดรหัสพารามิเตอร์แบนเนอร์คืออะไร

พารามิเตอร์แบนเนอร์ (hivepromotion_p) เป็นค่าที่เข้ารหัสโดย คีย์เข้าสู่ระบบอัตโนมัติ ที่ตั้งค่าในคอนโซล เมื่อผู้ใช้คลิกแบนเนอร์โปรโมชั่นในเกมเพื่อนำทางไปยังเว็บไซต์ จะรวมอยู่ใน URL ของเว็บไซต์และส่งผ่าน GET method (เช่น https://your-website-url.com?hivepromotion_p={ข้อมูลบัญชีที่เข้ารหัส})

เพื่อจัดการการเข้าสู่ระบบอัตโนมัติในเว็บไซต์เป้าหมาย คุณต้องถอดรหัสค่าคีย์พารามิเตอร์แบนเนอร์โปรโมชั่น hivepromotion_p

ข้อกำหนดเบื้องต้น

ก่อนทำการถอดรหัสพารามิเตอร์ ต้องมี URL ที่มีค่าคีย์ hivepromotion_p สำหรับรายละเอียดการลงทะเบียนโดเมนเว็บไซต์สำหรับการเข้าสู่ระบบอัตโนมัติ โปดูที่ คู่มือคอนโซล > โปรโมชั่น > การจัดการคีย์เข้าสู่ระบบอัตโนมัติ

กระบวนการถอดรหัส

กระบวนการถอดรหัสพารามิเตอร์แบนเนอร์โปรโมชั่น hivepromotion_p มีดังนี้:

  1. การถอดรหัส Safe Base64
    ถอดรหัสสตริงที่ได้รับโดยใช้วิธี Safe Base64

  2. การถอดรหัส AES-256-CBC
    ถอดรหัสข้อมูลที่ถอดรหัสแล้วโดยใช้อัลกอริทึม AES-256-CBC

  3. การลบ Zero padding ลบ zero padding ออกจากผลลัพธ์ที่ถอดรหัสแล้ว

  4. การขยายข้อมูล Gzip
    ขยายข้อมูลที่ลบ padding แล้วในรูปแบบ Gzip

  5. การแปลง JSON
    สุดท้ายแปลงสตริงที่ขยายแล้วเป็นรูปแบบ JSON

ตัวอย่างโค้ดการถอดรหัส

ข้อกำหนดเบื้องต้นสำหรับค่าอินพุตพารามิเตอร์ hivepromotion_p เมื่อรันโค้ดการถอดรหัสมีดังนี้:

  • ส่งมาในรูปแบบ URL-safe Base64
  • Ciphertext อยู่ในรูปแบบ IV || CIPHERTEXT (IV อยู่ข้างหน้า)
  • Key เป็นสตริง hex ความยาว 64 ตัวอักษร (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);
}

ตัวอย่างผลลัพธ์การถอดรหัสและคำอธิบายฟิลด์

ตัวอย่างของ JSON object ผลลัพธ์การถอดรหัสมีดังนี้

{
  "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"
}

รายละเอียดของแต่ละฟิลด์ที่ประกอบเป็น JSON object ผลลัพธ์การถอดรหัสมีดังนี้

ชื่อฟิลด์ คำอธิบาย ประเภท จำเป็น
player_id HIVE Player ID (ตัวระบุผู้ใช้ที่คลิกแบนเนอร์) String Y
player_token โทเค็นยืนยันความปลอดภัย (ใช้สำหรับยืนยันความถูกต้องของคำขอ) String Y
appid Package/bundle ID ของแอปเกม String Y
did Device ID String Y
server_id ตัวระบุเกมเซิร์ฟเวอร์สำหรับการเชื่อมต่อ (หรือแนะนำ) String Y
game_language รหัสภาษาที่ใช้ในเกม (เช่น ko, en) String Y
device_type ข้อมูลประเภท/รุ่นอุปกรณ์ อาจไม่ได้ให้มาและสามารถเป็น null ได้ String หรือ null N
is_webview สถานะการเรียก WebView (0: ไม่ใช่, 1: WebView) Integer N
os รหัสระบบปฏิบัติการ (I: iOS, A: Android, ฯลฯ) String N

การประมวลผลการเข้าสู่ระบบโดยใช้ผลลัพธ์การถอดรหัส

ข้อมูลผลลัพธ์การถอดรหัสที่สอดคล้องกับค่าการตอบกลับสามารถใช้เพื่อประมวลผลการเข้าสู่ระบบ (หรือการเข้าสู่ระบบอัตโนมัติ) อย่างปลอดภัยมากขึ้น

กระบวนการใช้ข้อมูลผลลัพธ์การถอดรหัส hivepromotion_p สำหรับการเข้าสู่ระบบ (หรือการเข้าสู่ระบบอัตโนมัติ) มีดังนี้

1. การตรวจสอบความถูกต้องพื้นฐานด้วยตนเอง (เลือกได้)

  • ตรวจสอบการมีอยู่ของฟิลด์ที่จำเป็น: player_id, player_token, appid, did, server_id
  • ตรวจสอบตัวระบุแอปแบบไขว้: ยืนยันว่า appid ที่ถอดรหัสแล้วตรงกับแอปที่ให้บริการอยู่ปัจจุบัน (หรือรายการแอปที่อนุญาต)
  • การตรวจสอบเซิร์ฟเวอร์/สภาพแวดล้อม (เลือกได้): ตรวจสอบว่า server_id เป็นของรายการเซิร์ฟเวอร์ที่อนุญาตหรือไม่

2. การตรวจสอบโทเค็น (จำเป็น)

  • เรียก HIVE authentication API ด้วย player_token จากค่าที่ถอดรหัสแล้วเพื่อยืนยันความถูกต้องของโทเค็น
  • เอกสารอ้างอิง: Auth v4 VerifyToken
  • รายการที่แนะนำให้ตรวจสอบระหว่างการยืนยัน:
    • ความถูกต้องของโทเค็น (สถานะหมดอายุ/การดัดแปลง)
    • ข้อมูลระบุตัวตน player_id ในการตอบกลับตรงกับ player_id ที่ถอดรหัสแล้วหรือไม่
    • หากจำเป็น ความสอดคล้องของข้อมูลเพิ่มเติมเช่นช่องทาง/แพลตฟอร์ม

3. การประมวลผลการเข้าสู่ระบบ/เซสชั่น

  • เมื่อการตรวจสอบสำเร็จ
    • ทำการประมวลผลการเข้าสู่ระบบอย่างอิสระ
  • เมื่อการตรวจสอบล้มเหลว
    • ทำการประมวลผลการเข้าสู่ระบบล้มเหลวอย่างอิสระ

4. ตัวอย่างขั้นตอน (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
  • เนื่องจาก player_token เป็นข้อมูลที่มีความอ่อนไหว ให้ระมัดระวังไม่ให้ทิ้งไว้ใน logs
  • ตั้งค่านโยบายการลองใหม่และ timeouts เพื่อเตรียมพร้อมสำหรับข้อผิดพลาดเครือข่าย
  • ข้อกำหนดการตอบกลับ VerifyToken ต้องได้รับการออกแบบให้สอดคล้องกับ เอกสารอ้างอิง ที่เกี่ยวข้อง