Pak encryption and decryption

Dawn Engine pak encryption protects shipped asset packs (.pak files) so that file data, file names, and the zip central directory are ciphertext on disk. Ordinary players and standard zip tools (7-Zip, WinRAR, unzip, Python zipfile) cannot list or extract the real assets. At runtime the Archive layer decrypts on demand; game code, FileIO, and asset loading do not need changes.

Maturity: Delivered. End-to-end packaging, runtime decryption, key hardening, and shipping validation are in place. Encryption is a build- and pack-time opt-in: tools and launchers must be built with LY_PAK_ENCRYPTION=ON, and bundles must be produced with --encrypt / --encrypt-paks.

This is hardening, not absolute DRM. Because the client must decrypt offline, a determined reverse engineer with full control of the binary can eventually recover keys or dump plaintext from memory. The goal is to stop casual extraction and raise the cost of amateur reverse engineering above the value of the assets.

What problem this solves

O3DE pak files are zip archives. Without encryption, anyone who obtains game_<platform>.pak can open it with common tools and pull meshes, textures, configs, and other content. Dawn Engine reuses the engine’s existing zip encryption header skeleton and implements AES-256-CTR encryption of both the central directory and each stored file, so the pack is unreadable outside the matching launcher.

It does not encrypt the engine executable itself, replace the pak/zip container format, or require online key servers in v1.

How it works

  1. Pack time: AssetBundlerBatch (or export-project --encrypt-paks) compresses assets as usual, then encrypts each file’s stored bytes and the central directory (CDR). Each pack gets its own salt; keys are derived from a 32-byte master key.
  2. Optional integrity: from format v2, HMAC-SHA256 detects tampering. From v4, optional Ed25519 CDR signatures stop forged packs even if the symmetric master key were extracted.
  3. Ship time: the release launcher embeds an obfuscated copy of the same master key (and optionally the signature public key). No key environment variables are required on the player machine.
  4. Run time: opening a pack decrypts the CDR; reading a file decrypts that file’s ciphertext in place, then decompresses. Mixed mounts of plaintext and encrypted packs are supported when not in strict mode.
Seed / asset list → AssetBundler (--encrypt) → encrypted .pak
 DAWN_PAK_MASTER_KEY (env / file)
 Launcher build (embedded obfuscated key)
 OpenPack → decrypt CDR → ReadFile → decrypt → decompress → assets

Prerequisites

RequirementNotes
Encryption-enabled toolchainBuild AssetBundlerBatch and the shipping launcher with LY_PAK_ENCRYPTION=ON. An OFF build that receives --encrypt fails instead of writing a plaintext pack.
Master key32 bytes (64 hex characters), injected via environment variable or key file—never via git, setreg, or CLI plaintext.
Optional sign key32-byte Ed25519 private seed (DAWN_PAK_SIGN_KEY) for v4 signed packs; public key is embedded at launcher configure time.
Same key for pack and embedPackaging and launcher embed must use the same master key, or runtime rejects the pack with a master-key mismatch (KCV).

Recommended shipping CMake flags for the launcher:

-DLY_PAK_ENCRYPTION=ON
-DLY_PAK_ENCRYPTION_STRICT=ON
-DLY_PAK_EMBED_MASTER_KEY=ON
SwitchRole
LY_PAK_ENCRYPTIONCompiles encrypt/decrypt paths
LY_PAK_ENCRYPTION_STRICTFail closed on decrypt/HMAC/signature failures; if a sign public key is embedded, unsigned packs are rejected
LY_PAK_EMBED_MASTER_KEYAt configure time, generate obfuscated key material into the build tree (never commit the generated header)

Day-to-day tip: use plaintext packs in profile/debug for iteration; encrypt only release PAK shipping builds.

Prepare keys (never commit)

Generate a master key:

openssl rand -hex 32
# or
python -c "import secrets; print(secrets.token_hex(32))"

