mirror of
https://github.com/deepkicks/TitaniaPy.git
synced 2026-10-17 19:00:33 +02:00
183 lines
5.6 KiB
Python
183 lines
5.6 KiB
Python
"""
|
|
PS5 Crypto Operations
|
|
- AES-128-CBC decryption with known keys (Layer 3)
|
|
- PBKDF2 key derivation (Layer 0 - experimental)
|
|
- Key derivation infrastructure for when Layer 1 gaps are filled
|
|
"""
|
|
import hashlib
|
|
import struct
|
|
from typing import Optional
|
|
|
|
from Crypto.Cipher import AES
|
|
|
|
|
|
def aes_128_cbc_decrypt(data: bytes, key: bytes, iv: bytes = None) -> bytes:
|
|
"""Standard AES-128-CBC decryption."""
|
|
if iv is None:
|
|
iv = b"\x00" * 16
|
|
if len(key) != 16:
|
|
raise ValueError(f"Key must be 16 bytes, got {len(key)}")
|
|
if len(iv) != 16:
|
|
raise ValueError(f"IV must be 16 bytes, got {len(iv)}")
|
|
# Pad data to 16-byte boundary if needed
|
|
padded = data
|
|
remainder = len(data) % 16
|
|
if remainder:
|
|
padded = data + b"\x00" * (16 - remainder)
|
|
cipher = AES.new(key, AES.MODE_CBC, iv)
|
|
result = cipher.decrypt(padded)
|
|
return result[: len(data)]
|
|
|
|
|
|
def aes_128_ecb_decrypt(data: bytes, key: bytes) -> bytes:
|
|
"""AES-128-ECB decryption (single block or multiple)."""
|
|
if len(key) != 16:
|
|
raise ValueError(f"Key must be 16 bytes, got {len(key)}")
|
|
padded = data
|
|
remainder = len(data) % 16
|
|
if remainder:
|
|
padded = data + b"\x00" * (16 - remainder)
|
|
cipher = AES.new(key, AES.MODE_ECB)
|
|
result = cipher.decrypt(padded)
|
|
return result[: len(data)]
|
|
|
|
|
|
def pbkdf2_derive(
|
|
password: bytes, salt: bytes, iterations: int, key_len: int = 16
|
|
) -> bytes:
|
|
"""PBKDF2-HMAC-SHA256 key derivation."""
|
|
return hashlib.pbkdf2_hmac("sha256", password, salt, iterations, dklen=key_len)
|
|
|
|
|
|
def decrypt_emc_ipl_header(data: bytes) -> bytes:
|
|
"""
|
|
Decrypt EMC IPL header using the known CP key.
|
|
Key: F0332357C8CFAE7E7E26E52BE9E3AED4
|
|
AES-128-CBC with zeroed IV.
|
|
"""
|
|
from keys import EMC_IPL_HEADER_KEY
|
|
|
|
return aes_128_cbc_decrypt(data, EMC_IPL_HEADER_KEY)
|
|
|
|
|
|
def decrypt_emc_ipl_body(data: bytes) -> bytes:
|
|
"""
|
|
Decrypt EMC IPL body (rev c0 only).
|
|
Key: D5C92E39759A3E5CE954E772B1C2B651
|
|
AES-128-CBC with zeroed IV.
|
|
"""
|
|
from keys import EMC_IPL_CIPHER_KEY
|
|
|
|
return aes_128_cbc_decrypt(data, EMC_IPL_CIPHER_KEY)
|
|
|
|
|
|
def decrypt_eap_kbl(data: bytes) -> bytes:
|
|
"""
|
|
Decrypt EAP KBL.
|
|
Key: 262555E3CF062B070B5AA2CDDF3A5D0E
|
|
AES-128-CBC with zeroed IV.
|
|
"""
|
|
from keys import EAP_KBL_KEY
|
|
|
|
return aes_128_cbc_decrypt(data, EAP_KBL_KEY)
|
|
|
|
|
|
def decrypt_eap_kernel(data: bytes) -> bytes:
|
|
"""
|
|
Decrypt EAP Kernel (from KBL).
|
|
Key: CBCC1E53F42C1CB44D965E233CD792A8
|
|
AES-128-CBC with zeroed IV.
|
|
"""
|
|
from keys import EAP_KBL_KERNEL_KEY
|
|
|
|
return aes_128_cbc_decrypt(data, EAP_KBL_KERNEL_KEY)
|
|
|
|
|
|
def decrypt_eap_kernel_self(data: bytes) -> bytes:
|
|
"""
|
|
Decrypt EAP Kernel SELF modules.
|
|
Key: 32D00F27AE38FE4AC88A352313A2BFB4
|
|
IV: 08FEA1ACC37A630999745386168812EC
|
|
AES-128-CBC.
|
|
"""
|
|
from keys import EAP_KERNEL_SELF_KEY, EAP_KERNEL_SELF_IV
|
|
|
|
return aes_128_cbc_decrypt(data, EAP_KERNEL_SELF_KEY, EAP_KERNEL_SELF_IV)
|
|
|
|
|
|
def decrypt_with_titania_key(data: bytes, revision: int) -> Optional[bytes]:
|
|
"""
|
|
Decrypt using Titania master key for a given hardware revision.
|
|
Only revision 2 has a known key; 1 and 3 have only IVs.
|
|
"""
|
|
from keys import TITANIA_KEYS
|
|
|
|
if revision not in TITANIA_KEYS:
|
|
return None
|
|
tk = TITANIA_KEYS[revision]
|
|
if "key" not in tk:
|
|
return None
|
|
return aes_128_cbc_decrypt(data, tk["key"], tk["iv"])
|
|
|
|
|
|
# --- Experimental: PS4-style key derivation (adapted for PS5) ---
|
|
|
|
def ps4_decrypt_with_seed(
|
|
rom_constant: bytes, key_seed: bytes, fuse_key: bytes
|
|
) -> bytes:
|
|
"""
|
|
PS4 Aeolia-style key derivation (fail0verflow).
|
|
Builds 32-byte input: key_seed[8] repeated twice + rom_constant[16]
|
|
AES-128-CBC decrypt with zeroed IV using fuse_key.
|
|
Returns second 16 bytes of result.
|
|
|
|
NOTE: PS5 probably uses a different scheme (PBKDF2 + KMB_AES).
|
|
This is here for reference/experimentation.
|
|
"""
|
|
if len(key_seed) < 8:
|
|
key_seed = key_seed + b"\x00" * (8 - len(key_seed))
|
|
plaintext = key_seed[:8] + key_seed[:8] + rom_constant[:16]
|
|
decrypted = aes_128_cbc_decrypt(plaintext, fuse_key)
|
|
return decrypted[16:32]
|
|
|
|
|
|
def experimental_pbkdf2_master_key() -> bytes:
|
|
"""
|
|
Experimental: derive master key using OTP_AES_ROOT + KDF_SALT1 + 1000 iters.
|
|
This is Layer 0 of the PS5 key derivation.
|
|
The exact parameters (hash function, key length) are not fully confirmed.
|
|
Trying PBKDF2-HMAC-SHA256 with 16-byte output.
|
|
"""
|
|
from keys import OTP_AES_ROOT, KDF_SALT1, KDF_ITERS
|
|
|
|
return pbkdf2_derive(OTP_AES_ROOT, KDF_SALT1, KDF_ITERS, key_len=16)
|
|
|
|
|
|
def show_experimental_derivation():
|
|
"""Show experimental PBKDF2 derivation results for research."""
|
|
from keys import OTP_AES_ROOT, KDF_SALT1, KDF_ITERS
|
|
|
|
print("=== Experimental PBKDF2 Key Derivation ===")
|
|
print(f"OTP_AES_ROOT: {OTP_AES_ROOT.hex()}")
|
|
print(f"KDF_SALT1: {KDF_SALT1.hex()}")
|
|
print(f"KDF_ITERS: {KDF_ITERS}")
|
|
print()
|
|
|
|
for hash_fn in ("sha256", "sha1", "sha512"):
|
|
for kl in (16, 32):
|
|
derived = hashlib.pbkdf2_hmac(
|
|
hash_fn, OTP_AES_ROOT, KDF_SALT1, KDF_ITERS, dklen=kl
|
|
)
|
|
print(f"PBKDF2-HMAC-{hash_fn.upper()} (dklen={kl}): {derived.hex()}")
|
|
|
|
print()
|
|
print("NOTE: The correct hash function and key length are not confirmed.")
|
|
print("These are candidates to test against known Titania master keys.")
|
|
print()
|
|
|
|
from keys import TITANIA_KEYS
|
|
for rev, tk in TITANIA_KEYS.items():
|
|
if "key" in tk:
|
|
print(f"Titania {rev} known key: {tk['key'].hex()}")
|
|
print(f"Titania {rev} known IV: {tk['iv'].hex()}")
|