SubSpace: Encryption


This page is currently under construction. It will describe Continuum's packet encryption scheme, its underlying security principles, and how it was compromised.


Continuum's packet encryption is designed to prevent in-game cheating. It uses a block cipher (of unknown origin) with a 64-bit block size and 640-bit key. The original key space is 64 bits which is then expanded to 640 bits using a key expansion routine.

Key Exchange

The 64-bit key exchange takes place when a connection is established. The login sequence is below (bytes in hex, fields little-endian):

C2S: 00 01 <legacy (4)> 11 00
S2C: 00 10 <key1 (4)> <key2 (4)> 01 00
C2S: 00 11 <key1 (4)> 01 00
-- keys exchanged --

Note that there are two 32-bit keys and both are entirely specified by the server unlike the original SubSpace encryption. All packets after the key exchange are prepended with an 8-bit checksum and then encrypted prior to sending.

Key Expansion

Continuum's key expansion consists of three distinct steps. The input to each step is an 80-byte buffer (the expanded key), and one of the two 32-bit keys described above. The output of each step is a modified buffer containing an intermediate result. The first and second steps take key2 and the final step takes key1. Below is some pseudocode that describes the process.

uint8 expandedKey[80];
step1(expandedKey, key2);
step2(expandedKey, key2);
step3(expandedKey, key1);

The third step is the simplest: it calculates a modified MD5 of the expanded key and key1 and XORs it into the expanded key.

Server Security

The security of the system relies on the secrecy of the key expansion routines. Consequently, the first two steps of the key expansion routine are contained only in the Continuum executable. The third step is contained in ASSS (, subgame, and Continuum. Since the servers cannot expand a key themselves, they rely on Continuum to perform the first two expansion steps. The file scrty1 in the sever distribution contains these partially expanded keys.

The scrty1 file has the following format:

<version (4)>
    <key2 (4)>
    <step1 expansion (80)>
} * 1024
    <key2 (4)>
    <step1,2 expansion (80)>
} * 1024

The scrty1 file (along with scrty) is generated by running Continuum with a single argument, "Z". When a connection request is made, the server generates a random key1 and uses the bottom 10 bits to index into the step1,2 expansion key array. When the client responds with key1, the server again takes the bottom 10 bits, indexes into the array, retrieves the partially expanded key, and runs step3 to generate a fully-expanded key.

For a server to remain secure, it is imperative that the scrty1 file be kept secret. Moreover, every zone should run "Continuum Z" in the server directory before launch to ensure that a unique scrty1 file is used.

Checksum Byte

Each packet is first prepended with an 8-bit checksum before is it encrypted. Continuum uses CRC8 with the generator polynomial x^8 + x^5 + x^4 + 1. The algorithm is presented below.

uint8 crc8(uint8 * packet, int length)
    uint8 crc = 0;
    for(int i = 0; i < length; ++i)
        crc = lookupTable[crc ^ packet[i]];
    return crc;

The full source (including the lookup table) is available here.


I haven't had a chance to write much about the Continuum encryption but the source is now available for download. The key parameter is a 20-element array of 32-bit integers. Note that the encrypt routine must be called after prepending the CRC8 (described above) and the decrypt routine must be called before discarding the checksum.


I have constructed and deployed an oracular application which provides answers to key expansion and executable checksum problems. The service is available on (UDP) with the following protocol:

Key Expansion Request:
00 80 <key1 (4)> <key2 (4)> 01 00

Key Expansion Response:
00 81 <key1 (4)> <key2 (4)> <expandedKey (80)> 01 00

EXE Checksum Request:
00 82 <seed (4)>

EXE Checksum Response:
00 83 <checksum (4)>

Whenever a client receives the 00 10 key exchange packet from the server, it should rewrite the second byte of the packet to 80 and forward it to the oracle. The oracle will reply with the full, 80-byte expanded key which can then be used with the encryption and decryption routines. Once the client receives the 00 81 packet from the oracle, it should respond to the game server with the 00 11 packet.

When a client receives an EXE checksum challenge with a seed, it should construct a 00 82 packet with the seed. The oracle will provide the correct answer which the client can use in its response to the game server.