Inject at pack (and launcher configure) time:

# Linux / macOS
export DAWN_PAK_MASTER_KEY=<64-hex>
# or
export DAWN_PAK_MASTER_KEY_FILE=/secure/dawn_pak_master_key.hex

# Windows PowerShell
$env:DAWN_PAK_MASTER_KEY = "<64-hex>"

Optional CDR signing (v4):

export DAWN_PAK_SIGN_KEY=<64-hex> # or DAWN_PAK_SIGN_KEY_FILE=<path>
# Dev/test only for the public key:
export DAWN_PAK_SIGN_PUBLIC_KEY=<64-hex>

If DAWN_PAK_SIGN_KEY is set, packs are signed; if not, packs remain encrypted but unsigned and still load (unless STRICT + embedded public key requires signatures).

Hard rules

  • Do not put master or sign keys in git, CI logs, crash dumps, or command-line arguments.
  • Prefer CI secrets / a vault; use the same secret for both “bundle” and “embed launcher” steps.
  • Repository guards (.gitignore, scripts/pak_encryption/check_no_keys.py on PRs) block accidental key check-ins.

Produce encrypted packs

Pick one path. The master key is always read from the environment or a key file—there is no CLI flag that accepts the raw key.

export DAWN_PAK_MASTER_KEY=<64-hex>
o3de export-project -es ExportScripts/export_source_built_project.py \
 --project-path <ProjectPath> \
 -assets --build-tools --config release --archive-output zip \
 --seedlist <ProjectPath>/Assets/seedlist.seed -out <OutputPath> \
 --encrypt-paks
  • --encrypt-paks / -ep enables encryption; --no-encrypt-paks / -nep turns it off.
  • Missing master key with encryption requested aborts early; no plaintext pack is written.

Path B — AssetBundlerBatch bundles

export DAWN_PAK_MASTER_KEY=<64-hex>
AssetBundlerBatch bundles \
 --assetListFile game_pc.assetlist \
 --outputBundlePath Cache/pc/game_pc.pak \
 --maxSize 2048 \
 --encrypt \
 --platform pc \
 --allowOverwrites

Path C — AssetBundlerBatch bundleSeed

export DAWN_PAK_MASTER_KEY=<64-hex>
AssetBundlerBatch bundleSeed \
 --seedListFile game.seed \
 --outputBundlePath Cache/pc/game_pc.pak \
 --encrypt --platform pc
FlagMeaning
--encryptMark the bundle for Dawn pak encryption
--outputBundlePathOutput path (must end in .pak)
--maxSize <MB>Split into dependent volumes when exceeded (default 2048)
--platformTarget platform
--allowOverwritesOverwrite existing outputs (typical in CI)

Multi-volume packs

When size exceeds --maxSize, the bundler writes <name>.pak, <name>__1.pak, <name>__2.pak, … (not .pak.001). Each volume is a full encrypted pack with its own salt and CDR crypto. Runtime mounts volumes from the primary manifest. Verify every volume, not only the primary.

Runtime decryption

With an ON launcher and a matching embedded (or env-provided) key:

  • Encrypted packs open and load like normal packs.
  • Call sites under IArchive / FileIO stay unchanged.
  • Decrypt is streaming and offset-friendly (AES-256-CTR); packs are not fully decrypted to disk.
  • In non-strict configurations, plaintext and encrypted packs can be mounted together.

For shipping, prefer embedded obfuscated key + STRICT. Environment variables are for pack tools and local/dev launchers, not for end-user machines.

Verify after packaging

CheckCommandExpect
Not extractable by common toolspython scripts/pak_encryption/verify_pak_unreadable.py Cache/pc/game_pc.pak --expect-name <known-asset>Exit 0
Script sanity on plaintextSame with --plaintext on a non-encrypted packExit 0
Pack key matches embed keypython scripts/pak_encryption/verify_key_fingerprint.py --expected-file <embed-fingerprint>Fingerprints match
No plaintext key in binariespython scripts/pak_encryption/scan_binary_for_key.py <install-or-layout>/Exit 0
OFF vs ON-without-encryptpython scripts/pak_encryption/compare_pak_regression.py <off.pak> <on-plain.pak>Equivalent aside from timestamps

