Apollo Save File Encryption and Decryption: Algorithms and Key Locations
Apollo uses RC4 for PS2 CodeBreaker saves, AES-CBC-128 for PS1 MCX memory cards, AES-ECB/CBC with SHA-1 for PSV/VMP resigning, and system-generated sealed keys for PS4 encrypted saves, with all hard-coded keys defined in specific source files under source/.
The Apollo save game manager for PlayStation 4 (repository bucanero/apollo-ps4) implements multiple cryptographic primitives to handle legacy and modern save formats. Each algorithm is tailored to the specific console generation's security model, with keys either embedded in the source code or generated at runtime through kernel interfaces.
RC4 Stream Cipher for PS2 CodeBreaker Saves (*.cbs)
Key Location and Implementation
Apollo handles PS2 CodeBreaker saves using the RC4 (ARC4) stream cipher. The 256-byte permutation table is defined as a static constant named cbsKey in source/psv_ps2.c at lines 24-55. This hard-coded key table is identical for both encryption and decryption operations because RC4 is symmetric.
Encryption and Decryption Process
The cbsCrypt() function initializes an arc4_context structure, copies the static cbsKey into the context, and performs in-place encryption or decryption using arc4_crypt().
/* psv_ps2.c – cbsCrypt() */
static void cbsCrypt(uint8_t *buf, size_t bufLen)
{
arc4_context ctx; // ARC4 (RC4) context
memset(&ctx, 0, sizeof(arc4_context));
memcpy(ctx.m, cbsKey, sizeof(cbsKey)); // static 256-byte key table
arc4_crypt(&ctx, bufLen, buf, buf); // encrypt / decrypt in-place
}
Because RC4 generates a keystream that is XORed with plaintext, calling cbsCrypt() twice on the same buffer returns the original data, making the same function serve both operations.
AES-CBC-128 for PS1 MCX Memory Cards
Static Key and IV Definitions
For PS1 MCX (encrypted memory card) files, Apollo implements AES-CBC-128. The cryptographic material consists of a 16-byte key (mcxKey) and a 16-byte initialization vector (mcxIv), both defined as static constants in source/ps1card.c at lines 32-35.
Implementation Details
The AesCbcEncrypt() function initializes an AES context, sets the 128-bit encryption key, and processes the buffer using CBC mode. The corresponding AesCbcDecrypt() function uses the same key and IV for reverse operation.
/* ps1card.c – AesCbcEncrypt() */
static void AesCbcEncrypt(uint8_t *toEncrypt, size_t len,
const uint8_t *key, const uint8_t *aes_iv)
{
uint8_t iv[16];
aes_context ctx;
memcpy(iv, aes_iv, 16);
aes_init(&ctx);
aes_setkey_enc(&ctx, key, 128); // 128-bit key
aes_crypt_cbc(&ctx, AES_ENCRYPT, len, iv, toEncrypt, toEncrypt);
aes_free(&ctx);
}
The static nature of mcxKey and mcxIv means all MCX files processed by Apollo use the same encryption parameters, consistent with the legacy PS1 memory card format specification.
AES and SHA-1 for PSV/VMP Re-signing
Key Hierarchy and Hash Generation
Apollo re-signs PSV (PS3-style) and VMP (Vita) files using a combination of AES-ECB, AES-CBC, and SHA-1. The process uses three static values defined in source/psv_resign.c at lines 39-49: psv_ps1key (16-byte), psv_ps2key (16-byte), and psv_iv (16-byte).
The generateHash() function creates a new signature by decrypting a salt with AES-ECB, re-encrypting with AES-ECB, XORing with the IV, then hashing the result with SHA-1.
/* psv_resign.c – generateHash() */
aes_setkey_dec(&aes_ctx, psv_ps1key, 128);
aes_crypt_ecb(&aes_ctx, AES_DECRYPT, work_buf, salt);
aes_setkey_enc(&aes_ctx, psv_ps1key, 128);
aes_crypt_ecb(&aes_ctx, AES_ENCRYPT, work_buf, salt + 0x10);
XorWithIv(salt, psv_iv);
...
sha1_starts(&sha1_ctx);
sha1_update(&sha1_ctx, salt, sizeof(salt));
sha1_update(&sha1_ctx, input, sz);
sha1_finish(&sha1_ctx, work_buf);
The resulting 20-byte hash is written to PSV_HASH_OFFSET in the file header, replacing the original signature while preserving the save data integrity.
Sealed Keys for PS4 Encrypted Saves
Runtime Key Generation
For PS4 encrypted saves, Apollo does not perform cryptographic operations in userspace. Instead, it relies on the Orbis kernel to handle encryption via sealed keys. The generateSealedKey() function in source/sd.c interfaces with /dev/sbl_srv using ioctl 0x40845303 to request the kernel generate a 256-byte sealed key.
/* sd.c – generateSealedKey() */
int generateSealedKey(uint8_t data[ENC_SEALEDKEY_LEN])
{
uint8_t sealedKey[ENC_SEALEDKEY_LEN];
int fd = open("/dev/sbl_srv", O_RDWR);
ioctl(fd, 0x40845303, sealedKey); // kernel creates the sealed key
memcpy(data, sealedKey, sizeof(sealedKey));
close(fd);
return 0;
}
Volume Key File Storage
The sealed key is persisted to a volume key file on the user's filesystem (conventionally named save.bin.key). The createSave() function in source/sd.c (lines 129-150) writes the sealed key to the path specified by volumeKeyPath, while mountSave() (lines 189-202) reads it back via decryptSealedKeyAtPath() and passes the decrypted key to sceFsMountSaveData().
/* sd.c – createSave() – writes the key to a file */
fd = sceKernelOpen(volumeKeyPath, O_CREAT|O_TRUNC|O_WRONLY, 0777);
sceKernelWrite(fd, sealedKey, sizeof(sealedKey));
sceKernelClose(fd);
This architecture ensures that the actual encryption keys never exist in plaintext within Apollo's memory or source code, leveraging the PS4's hardware security module for PS4 save operations.
Summary
- RC4 (ARC4) encrypts PS2 CodeBreaker saves using the static
cbsKeytable defined insource/psv_ps2.c. - AES-CBC-128 protects PS1 MCX memory cards with the static
mcxKeyandmcxIvconstants insource/ps1card.c. - AES-ECB/CBC with SHA-1 generates new signatures for PSV/VMP files using
psv_ps1key,psv_ps2key, andpsv_ivfromsource/psv_resign.c. - Sealed keys handle PS4 save encryption via the kernel; Apollo generates these keys in
source/sd.cand stores them in user-supplied volume key files.
Frequently Asked Questions
What encryption algorithm does Apollo use for PS2 saves?
Apollo uses the RC4 (ARC4) stream cipher for PS2 CodeBreaker (*.cbs) files. The algorithm is implemented in source/psv_ps2.c within the cbsCrypt() function, which uses a static 256-byte permutation table called cbsKey to encrypt or decrypt the save data in-place.
Where are the AES keys stored for PS1 memory card operations?
The AES-128 keys for PS1 MCX files are stored as static constants in source/ps1card.c at lines 32-35. Specifically, mcxKey (16 bytes) and mcxIv (16 bytes) are hard-coded and used by the AesCbcEncrypt() and AesCbcDecrypt() functions to process encrypted PS1 memory card images.
How does Apollo handle PS4 save file encryption?
Apollo does not perform PS4 encryption in userspace. Instead, it relies on the Orbis kernel and the sbl_srv device. The generateSealedKey() function in source/sd.c requests a sealed key from the kernel via ioctl 0x40845303, stores it in a volume key file (e.g., save.bin.key), and later passes it to sceFsMountSaveData() through mountSave() to access encrypted saves.
Can Apollo decrypt PSV files without the original keys?
Yes, Apollo can re-sign PSV files because it contains the necessary cryptographic material in source/psv_resign.c. The generateHash() function uses hard-coded keys (psv_ps1key for PS1-style saves, psv_ps2key for PS2-style saves) and the initialization vector psv_iv to generate a new SHA-1 hash and AES-based signature, effectively allowing the file to be resigned without requiring the original user's keys.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →