Specification
1. Overview
To make HTTP requests to PGOS Backend, you need to put essential parameters in HTTP Header, request payload in HTTP Body and send it to the correct host.
In this document, we'll cover the basics of each component and how to put them together to make a correct HTTP request.
2. Components
2.1 Domain
2.1.1 For Title-Region-Wide
The domain is specific for your title region. Obtain the domain name on the web portal here.
2.1.2 For Title-Wide
Domain is fixed: server.pgosglobal.com
2.2 ServerKey
Server keys are configured on the title settings page of the web portal.
The
secret_id
is the first part of serverkey: LTRN.The
secret_key
is the last parts: NANI-D3TK-YQBM-MOUX.
Please note that the composition of server key: secret id
and secret key
. In the following steps, the secret id
part will be put directly in the HTTP header, and the secret key
part is used to calculate the server ticket
.
2.3 TitleID & TitleRegionID
In PGOS, "title" stands for game. Each title is isolated and does not affect each other. Normally there are multiple "regions" for each title. That's the meaning of TitleID and TitleRegionID. Put them in HTTP header to identify for the specific region of your game.
2.4 Verify Method
2.4.1 For Title-Region-Wide
ServerTicket is essentially a piece of JSON data encrypted by the AES CBC algorithm.
Let's demonstrate the process using this example:
TitleRegionID: d_5_123 ServerKey: LTRN-NANI-D3TK-YQBM-MOUX
First, you need to generate the plain text of JSON data.
{ "title_region_id": "d_5_123", "secret_id": "LTRN", "time": 1600531200 }
Then make the AES Key, which is the secret_key
but wiped all the -
delimiters.
NANID3TKYQBMMOUX
Finally, invoke the AES CBC algorithm to encrypt the JSON data above.
Two parameters are required to run the AES CBC algorithm: AES Key and IV. The IV is a constant value for all PGOS requests: $3,.'/&^rgnjkl!#
.
AESKey: NANID3TKYQBMMOUX
IV: $3,.'/&^rgnjkl!#
The base64 string of the final product of AES CBC algorithm is the ServerTicket.
2.4.2 For Title-Wide
Signature is essentially the sha256 of some fields and secret key
.
Let's use the following example to demonstrate the process:
TitleID: 5 ServerKey: LTRN-NANI-D3TK-YQBM-MOUX
First, you need to generate the plain text of JSON data.
{
"title_id":"5",
"secret_id":"LTRN",
"secret_key":"NANI-D3TK-YQBM-MOUX",
"timestamp": 1719386647 // Current timestamp in second
}
Then, connect the key and value with = and then connect them with & in ascending lexicographical order of the key.
secret_id=LTRN&secret_key=NANI-D3TK-YQBM-MOUX×tamp=1719386647&title_id=5
Finally, sha256 is calculated for the obtained string and converted to a string.
2.5 Putting it all Together
To sum up, then set the required HTTP headers, and finally put the request JSON data in the HTTP body.
2.5.1 For Title-Region-Wide
we need a POST request to the URL specific to your title region
POST https://d.server.pgos.intlgame.cn/title_svr/get_player_data HTTP/1.1
Host: d.server.pgos.intlgame.cn
Content-Type: application/json
Secretid: LTRN
Serverticket: O842PzLWMy2pXYwQwAY9ShHzPdj0kZL6TmIyDwd8wdRIxupNhS8Wx4I485lnDS+fU8KqVKX32OV7gstkQ/ZxZkcSIylWEYAlhKWVJVxhVLs=
Titleid: 5
Titleregionid: d_5_123
{
"player_id": "123456"
}
2.5.2 For Title-Wide
POST https://server.pgosglobal.com/title_svr/get_title_file_info HTTP/1.1
Host: server.pgosglobal.com
Content-Type: application/json
Secretid: LTRN
Titleid: 5
Secretid:LTRN,
Timestamp:1719386647
Signature: f9d9ad3cbd9859f7b80296eeb42019232a03c387a83769b5b9ea41ea506dafe6
{
"paths": ["/123456"]
}
3. Debug HTTP API via Postman
While you can access the HTTP API by writing code, you may need to debug the HTTP API first to ensure you understand the related protocols and parameters before starting to write the code.
You can follow these steps to use Postman to access the HTTP API.
3.1 For Title-Region-Wide
Four components are needed to invoke HTTP API via Postman.
- Determine the HTTP Method and URL, where the URL includes the service path to be accessed.
- Fill in the HTTP Header, using the variable
{{serverTicket}}
for the ServerTicket field. - Fill in the HTTP Body, adhering to the JSON format specified in the interface protocol.
- Fill in the Pre-request Script by copying the following code and change the required fields for your project.
// Change these fields for your project
const secretID = 'AAAA';
const secretKey = 'BBBB-CCCC-DDDD-EEEE';
const titleRegionID = 'YourTitleRegionID';
const titleID = 'YourTitleID';
const ServerTokenIv = CryptoJS.enc.Utf8.parse("$3,.'/&^rgnjkl!#");
class AesCbc {
constructor(key, iv) {
this.key = key;
this.iv = iv;
}
encrypt(text) {
const plainText = CryptoJS.enc.Utf8.parse(text);
const encrypted = CryptoJS.AES.encrypt(plainText, this.key, { iv: this.iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
return encrypted.toString();
}
}
function encryptServerTicket(key, ticketJsonStr) {
const ac = new AesCbc(CryptoJS.enc.Utf8.parse(key), ServerTokenIv);
return ac.encrypt(ticketJsonStr);
}
class SecretTicket {
constructor(titleRegionID, secretID, time) {
this.title_region_id = titleRegionID;
this.secret_id = secretID;
this.time = time;
}
}
function genServerTicket(secretID, secretKey, titleRegionID) {
const st = new SecretTicket(titleRegionID, secretID, Math.floor(Date.now() / 1000));
const ticketJson = JSON.stringify(st);
const key = secretKey.split('-').join('');
const ticket = encryptServerTicket(key, ticketJson);
return ticket;
}
const ticket = genServerTicket(secretID, secretKey, titleRegionID);
pm.environment.set('serverTicket', ticket);
3.2 For Title-Wide
Four components are needed to invoke HTTP API via Postman.
- Determine the HTTP Method and URL, where the URL includes the service path to be accessed.
- Fill in the HTTP Header, using the variable
{{signature}}
for the Signature field. - Fill in the HTTP Body, adhering to the JSON format specified in the interface protocol.
- Fill in the Pre-request Script by copying the following code and change the required fields for your project.
function mapToStr(param) {
const keys = Object.keys(param).sort();
const keyValuePairs = keys.map(key => `${key}=${param[key]}`);
return keyValuePairs.join('&');
}
function reqParamToStr(data) {
const param = {};
for (const k in data) {
if (typeof data[k] === 'object' && data[k] !== null && !Array.isArray(data[k])) {
param[k] = mapToStr(data[k]);
} else {
param[k] = data[k];
}
}
return mapToStr(param);
}
function sha256Hex(str) {
const hash = CryptoJS.SHA256(str);
const hexHash = hash.toString(CryptoJS.enc.Hex);
return hexHash;
}
function makeSig(param) {
const strToSig = reqParamToStr(param);
const sigStr = sha256Hex(strToSig);
return sigStr;
}
const result = makeSig({
title_id: pm.request.headers.get("TitleId"),
secret_id:pm.request.headers.get("SecretID"),
secret_key:"BBBB-CCCC-DDDD-EEEE",
timestamp:pm.request.headers.get("Timestamp"),
});
pm.environment.set("signature", result);
4. Access HTTP API via Code
The following samples demonstrate how to access HTTP API in different languages.
4.1 For Title-Region-Wide
- Golang
- Nodejs
- Python
- C++
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"time"
)
const ServerTokenIv string = "$3,.'/&^rgnjkl!#"
type AesCbc struct {
key []byte
iv []byte
}
func NewAesCbc(key, iv []byte) *AesCbc {
return &AesCbc{key: key, iv: iv}
}
func (ac *AesCbc) Encrypt(text string) (string, error) {
plainText := []byte(text)
paddedPlainText := Pad(plainText)
block, err := aes.NewCipher(ac.key)
if err != nil {
return "", err
}
ciphertext := make([]byte, len(paddedPlainText))
mode := cipher.NewCBCEncrypter(block, ac.iv)
mode.CryptBlocks(ciphertext, paddedPlainText)
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
func (ac *AesCbc) Decrypt(text string) (string, error) {
ciphertext, err := base64.StdEncoding.DecodeString(text)
if err != nil {
return "", err
}
block, err := aes.NewCipher(ac.key)
if err != nil {
return "", err
}
plaintext := make([]byte, len(ciphertext))
mode := cipher.NewCBCDecrypter(block, ac.iv)
mode.CryptBlocks(plaintext, ciphertext)
unpaddedPlaintext := UnPad(plaintext)
return string(unpaddedPlaintext), nil
}
func Pad(text []byte) []byte {
padding := aes.BlockSize - (len(text) % aes.BlockSize)
padText := bytes.Repeat([]byte{byte(padding)}, padding)
return append(text, padText...)
}
func UnPad(text []byte) []byte {
length := len(text)
unPadding := int(text[length-1])
return text[:(length - unPadding)]
}
func EncryptServerTicket(key, ticketJsonStr string) (string, error) {
ac := NewAesCbc([]byte(key), []byte(ServerTokenIv))
return ac.Encrypt(ticketJsonStr)
}
func DecryptServerTicket(key, ticketJsonStr string) (string, error) {
ac := NewAesCbc([]byte(key), []byte(ServerTokenIv))
return ac.Decrypt(ticketJsonStr)
}
type SecretTicket struct {
TitleRegionID string `json:"title_region_id"`
SecretID string `json:"secret_id"`
Time int64 `json:"time"`
}
func GenServerTicket(secretID, secretKey, titleRegionID string) (string, error) {
st := SecretTicket{
TitleRegionID: titleRegionID,
SecretID: secretID,
Time: time.Now().Unix(),
}
ticketJson, err := json.Marshal(st)
if err != nil {
return "", err
}
key := strings.Join(strings.Split(secretKey, "-"), "")
ticket, err := EncryptServerTicket(key, string(ticketJson))
return ticket, err
}
func SplitServerKey(serverKey string) (string, string) {
s := strings.Split(serverKey, "-")
if len(s) > 1 {
secretID := s[0]
secretKey := strings.Join(s[1:], "-")
return secretID, secretKey
}
return "", ""
}
func GetServerUrl(titleRegionID string) string {
location := "euff"
domainSuffix := "com"
arr := strings.Split(titleRegionID, "_")
if len(arr) > 0 {
location = arr[0]
}
if location == "t" || location == "d" || strings.HasPrefix(location, "cn") {
domainSuffix = "cn"
}
serverUrl := fmt.Sprintf("https://%s.server.pgos.intlgame.%s", location, domainSuffix)
return serverUrl
}
func InvokeBackendApi(titleID, titleRegionID, serverKey, apiUri string, body []byte) (*http.Header, []byte, error) {
secretID, secretKey := SplitServerKey(serverKey)
ticket, _ := GenServerTicket(secretID, secretKey, titleRegionID)
url := GetServerUrl(titleRegionID) + apiUri
client := &http.Client{
Timeout: time.Duration(3) * time.Second,
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
if err != nil {
return nil, nil, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("TitleId", titleID)
req.Header.Set("TitleRegionId", titleRegionID)
req.Header.Set("SecretId", secretID)
req.Header.Set("ServerTicket", ticket)
resp, err := client.Do(req)
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
rspBody, err := ioutil.ReadAll(resp.Body)
return &resp.Header, rspBody, nil
}
type PlayerInfoRequest struct {
PlayerId string `json:"player_id"`
}
func main() {
titleID := "your_title_id"
titleRegionID := "your_title_region_id"
serverKey := "your_server_key"
// e.g. invoke GetPlayerInfo
apiUri := "/player/get_player_info"
req := &PlayerInfoRequest{
PlayerId: "123",
}
body, _ := json.Marshal(req)
rspHeader, rspBody, err := InvokeBackendApi(titleID, titleRegionID, serverKey, apiUri, body)
if err != nil {
fmt.Printf("err: %s", err)
} else {
fmt.Printf("rspHeader:%v, rspBody: %s\n", rspHeader, string(rspBody))
}
}
const crypto = require('crypto');
const axios = require('axios');
const { Buffer } = require('buffer');
const ServerTokenIv = Buffer.from("$3,.'/&^rgnjkl!#", 'utf-8');
class AesCbc {
constructor(key, iv) {
this.key = key;
this.iv = iv;
}
encrypt(text) {
const cipher = crypto.createCipheriv('aes-128-cbc', this.key, this.iv);
const paddedText = this.pad(Buffer.from(text, 'utf-8'));
const encrypted = Buffer.concat([cipher.update(paddedText), cipher.final()]);
return encrypted.toString('base64');
}
decrypt(text) {
const decipher = crypto.createDecipheriv('aes-128-cbc', this.key, this.iv);
const decodedText = Buffer.from(text, 'base64');
const decrypted = Buffer.concat([decipher.update(decodedText), decipher.final()]);
return this.unPad(decrypted).toString('utf-8');
}
pad(text) {
const blockSize = 16;
const padding = blockSize - (text.length % blockSize);
const padText = Buffer.alloc(padding, padding);
return Buffer.concat([text, padText]);
}
unPad(text) {
const unPadding = text[text.length - 1];
return text.slice(0, text.length - unPadding);
}
}
function encryptServerTicket(key, ticketJsonStr) {
const ac = new AesCbc(Buffer.from(key, 'utf-8'), ServerTokenIv);
return ac.encrypt(ticketJsonStr);
}
function decryptServerTicket(key, ticketJsonStr) {
const ac = new AesCbc(Buffer.from(key, 'utf-8'), ServerTokenIv);
return ac.decrypt(ticketJsonStr);
}
class SecretTicket {
constructor(titleRegionID, secretID, time) {
this.title_region_id = titleRegionID;
this.secret_id = secretID;
this.time = time;
}
}
function genServerTicket(secretID, secretKey, titleRegionID) {
const st = new SecretTicket(titleRegionID, secretID, Math.floor(Date.now() / 1000));
const ticketJson = JSON.stringify(st);
const key = secretKey.split('-').join('');
const ticket = encryptServerTicket(key, ticketJson);
return ticket;
}
function splitServerKey(serverKey) {
var s = serverKey.split('-');
if (s.length > 1) {
secretID = s[0];
secretKey = s.slice(1).join('-');
return { secretID: secretID, secretKey: secretKey };
}
return null;
}
function getServerUrl(titleRegionID) {
let location = 'euff';
let domainSuffix = 'com';
let arr = titleRegionID.split('_');
if (arr.length > 0) {
location = arr[0];
}
if (location === 't' || location === 'd' || location.indexOf('cn') === 0) {
domainSuffix = 'cn';
}
let serverUrl = `https://${location}.server.pgos.intlgame.${domainSuffix}`;
return serverUrl;
}
async function invokeBackendApi(titleID, titleRegionID, serverKey, apiUri, bodyStr) {
var k = splitServerKey(serverKey);
if (k == null) {
return null;
}
var ticket = genServerTicket(k.secretID, k.secretKey, titleRegionID);
let url = getServerUrl(titleRegionID) + apiUri;
let headers = {
'Content-Type': 'application/json',
TitleId: titleID,
TitleRegionId: titleRegionID,
SecretId: k.secretID,
ServerTicket: ticket
};
try {
let response = await axios.post(url, bodyStr, { headers, timeout: 3000 });
return { headers: response.headers, data: response.data };
} catch (err) {
return { err: err };
}
}
// e.g. invoke GetPlayerInfo
(async () => {
await invokeBackendApi('your_title_id', 'your_title_region_id', 'your_server_key', '/player/get_player_info', JSON.stringify({ player_id: '123' }));
})();
# requirements:pycryptodome, requests
from Crypto.Cipher import AES
import base64
import requests
import json
ServerTokenIv = b"$3,.'/&^rgnjkl!#"
class AesCbc:
def __init__(self, key, iv):
self.key = key
self.iv = iv
def encrypt(self, text):
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
padded_text = self.pad(text.encode('utf-8'))
encrypted = cipher.encrypt(padded_text)
return base64.b64encode(encrypted).decode('utf-8')
def decrypt(self, text):
decipher = AES.new(self.key, AES.MODE_CBC, self.iv)
decoded_text = base64.b64decode(text)
decrypted = decipher.decrypt(decoded_text)
return self.unpad(decrypted).decode('utf-8')
def pad(self, text):
block_size = 16
padding = block_size - (len(text) % block_size)
pad_text = bytes([padding]) * padding
return text + pad_text
def unpad(self, text):
unpadding = text[-1]
return text[:len(text) - unpadding]
def encrypt_server_ticket(key, ticket_json_str):
ac = AesCbc(key.encode('utf-8'), ServerTokenIv)
return ac.encrypt(ticket_json_str)
def decrypt_server_ticket(key, ticket_json_str):
ac = AesCbc(key.encode('utf-8'), ServerTokenIv)
return ac.decrypt(ticket_json_str)
class SecretTicket:
def __init__(self, title_region_id, secret_id, t):
self.title_region_id = title_region_id
self.secret_id = secret_id
self.time = t
def gen_server_ticket(secret_id, secret_key, title_region_id):
import time
st = SecretTicket(title_region_id, secret_id, int(time.time()))
ticket_json = json.dumps(st.__dict__)
key = secret_key.replace("-", "")
ticket = encrypt_server_ticket(key, ticket_json)
return ticket
def split_server_key(server_key):
s = server_key.split("-")
if len(s) >= 2:
secret_id = s[0]
secret_key = '-'.join(s[1:])
return secret_id, secret_key
else:
return None, None
def get_server_url(title_region_id):
parts = title_region_id.split('_')
location = parts[0] if len(parts) > 0 else 'euff'
domain_suffix = 'com'
if location == 't' or location == 'd' or location.startswith('cn'):
domain_suffix = 'cn'
server_url = f'https://{location}.server.pgos.intlgame.{domain_suffix}'
return server_url
def invoke_backend_api(title_id, title_region_id, server_key, api_uri, body_str):
secret_id, secret_key = split_server_key(server_key)
if secret_id is None or secret_key is None:
return None
ticket = gen_server_ticket(secret_id, secret_key, title_region_id)
url = get_server_url(title_region_id) + api_uri
headers = {
"Content-Type": "application/json",
"TitleId": title_id,
"TitleRegionId": title_region_id,
"SecretId": secret_id,
"ServerTicket": ticket
}
response = requests.post(url, body_str, headers=headers, timeout=3)
return response.headers, response.text
if __name__ == "__main__":
# e.g. invoke GetPlayerInfo
invoke_backend_api("your_title_id", "your_title_region_id", "your_server_key", "/player/get_player_info", "{\"player_id\":\"123\"}")
#include <stdint.h>
#include <string>
#include <iostream>
#include <algorithm>
#include <ctime>
#include <cstring>
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Code Block Begin: AES encryption algorithm extracted from mbedtls: https://github.com/Mbed-TLS/mbedtls
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*
* Forward S-box & tables
*/
static unsigned char FSb[256];
static uint32_t FT0[256];
static uint32_t FT1[256];
static uint32_t FT2[256];
static uint32_t FT3[256];
/*
* Reverse S-box & tables
*/
static unsigned char RSb[256];
static uint32_t RT0[256];
static uint32_t RT1[256];
static uint32_t RT2[256];
static uint32_t RT3[256];
/*
* Round constants
*/
static uint32_t RCON[10];
static int aes_init_done = 0;
/* Error codes in range 0x0020-0x0022 */
#define MBEDTLS_ERR_AES_INVALID_KEY_LENGTH -0x0020 /**< Invalid key length. */
#define MBEDTLS_ERR_AES_INVALID_INPUT_LENGTH -0x0022 /**< Invalid data input length. */
/* padlock.c and aesni.c rely on these values! */
#define MBEDTLS_AES_ENCRYPT 1 /**< AES encryption. */
#define MBEDTLS_AES_DECRYPT 0 /**< AES decryption. */
/*
* Tables generation code
*/
#define ROTL8(x) ( ( (x) << 8 ) & 0xFFFFFFFF ) | ( (x) >> 24 )
#define XTIME(x) ( ( (x) << 1 ) ^ ( ( (x) & 0x80 ) ? 0x1B : 0x00 ) )
#define MUL(x,y) ( ( (x) && (y) ) ? pow[(log[(x)]+log[(y)]) % 255] : 0 )
#define AES_RT0(idx) RT0[idx]
#define AES_RT1(idx) RT1[idx]
#define AES_RT2(idx) RT2[idx]
#define AES_RT3(idx) RT3[idx]
#define AES_FT0(idx) FT0[idx]
#define AES_FT1(idx) FT1[idx]
#define AES_FT2(idx) FT2[idx]
#define AES_FT3(idx) FT3[idx]
#define GET_UINT32_LE(n, b, i) \
{ \
(n) = ((uint32_t)(b)[(i)]) | ((uint32_t)(b)[(i) + 1] << 8) | ((uint32_t)(b)[(i) + 2] << 16) | \
((uint32_t)(b)[(i) + 3] << 24); \
}
#define AES_FROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3) \
do { \
(X0) = *RK++ ^ AES_FT0(((Y0)) & 0xFF) ^ AES_FT1(((Y1) >> 8) & 0xFF) ^ AES_FT2(((Y2) >> 16) & 0xFF) ^ \
AES_FT3(((Y3) >> 24) & 0xFF); \
\
(X1) = *RK++ ^ AES_FT0(((Y1)) & 0xFF) ^ AES_FT1(((Y2) >> 8) & 0xFF) ^ AES_FT2(((Y3) >> 16) & 0xFF) ^ \
AES_FT3(((Y0) >> 24) & 0xFF); \
\
(X2) = *RK++ ^ AES_FT0(((Y2)) & 0xFF) ^ AES_FT1(((Y3) >> 8) & 0xFF) ^ AES_FT2(((Y0) >> 16) & 0xFF) ^ \
AES_FT3(((Y1) >> 24) & 0xFF); \
\
(X3) = *RK++ ^ AES_FT0(((Y3)) & 0xFF) ^ AES_FT1(((Y0) >> 8) & 0xFF) ^ AES_FT2(((Y1) >> 16) & 0xFF) ^ \
AES_FT3(((Y2) >> 24) & 0xFF); \
} while (0)
#define AES_RROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3) \
do { \
(X0) = *RK++ ^ AES_RT0(((Y0)) & 0xFF) ^ AES_RT1(((Y3) >> 8) & 0xFF) ^ AES_RT2(((Y2) >> 16) & 0xFF) ^ \
AES_RT3(((Y1) >> 24) & 0xFF); \
\
(X1) = *RK++ ^ AES_RT0(((Y1)) & 0xFF) ^ AES_RT1(((Y0) >> 8) & 0xFF) ^ AES_RT2(((Y3) >> 16) & 0xFF) ^ \
AES_RT3(((Y2) >> 24) & 0xFF); \
\
(X2) = *RK++ ^ AES_RT0(((Y2)) & 0xFF) ^ AES_RT1(((Y1) >> 8) & 0xFF) ^ AES_RT2(((Y0) >> 16) & 0xFF) ^ \
AES_RT3(((Y3) >> 24) & 0xFF); \
\
(X3) = *RK++ ^ AES_RT0(((Y3)) & 0xFF) ^ AES_RT1(((Y2) >> 8) & 0xFF) ^ AES_RT2(((Y1) >> 16) & 0xFF) ^ \
AES_RT3(((Y0) >> 24) & 0xFF); \
} while (0)
#define PUT_UINT32_LE(n, b, i) \
{ \
(b)[(i)] = (unsigned char)(((n)) & 0xFF); \
(b)[(i) + 1] = (unsigned char)(((n) >> 8) & 0xFF); \
(b)[(i) + 2] = (unsigned char)(((n) >> 16) & 0xFF); \
(b)[(i) + 3] = (unsigned char)(((n) >> 24) & 0xFF); \
}
typedef struct mbedtls_aes_context
{
int nr; /*!< The number of rounds. */
uint32_t *rk; /*!< AES round keys. */
uint32_t buf[68]; /*!< Unaligned data buffer. */
} mbedtls_aes_context;
void mbedtls_platform_zeroize(void* buf, size_t len)
{
if (len > 0) memset(buf, 0, len);
}
static void aes_gen_tables(void)
{
int i, x, y, z;
int pow[256];
int log[256];
/*
* compute pow and log tables over GF(2^8)
*/
for (i = 0, x = 1; i < 256; i++)
{
pow[i] = x;
log[x] = i;
x = (x ^ XTIME(x)) & 0xFF;
}
/*
* calculate the round constants
*/
for (i = 0, x = 1; i < 10; i++)
{
RCON[i] = (uint32_t)x;
x = XTIME(x) & 0xFF;
}
/*
* generate the forward and reverse S-boxes
*/
FSb[0x00] = 0x63;
RSb[0x63] = 0x00;
for (i = 1; i < 256; i++)
{
x = pow[255 - log[i]];
y = x; y = ((y << 1) | (y >> 7)) & 0xFF;
x ^= y; y = ((y << 1) | (y >> 7)) & 0xFF;
x ^= y; y = ((y << 1) | (y >> 7)) & 0xFF;
x ^= y; y = ((y << 1) | (y >> 7)) & 0xFF;
x ^= y ^ 0x63;
FSb[i] = (unsigned char)x;
RSb[x] = (unsigned char)i;
}
/*
* generate the forward and reverse tables
*/
for (i = 0; i < 256; i++)
{
x = FSb[i];
y = XTIME(x) & 0xFF;
z = (y ^ x) & 0xFF;
FT0[i] = ((uint32_t)y) ^
((uint32_t)x << 8) ^
((uint32_t)x << 16) ^
((uint32_t)z << 24);
#if !defined(MBEDTLS_AES_FEWER_TABLES)
FT1[i] = ROTL8(FT0[i]);
FT2[i] = ROTL8(FT1[i]);
FT3[i] = ROTL8(FT2[i]);
#endif /* !MBEDTLS_AES_FEWER_TABLES */
x = RSb[i];
RT0[i] = ((uint32_t)MUL(0x0E, x)) ^
((uint32_t)MUL(0x09, x) << 8) ^
((uint32_t)MUL(0x0D, x) << 16) ^
((uint32_t)MUL(0x0B, x) << 24);
#if !defined(MBEDTLS_AES_FEWER_TABLES)
RT1[i] = ROTL8(RT0[i]);
RT2[i] = ROTL8(RT1[i]);
RT3[i] = ROTL8(RT2[i]);
#endif /* !MBEDTLS_AES_FEWER_TABLES */
}
}
void mbedtls_aes_init(mbedtls_aes_context* ctx) {
memset(ctx, 0, sizeof(mbedtls_aes_context));
}
int mbedtls_aes_setkey_enc(mbedtls_aes_context* ctx, const unsigned char* key, unsigned int keybits) {
unsigned int i;
uint32_t* RK;
switch (keybits) {
case 128:
ctx->nr = 10;
break;
case 192:
ctx->nr = 12;
break;
case 256:
ctx->nr = 14;
break;
default:
return (MBEDTLS_ERR_AES_INVALID_KEY_LENGTH);
}
#if !defined(MBEDTLS_AES_ROM_TABLES)
if (aes_init_done == 0) {
aes_gen_tables();
aes_init_done = 1;
}
#endif
#if defined(MBEDTLS_PADLOCK_C) && defined(MBEDTLS_PADLOCK_ALIGN16)
if (aes_padlock_ace == -1) aes_padlock_ace = mbedtls_padlock_has_support(MBEDTLS_PADLOCK_ACE);
if (aes_padlock_ace)
ctx->rk = RK = MBEDTLS_PADLOCK_ALIGN16(ctx->buf);
else
#endif
ctx->rk = RK = ctx->buf;
#if defined(MBEDTLS_AESNI_C) && defined(MBEDTLS_HAVE_X86_64)
if (mbedtls_aesni_has_support(MBEDTLS_AESNI_AES))
return (mbedtls_aesni_setkey_enc((unsigned char*)ctx->rk, key, keybits));
#endif
for (i = 0; i < (keybits >> 5); i++) {
GET_UINT32_LE(RK[i], key, i << 2);
}
switch (ctx->nr) {
case 10:
for (i = 0; i < 10; i++, RK += 4) {
RK[4] = RK[0] ^ RCON[i] ^ ((uint32_t)FSb[(RK[3] >> 8) & 0xFF]) ^
((uint32_t)FSb[(RK[3] >> 16) & 0xFF] << 8) ^ ((uint32_t)FSb[(RK[3] >> 24) & 0xFF] << 16) ^
((uint32_t)FSb[(RK[3]) & 0xFF] << 24);
RK[5] = RK[1] ^ RK[4];
RK[6] = RK[2] ^ RK[5];
RK[7] = RK[3] ^ RK[6];
}
break;
case 12:
for (i = 0; i < 8; i++, RK += 6) {
RK[6] = RK[0] ^ RCON[i] ^ ((uint32_t)FSb[(RK[5] >> 8) & 0xFF]) ^
((uint32_t)FSb[(RK[5] >> 16) & 0xFF] << 8) ^ ((uint32_t)FSb[(RK[5] >> 24) & 0xFF] << 16) ^
((uint32_t)FSb[(RK[5]) & 0xFF] << 24);
RK[7] = RK[1] ^ RK[6];
RK[8] = RK[2] ^ RK[7];
RK[9] = RK[3] ^ RK[8];
RK[10] = RK[4] ^ RK[9];
RK[11] = RK[5] ^ RK[10];
}
break;
case 14:
for (i = 0; i < 7; i++, RK += 8) {
RK[8] = RK[0] ^ RCON[i] ^ ((uint32_t)FSb[(RK[7] >> 8) & 0xFF]) ^
((uint32_t)FSb[(RK[7] >> 16) & 0xFF] << 8) ^ ((uint32_t)FSb[(RK[7] >> 24) & 0xFF] << 16) ^
((uint32_t)FSb[(RK[7]) & 0xFF] << 24);
RK[9] = RK[1] ^ RK[8];
RK[10] = RK[2] ^ RK[9];
RK[11] = RK[3] ^ RK[10];
RK[12] = RK[4] ^ ((uint32_t)FSb[(RK[11]) & 0xFF]) ^ ((uint32_t)FSb[(RK[11] >> 8) & 0xFF] << 8) ^
((uint32_t)FSb[(RK[11] >> 16) & 0xFF] << 16) ^ ((uint32_t)FSb[(RK[11] >> 24) & 0xFF] << 24);
RK[13] = RK[5] ^ RK[12];
RK[14] = RK[6] ^ RK[13];
RK[15] = RK[7] ^ RK[14];
}
break;
}
return (0);
}
int mbedtls_internal_aes_encrypt(mbedtls_aes_context* ctx, const unsigned char input[16], unsigned char output[16]) {
int i;
uint32_t *RK, X0, X1, X2, X3, Y0, Y1, Y2, Y3;
RK = ctx->rk;
GET_UINT32_LE(X0, input, 0);
X0 ^= *RK++;
GET_UINT32_LE(X1, input, 4);
X1 ^= *RK++;
GET_UINT32_LE(X2, input, 8);
X2 ^= *RK++;
GET_UINT32_LE(X3, input, 12);
X3 ^= *RK++;
for (i = (ctx->nr >> 1) - 1; i > 0; i--) {
AES_FROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);
AES_FROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3);
}
AES_FROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);
X0 = *RK++ ^ ((uint32_t)FSb[(Y0) & 0xFF]) ^ ((uint32_t)FSb[(Y1 >> 8) & 0xFF] << 8) ^
((uint32_t)FSb[(Y2 >> 16) & 0xFF] << 16) ^ ((uint32_t)FSb[(Y3 >> 24) & 0xFF] << 24);
X1 = *RK++ ^ ((uint32_t)FSb[(Y1) & 0xFF]) ^ ((uint32_t)FSb[(Y2 >> 8) & 0xFF] << 8) ^
((uint32_t)FSb[(Y3 >> 16) & 0xFF] << 16) ^ ((uint32_t)FSb[(Y0 >> 24) & 0xFF] << 24);
X2 = *RK++ ^ ((uint32_t)FSb[(Y2) & 0xFF]) ^ ((uint32_t)FSb[(Y3 >> 8) & 0xFF] << 8) ^
((uint32_t)FSb[(Y0 >> 16) & 0xFF] << 16) ^ ((uint32_t)FSb[(Y1 >> 24) & 0xFF] << 24);
X3 = *RK++ ^ ((uint32_t)FSb[(Y3) & 0xFF]) ^ ((uint32_t)FSb[(Y0 >> 8) & 0xFF] << 8) ^
((uint32_t)FSb[(Y1 >> 16) & 0xFF] << 16) ^ ((uint32_t)FSb[(Y2 >> 24) & 0xFF] << 24);
PUT_UINT32_LE(X0, output, 0);
PUT_UINT32_LE(X1, output, 4);
PUT_UINT32_LE(X2, output, 8);
PUT_UINT32_LE(X3, output, 12);
mbedtls_platform_zeroize(&X0, sizeof(X0));
mbedtls_platform_zeroize(&X1, sizeof(X1));
mbedtls_platform_zeroize(&X2, sizeof(X2));
mbedtls_platform_zeroize(&X3, sizeof(X3));
mbedtls_platform_zeroize(&Y0, sizeof(Y0));
mbedtls_platform_zeroize(&Y1, sizeof(Y1));
mbedtls_platform_zeroize(&Y2, sizeof(Y2));
mbedtls_platform_zeroize(&Y3, sizeof(Y3));
mbedtls_platform_zeroize(&RK, sizeof(RK));
return (0);
}
int mbedtls_internal_aes_decrypt(mbedtls_aes_context* ctx, const unsigned char input[16], unsigned char output[16]) {
int i;
uint32_t *RK, X0, X1, X2, X3, Y0, Y1, Y2, Y3;
RK = ctx->rk;
GET_UINT32_LE(X0, input, 0);
X0 ^= *RK++;
GET_UINT32_LE(X1, input, 4);
X1 ^= *RK++;
GET_UINT32_LE(X2, input, 8);
X2 ^= *RK++;
GET_UINT32_LE(X3, input, 12);
X3 ^= *RK++;
for (i = (ctx->nr >> 1) - 1; i > 0; i--) {
AES_RROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);
AES_RROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3);
}
AES_RROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3);
X0 = *RK++ ^ ((uint32_t)RSb[(Y0) & 0xFF]) ^ ((uint32_t)RSb[(Y3 >> 8) & 0xFF] << 8) ^
((uint32_t)RSb[(Y2 >> 16) & 0xFF] << 16) ^ ((uint32_t)RSb[(Y1 >> 24) & 0xFF] << 24);
X1 = *RK++ ^ ((uint32_t)RSb[(Y1) & 0xFF]) ^ ((uint32_t)RSb[(Y0 >> 8) & 0xFF] << 8) ^
((uint32_t)RSb[(Y3 >> 16) & 0xFF] << 16) ^ ((uint32_t)RSb[(Y2 >> 24) & 0xFF] << 24);
X2 = *RK++ ^ ((uint32_t)RSb[(Y2) & 0xFF]) ^ ((uint32_t)RSb[(Y1 >> 8) & 0xFF] << 8) ^
((uint32_t)RSb[(Y0 >> 16) & 0xFF] << 16) ^ ((uint32_t)RSb[(Y3 >> 24) & 0xFF] << 24);
X3 = *RK++ ^ ((uint32_t)RSb[(Y3) & 0xFF]) ^ ((uint32_t)RSb[(Y2 >> 8) & 0xFF] << 8) ^
((uint32_t)RSb[(Y1 >> 16) & 0xFF] << 16) ^ ((uint32_t)RSb[(Y0 >> 24) & 0xFF] << 24);
PUT_UINT32_LE(X0, output, 0);
PUT_UINT32_LE(X1, output, 4);
PUT_UINT32_LE(X2, output, 8);
PUT_UINT32_LE(X3, output, 12);
mbedtls_platform_zeroize(&X0, sizeof(X0));
mbedtls_platform_zeroize(&X1, sizeof(X1));
mbedtls_platform_zeroize(&X2, sizeof(X2));
mbedtls_platform_zeroize(&X3, sizeof(X3));
mbedtls_platform_zeroize(&Y0, sizeof(Y0));
mbedtls_platform_zeroize(&Y1, sizeof(Y1));
mbedtls_platform_zeroize(&Y2, sizeof(Y2));
mbedtls_platform_zeroize(&Y3, sizeof(Y3));
mbedtls_platform_zeroize(&RK, sizeof(RK));
return (0);
}
int mbedtls_aes_crypt_ecb(mbedtls_aes_context* ctx, int mode, const unsigned char input[16], unsigned char output[16]) {
#if defined(MBEDTLS_AESNI_C) && defined(MBEDTLS_HAVE_X86_64)
if (mbedtls_aesni_has_support(MBEDTLS_AESNI_AES)) return (mbedtls_aesni_crypt_ecb(ctx, mode, input, output));
#endif
#if defined(MBEDTLS_PADLOCK_C) && defined(MBEDTLS_HAVE_X86)
if (aes_padlock_ace) {
if (mbedtls_padlock_xcryptecb(ctx, mode, input, output) == 0) return (0);
// If padlock data misaligned, we just fall back to
// unaccelerated mode
//
}
#endif
if (mode == MBEDTLS_AES_ENCRYPT)
return (mbedtls_internal_aes_encrypt(ctx, input, output));
else
return (mbedtls_internal_aes_decrypt(ctx, input, output));
}
int mbedtls_aes_crypt_cbc(mbedtls_aes_context* ctx, int mode, size_t length, unsigned char iv[16],
const unsigned char* input, unsigned char* output) {
int i;
unsigned char temp[16];
if (length % 16) return (MBEDTLS_ERR_AES_INVALID_INPUT_LENGTH);
#if defined(MBEDTLS_PADLOCK_C) && defined(MBEDTLS_HAVE_X86)
if (aes_padlock_ace) {
if (mbedtls_padlock_xcryptcbc(ctx, mode, length, iv, input, output) == 0) return (0);
// If padlock data misaligned, we just fall back to
// unaccelerated mode
//
}
#endif
if (mode == MBEDTLS_AES_DECRYPT) {
while (length > 0) {
memcpy(temp, input, 16);
mbedtls_aes_crypt_ecb(ctx, mode, input, output);
for (i = 0; i < 16; i++) output[i] = (unsigned char)(output[i] ^ iv[i]);
memcpy(iv, temp, 16);
input += 16;
output += 16;
length -= 16;
}
}
else {
while (length > 0) {
for (i = 0; i < 16; i++) output[i] = (unsigned char)(input[i] ^ iv[i]);
mbedtls_aes_crypt_ecb(ctx, mode, output, output);
memcpy(iv, output, 16);
input += 16;
output += 16;
length -= 16;
}
}
return (0);
}
void mbedtls_aes_free(mbedtls_aes_context* ctx) {
if (ctx == NULL) return;
mbedtls_platform_zeroize(ctx, sizeof(mbedtls_aes_context));
}
int AESEncryptCBC(const std::string& plaintext, const std::string& key, const std::string& iv,
std::string& ciphertext) {
int rc = -1;
size_t src_len = plaintext.size();
size_t newsize = (src_len / 16) * 16;
newsize += 16;
if (ciphertext.size() < newsize) {
ciphertext.resize(newsize);
}
std::string src_data(plaintext);
if (src_data.size() != newsize) {
src_data.resize(newsize);
// compatible with openssl , fill data for new size
size_t fill_len = newsize - src_len;
for (size_t i = src_len; i < newsize; i++) {
const_cast<char*>(src_data.data())[i] = static_cast<char>(fill_len); // (0, 16)
}
}
std::string tmp_iv(iv);
const unsigned char* input = (const unsigned char*)src_data.data();
unsigned char* output = (unsigned char*)ciphertext.data();
mbedtls_aes_context ctx;
mbedtls_aes_init(&ctx);
rc = mbedtls_aes_setkey_enc(&ctx, (const unsigned char*)key.data(), (unsigned int)(key.size() * 8));
if (rc != 0) {
return rc;
}
rc = mbedtls_aes_crypt_cbc(&ctx, MBEDTLS_AES_ENCRYPT, newsize, (unsigned char*)tmp_iv.data(), input, output);
if (rc != 0) {
return rc;
}
mbedtls_aes_free(&ctx);
return rc;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Code Block End: AES encryption algorithm extracted from mbedtls: https://github.com/Mbed-TLS/mbedtls
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
std::string Base64Encode(const std::string& input) {
const std::string base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
std::string encoded;
int i = 0;
int j = 0;
unsigned char char_array_3[3];
unsigned char char_array_4[4];
for (const auto& c : input) {
char_array_3[i++] = c;
if (i == 3) {
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for (i = 0; i < 4; i++) {
encoded += base64_chars[char_array_4[i]];
}
i = 0;
}
}
if (i) {
for (j = i; j < 3; j++) {
char_array_3[j] = '\0';
}
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for (j = 0; j < i + 1; j++) {
encoded += base64_chars[char_array_4[j]];
}
while (i++ < 3) {
encoded += '=';
}
}
return encoded;
}
std::string GenServerTicket(const std::string& aes_key, const std::string& secret_id, const std::string& title_region_id) {
const auto kHttpTicketIv = "$3,.'/&^rgnjkl!#";
std::time_t timestamp = std::time(nullptr);
std::string timestamp_str = std::to_string(static_cast<int>(timestamp));
std::string plaintext = "{\"secret_id\":\"" + secret_id + "\",\"time\":" + timestamp_str + ",\"title_region_id\":\"" + title_region_id + "\"}";
std::string ciphertext;
AESEncryptCBC(plaintext, aes_key, kHttpTicketIv, ciphertext);
std::string ticket = Base64Encode(ciphertext);
return ticket;
}
void InvokeBackendApi(const std::string& api_url, const std::string& req_body) {
// config from PGOS portal console, game(Pingpong Shooter):
std::string domain = "awseuff.server.pgos.intlgame.com";
std::string title_id = "5buyxq";
std::string title_region_id = "awseuff_5buyxq_589229_dev";
std::string server_key = "GR3Y-L8V7-8XA5-09YR-YT3M";
// parse secret_id, secret_key and aes_key from server_key
std::string secret_id;
std::string secret_key;
std::string aes_key;
std::string delimiter = "-";
auto first_delimiter_pos = server_key.find(delimiter);
if (first_delimiter_pos != std::string::npos) {
secret_id = server_key.substr(0, first_delimiter_pos);
secret_key = server_key.substr(first_delimiter_pos + 1);
aes_key = secret_key;
aes_key.erase(std::remove(aes_key.begin(), aes_key.end(), '-'), aes_key.end());
}
// To initiate a curl command, please ensure that curl is installed locally and accessible.
// The actual implementation may require replacing it with the LIBCURL library or any other library that can make HTTP requests.
std::string body_str_in_cmd = "'" + req_body + "'";
#ifdef _WIN32
std::string body_copy = req_body;
std::string replaceStr = "\\\""; // replace " with \" because Windows Command Prompt does not recognize '.
size_t pos = 0;
while ((pos = body_copy.find("\"", pos)) != std::string::npos) {
body_copy.replace(pos, 1, replaceStr);
pos += replaceStr.length();
}
body_str_in_cmd = "\"" + body_copy + "\"";
#endif
std::string curl_cmd;
curl_cmd.append("curl")
.append(" -H \"Content-Type: application/json\"")
.append(std::string(" -H \"Titleid: ") + title_id + "\"")
.append(std::string(" -H \"Titleregionid: ") + title_region_id + "\"")
.append(std::string(" -H \"Secretid: ") + secret_id + "\"")
.append(std::string(" -H \"Serverticket: ") + GenServerTicket(aes_key, secret_id, title_region_id) + "\"")
.append(std::string(" -d ") + body_str_in_cmd)
.append(std::string(" https://") + domain + api_url);
if (int result = system(curl_cmd.c_str())) {
std::cout << "\n\nrun system command failed, error=" << result << std::endl;
}
else {
std::cout << "\n\nrun system command success." << std::endl;
}
}
int main() {
// Test get_player_info
std::string player_id = "1121380";
std::string req_url = "/player/get_player_info";
std::string req_body = "{\"player_id\": \"" + player_id + "\"}";
std::cout << "get_player_info for player(" << player_id << "), the response data is:\n" << std::endl;
InvokeBackendApi(req_url, req_body);
system("pause");
return 0;
}
// compile: g++ -std=c++11 -o http_api_cpp_sample http_api_cpp_sample.cpp
// execute: ./http_api_cpp_sample
4.2 For Title-Wide
- Golang
- Nodejs
- Python
package main
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"sort"
"strconv"
"strings"
"time"
)
func SplitServerKey(serverKey string) (string, string) {
s := strings.Split(serverKey, "-")
if len(s) > 1 {
secretID := s[0]
secretKey := strings.Join(s[1:], "-")
return secretID, secretKey
}
return "", ""
}
func mapToStr(param map[string]interface{}) string {
var arrSlice []string
for key := range param {
arrSlice = append(arrSlice, key)
}
sort.Strings(arrSlice)
data := make([]string, 0, len(arrSlice))
for _, key := range arrSlice {
data = append(data, key+"="+fmt.Sprintf("%v", param[key]))
}
return strings.Join(data, "&")
}
func makeSig(param map[string]interface{}) string {
strToSig := mapToStr(param)
strByte := sha256.Sum256([]byte(strToSig))
return strings.ToLower(hex.EncodeToString(strByte[:]))
}
func InvokeTitleBackendApi(titleID string, serverKey string, path string, body []byte) (*http.Header, []byte, error) {
secretID, secretKey := SplitServerKey(serverKey)
url := fmt.Sprintf("https://server.pgosglobal.com%s", path)
client := &http.Client{
Timeout: time.Duration(3) * time.Second,
}
curTime := time.Now().Unix()
makeSigParam := map[string]interface{}{
"secret_key": secretKey,
"secret_id": secretID,
"title_id": titleID,
"timestamp": curTime,
}
signature := makeSig(makeSigParam)
req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
if err != nil {
return nil, nil, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("TitleId", titleID)
req.Header.Set("SecretId", secretID)
req.Header.Set("Timestamp", strconv.FormatInt(curTime, 10))
req.Header.Set("Signature", signature)
resp, err := client.Do(req)
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
rspBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, nil, err
}
return &resp.Header, rspBody, nil
}
func main() {
titleID := "your_title_id"
serverKey := "your_server_key"
apiUri := "/title/get_title_file_info"
req := &GetTitleFileInfoRequest{
Paths: []string{"/logo.jpg"},
UrlValidTime: 3600,
}
body, _ := json.Marshal(req)
rspHeader, rspBody, err := InvokeTitleBackendApi(titleID, serverKey, apiUri, body)
if err != nil {
fmt.Printf("err: %s", err)
} else {
fmt.Printf("rspHeader:%v, rspBody: %s\n", rspHeader, string(rspBody))
}
}
const crypto = require('crypto');
const https = require('https');
const querystring = require('querystring');
const { URL } = require('url');
function splitServerKey(serverKey) {
const s = serverKey.split('-');
if (s.length > 1) {
const secretID = s[0];
const secretKey = s.slice(1).join('-');
return { secretID, secretKey };
}
return { secretID: '', secretKey: '' };
}
function mapToStr(param) {
const keys = Object.keys(param).sort();
return keys.map(key => `${key}=${param[key]}`).join('&');
}
function makeSig(param) {
const strToSig = mapToStr(param);
const hash = crypto.createHash('sha256').update(strToSig).digest('hex');
return hash.toLowerCase();
}
function invokeTitleBackendApi(titleID, serverKey, path, body) {
return new Promise((resolve, reject) => {
const { secretID, secretKey } = splitServerKey(serverKey);
const url = new URL(`https://server.pgosglobal.com${path}`);
const curTime = Math.floor(Date.now() / 1000);
const makeSigParam = {
secret_key: secretKey,
secret_id: secretID,
title_id: titleID,
timestamp: curTime,
};
const signature = makeSig(makeSigParam);
const options = {
hostname: url.hostname,
path: url.pathname,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'TitleId': titleID,
'SecretId': secretID,
'Timestamp': curTime,
'Signature': signature,
},
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
resolve({ headers: res.headers, body: data });
});
});
req.on('error', (error) => {
reject(error);
});
req.write(JSON.stringify(body));
req.end();
});
}
(async () => {
const titleID = 'your_title_id';
const serverKey = 'your_server_key';
const apiUri = '/title/get_title_file_info';
const req = {
paths: ['/logo.jpg'],
url_valid_time: 3600,
};
try {
const { headers, body } = await invokeTitleBackendApi(titleID, serverKey, apiUri, req);
console.log(`rspHeader:${JSON.stringify(headers)}, rspBody: ${body}`);
} catch (err) {
console.error(`err: ${err}`);
}
})();
import hashlib
import json
import requests
import time
from typing import Dict, Tuple
from urllib.parse import urlencode
def split_server_key(server_key: str) -> Tuple[str, str]:
s = server_key.split('-', 1)
if len(s) > 1:
secret_id, secret_key = s
return secret_id, secret_key
return "", ""
def map_to_str(param: Dict) -> str:
sorted_keys = sorted(param.keys())
return '&'.join(f'{key}={param[key]}' for key in sorted_keys)
def make_sig(param: Dict) -> str:
str_to_sig = map_to_str(param)
sig = hashlib.sha256(str_to_sig.encode('utf-8')).hexdigest()
return sig.lower()
def invoke_title_backend_api(title_id: str, server_key: str, path: str, body: Dict) -> Tuple[Dict, str]:
secret_id, secret_key = split_server_key(server_key)
url = f'https://server.pgosglobal.com{path}'
cur_time = int(time.time())
make_sig_param = {
'secret_key': secret_key,
'secret_id': secret_id,
'title_id': title_id,
'timestamp': cur_time,
}
signature = make_sig(make_sig_param)
headers = {
'Content-Type': 'application/json',
'TitleId': title_id,
'SecretId': secret_id,
'Timestamp': str(cur_time),
'Signature': signature,
}
response = requests.post(url, json=body, headers=headers)
response.raise_for_status()
return response.headers, response.text
if __name__ == "__main__":
title_id = "your_title_id"
server_key = "your_server_key"
api_uri = "/title/get_title_file_info"
req = {
"paths": ["/logo.jpg"],
"url_valid_time": 3600,
}
try:
rsp_header, rsp_body = invoke_title_backend_api(title_id, server_key, api_uri, req)
print(f"rspHeader: {rsp_header}, rspBody: {rsp_body}")
except Exception as err:
print(f"err: {err}")