For multi-volume packs, run the unreadable check on the primary and each __N volume.

Minimal CI sketch:

export DAWN_PAK_MASTER_KEY="$PAK_MASTER_KEY_SECRET"
export DAWN_PAK_SIGN_KEY="$PAK_SIGN_KEY_SECRET" # optional

cmake --preset windows-mono \
 -DLY_PAK_ENCRYPTION=ON -DLY_PAK_ENCRYPTION_STRICT=ON -DLY_PAK_EMBED_MASTER_KEY=ON
cmake --build build/windows_mono --config release

o3de export-project -es ExportScripts/export_source_built_project.py \
 --project-path <ProjectPath> -assets --build-tools --config release \
 --archive-output zip --seedlist <ProjectPath>/Assets/seedlist.seed \
 -out <OutputPath> --encrypt-paks

python scripts/pak_encryption/verify_pak_unreadable.py <layout>/Cache/pc/game_pc.pak --expect-name game.cfg
python scripts/pak_encryption/scan_binary_for_key.py <layout>/

Safe to rely on:

  • AES-256-CTR encryption of CDR and per-file data; HMAC integrity (v2+); optional Ed25519 CDR signing (v4);
  • Transparent runtime load; multi-volume __N encryption; mixed plaintext/encrypted mounts when not STRICT;
  • Key via env / file only; obfuscated embed for shipping; PR key-leak scan.

Do not assume:

  • Absolute protection against professional reverse engineering;
  • Online DRM or server-side key delivery in v1;
  • Compatibility with external zip tools (they are expected to fail);
  • Encryption without rebuilding tools/launcher with LY_PAK_ENCRYPTION=ON;
  • Successful load when pack and launcher keys differ.

Troubleshooting

SymptomLikely causeWhat to try
“pak encryption support is not compiled in”Tools built with encryption OFFRebuild AssetBundlerBatch with LY_PAK_ENCRYPTION=ON
“no pak master key is available”Missing DAWN_PAK_MASTER_KEY(_FILE)Set the env var or key file before packing
Runtime “master key mismatch”Pack key ≠ embedded keyAlign secrets; run verify_key_fingerprint.py; rebuild pack and launcher
“signature is invalid” / “requires signed paks”Sign key/public mismatch, or STRICT + unsigned packUse matching DAWN_PAK_SIGN_KEY / embedded public key, or ship signed packs
Configure: “embedding the pak master key failed”Embed ON but no key at configureInject DAWN_PAK_MASTER_KEY(_FILE) into the configure environment
check_no_keys.py hitsKey or generated header stagedRemove from the commit; confirm .gitignore
7-Zip can still list real namesPack was not encryptedConfirm --encrypt / --encrypt-paks and ON tools

Next steps

  • Introduction: Dawn Engine capabilities, including asset protection.
  • World Streaming: large-world streaming that may also ship as packs/DLC.

Copyright © 2026 DawnEngine. All rights reserved.

DawnEngine is a commercial 3D engine distributed under the DawnEngine end-user license agreement. Engine binaries and source are proprietary and are not covered by the licenses below.

Documentation only: the prose and templates on this site are a derivative work of Open 3D Engine (O3DE) documentation by the O3DE Contributors, used under CC BY 4.0 (documentation content), Apache 2.0 (site code), and the MIT license (inline code samples).

The open-source 3D engine that DawnEngine is built on top of is Open 3D Engine . DawnEngine is not affiliated with, endorsed by, or sponsored by The Linux Foundation or the O3DE project. “O3DE” and “Open 3D Engine” are trademarks of The Linux Foundation.