Authorizing GOG GALAXY Users in Third-Party Services¶
The GOG GALAXY SDK provides access to Encrypted App Tickets, which can be used by a game for seamless GOG user authorization in any third-party backend.
Encrypted App Tickets are created and encrypted in the GOG GALAXY backend using a shared Private Key. The game can request the ticket for the current user, and the ticket can be passed to any third-party backend that also knows the Private Key and will be able to decrypt the data and thus confirm the user’s identity and their license for the game. The whole process is shown on the following picture:
Retrieving the Encrypted App Ticket¶
-
Request the ticket using the
IUser::RequestEncryptedAppTicket()
method of the GOG GALAXY SDK and provide any additional elements in itsdata
parameter. By default, the following data is enclosed in the ticket:-
user_id
-
client_id
-
timestamp of ticket generation.
-
-
The response to this call comes to
IEncryptedAppTicketListener
. - If the request was successful (
OnEncryptedAppTicketRetrieveSuccess()
), you can retrieve the ticket with theIUser::GetEncryptedAppTicket()
method.
Important
encrypted_ticket.private_key and encryptedAppTicket are already encoded using base64url.
Using the Ticket in a Third-Party Backend¶
App Tickets are encrypted using AES (Rijndael) with a 128-bit block size, 256-bit encryption key in the CBC mode.
In order to use the ticket, first you will need to transfer encrypted_ticket.private_key
for your game to your backend service:
-
Go to the GOG Developer Portal.
-
Click Games in the main menu.
-
In the resulting list of your games, click the SDK Credentials button for your desired game:
-
A pop-up window appears with the SDK Credentials. Copy the value of the third entry (
encrypted_ticket.private_key
): -
Use this value for the
CLIENT_PRIVATE_KEY
variable in either of the scripts.
Important
Be advised, ticket and private_key are incomplete. Both are missing necessary padding(=) at the end of each.
The following scripts are sample implementations of decoding EncryptedAppTickets in a third-party backend.
In our GitHub repository you will find a simple PHP library which decodes encrypted tickets. The script below makes use of this library in a PHP-based backend.
<?php
require_once('vendor/autoload.php');
$mcryptPlaintext = \GOG\SessionTickets\MCryptSessionTicketDecoder::decode(
\GOG\SessionTickets\ExampleData::ENCRYPTED_SESSION_TICKET,
\GOG\SessionTickets\ExampleData::CLIENT_PRIVATE_KEY
);
$openSSLplaintext = \GOG\SessionTickets\OpenSSLSessionTicketDecoder::decode(
\GOG\SessionTickets\ExampleData::ENCRYPTED_SESSION_TICKET,
\GOG\SessionTickets\ExampleData::CLIENT_PRIVATE_KEY
);
if ($openSSLplaintext === $mcryptPlaintext && $mcryptPlaintext === \GOG\SessionTickets\ExampleData::PLAINTEXT_SESSION_TICKET) {
echo $openSSLplaintext."\n";
} else {
throw new \Exception('Encrypted Session Ticket decryption failed');
}
This python example shows how to decode both ticket and private as both are base64url encoded.
First make sure if padding is correct as both ticket and the key might be missing =
symbols.
Then using decoded private key we use AES decryption to extract our ticket.
``` python
import base64
from Crypto.Cipher import AES
def decrypt(base64_encrypted_ticket, base64_secret):
encrypted_ticket = base64.urlsafe_b64decode(base64_encrypted_ticket)
secret = base64.urlsafe_b64decode(base64_secret)
ivSize = AES.block_size
cipher = AES.new(secret, AES.MODE_CBC, encrypted_ticket[:ivSize])
data = cipher.decrypt(encrypted_ticket[ivSize:])
return data
ticket = ''
key = ''
# restore missing padding truncated on galaxy backend(?)
ticket += '=' * (-len(ticket) % 4)
key += '=' * (-len(key) % 4)
decrytped_data = decrypt(ticket, key)
print(base64.urlsafe_b64encode(decrytped_data))
print(decrytped_data)
```
CSharp example is analogous to the Python one except here we first convert base64url encoded ticket and key to base64.
using System;
using System.Collections;
using System.IO;
using System.Security.Cryptography;
using System.Text;
class Program
{
static byte[] decrypt(string ticket, string key)
{
// convert base64
byte[] decodedTicketBytes = Convert.FromBase64String(ticket);
byte[] decodedSecretBytes = Convert.FromBase64String(key);
// IV is the first 16 bytes of the ticket
byte[] iv = decodedTicketBytes[0..16];
// encrypted data is the rest of the ticket
byte[] bytesWithoutIV = decodedTicketBytes[16..^0];
using (Aes aes = Aes.Create())
{
aes.Key = decodedSecretBytes;
aes.IV = iv;
aes.Padding = PaddingMode.Zeros;
byte[] decryptedBytes = null;
using (ICryptoTransform decryptor = aes.CreateDecryptor())
{
decryptedBytes = decryptor.TransformFinalBlock(bytesWithoutIV, 0, bytesWithoutIV.Length);
}
return decryptedBytes;
}
}
static void Main()
{
string ticket = "";
string key = "";
// Convert base64url to base64
ticket = ticket.Replace("-", "+").Replace("_", "/");
key = key.Replace("-", "+").Replace("_", "/");
// Fix missing padding
ticket = ticket.PadRight(ticket.Length + (4 - ticket.Length % 4) % 4, '=');
key = key.PadRight(key.Length + (4 - key.Length % 4) % 4, '=');
byte[] decryptedData = decrypt(ticket, key);
Console.WriteLine(Convert.ToBase64String(decryptedData));
Console.WriteLine(System.Text.Encoding.UTF8.GetString(decryptedData));
}
}