7.6 KiB
pypr
pypr is a Python toolkit for parsing, analyzing, and interacting with Microsoft PlayReady (PRD) device files, PlayReady headers, PSSH data, and license acquisition workflows.
It provides structured, low-level access to PlayReady internals, enabling controlled inspection of device credentials, WRMHEADER data, SOAP-based license exchanges, and content key extraction.
Overview
pypr offers a standalone and modular implementation of PlayReady workflows, inspired by established projects such as pyplayready and Bento4. The project emphasizes clarity, extensibility, and direct control over DRM-related structures.
It is intended for research, interoperability testing, and technical analysis of PlayReady-protected content.
Features
-
Parsing and loading of PlayReady Device (PRD) files
-
Device initialization from:
- PRD files
- Exported certificate (
bgroupcert.dat) - Private key (
zgpriv.dat)
-
Parsing and decoding of PlayReady PSSH containers
-
Extraction and processing of WRMHEADER data
-
Generation of license acquisition challenges (SOAP/XML)
-
Parsing of license responses
-
Extraction of content keys (KID / KEY pairs)
-
Support for Revocation Lists in license requests
-
Creation and customization of PlayReady headers
-
Integration with required cryptographic primitives
Requirements
- Python 3.9 or higher
pycryptodomeconstruct==2.8.8lxmlrequests
Install dependencies:
pip install pycryptodome construct==2.8.8 lxml requests
Note: This project is specifically tested with
construct==2.8.8. Newer versions may introduce breaking changes.
Usage
python pypr.py [options]
Examples
Using Exported Device Files
import requests
from pypr import PSSH, Device, Cdm, RevocationList
device = Device.from_files(
certificate="bgroupcert.dat",
key="zgpriv.dat",
)
cdm = Cdm.from_device(device)
session_id = cdm.open()
pssh = PSSH("AAADfHBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAA1xcAwAAAQABAFIDPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9"
"ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAA"
"NwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAw"
"ACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwA"
"RQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+"
"ADwASwBJAEQAPgA0AFIAcABsAGIAKwBUAGIATgBFAFMAOAB0AEcAawBOAEYAVwBUAEUASABBAD0APQA8AC8ASwBJAEQAPgA8AEMA"
"SABFAEMASwBTAFUATQA+AEsATABqADMAUQB6AFEAUAAvAE4AQQA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AEwAQQBfAFUAUgBMAD4AaAB0AHQAcABzADoALwAvAHAAcgBvAGYAZgBpAGMAaQBhAGwAcwBpAHQAZQAuAGsAZQB5AGQAZQBsAGkAdgBlAHIAeQAuAG0AZQBkAGkAYQBzAGUAcgB2AGkAYwBlAHMALgB3AGkAbgBkAG8AdwBzAC4AbgBlAHQALwBQAGwAYQB5AFIAZQBhAGQAeQAvADwALwBMAEEAXwBVAFIATAA+ADwAQwBVAFMAVABPAE0AQQBUAFQAUgBJAEIAVQBUAEUAUwA+ADwASQBJAFMAXwBEAFIATQBfAFYARQBSAFMASQBPAE4APgA4AC4AMQAuADIAMwAwADQALgAzADEAPAAvAEkASQBTAF8ARABSAE0AXwBWAEUAUgBTAEkATwBOAD4APAAvAEMAVQBTAFQATwBNAEEAVABUAFIASQBCAFUAVABFAFMAPgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA==")
try:
request = cdm.get_license_challenge(
session_id,
pssh.wrm_headers[0],
rev_lists=RevocationList.SupportedListIds,
)
response = requests.post(
url="https://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(persist:false,sl:2000)",
headers={"Content-Type": "text/xml; charset=UTF-8"},
data=request,
)
response.raise_for_status()
cdm.parse_license(session_id, response.text)
for key in cdm.get_keys(session_id):
print(f"{key.key_id.hex}:{key.key.hex()}")
finally:
cdm.close(session_id)
Using a PRD Device File
import requests
from pypr import PSSH, Device, Cdm, RevocationList
device = Device.load("funai_electric_tv_43pfl4901f8_sl2000.prd")
cdm = Cdm.from_device(device)
session_id = cdm.open()
pssh = PSSH("AAADfHBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAA1xcAwAAAQABAFIDPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9"
"ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAA"
"NwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAw"
"ACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwA"
"RQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+"
"ADwASwBJAEQAPgA0AFIAcABsAGIAKwBUAGIATgBFAFMAOAB0AEcAawBOAEYAVwBUAEUASABBAD0APQA8AC8ASwBJAEQAPgA8AEMA"
"SABFAEMASwBTAFUATQA+AEsATABqADMAUQB6AFEAUAAvAE4AQQA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AEwAQQBfAFUAUgBMAD4AaAB0AHQAcABzADoALwAvAHAAcgBvAGYAZgBpAGMAaQBhAGwAcwBpAHQAZQAuAGsAZQB5AGQAZQBsAGkAdgBlAHIAeQAuAG0AZQBkAGkAYQBzAGUAcgB2AGkAYwBlAHMALgB3AGkAbgBkAG8AdwBzAC4AbgBlAHQALwBQAGwAYQB5AFIAZQBhAGQAeQAvADwALwBMAEEAXwBVAFIATAA+ADwAQwBVAFMAVABPAE0AQQBUAFQAUgBJAEIAVQBUAEUAUwA+ADwASQBJAFMAXwBEAFIATQBfAFYARQBSAFMASQBPAE4APgA4AC4AMQAuADIAMwAwADQALgAzADEAPAAvAEkASQBTAF8ARABSAE0AXwBWAEUAUgBTAEkATwBOAD4APAAvAEMAVQBTAFQATwBNAEEAVABUAFIASQBCAFUAVABFAFMAPgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA==")
try:
request = cdm.get_license_challenge(
session_id,
pssh.wrm_headers[0],
rev_lists=RevocationList.SupportedListIds,
)
response = requests.post(
url="https://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(persist:false,sl:2000)",
headers={"Content-Type": "text/xml; charset=UTF-8"},
data=request,
)
response.raise_for_status()
cdm.parse_license(session_id, response.text)
for key in cdm.get_keys(session_id):
print(f"{key.key_id.hex}:{key.key.hex()}")
finally:
cdm.close(session_id)
Building a PlayReady Header
import base64
from pypr import PlayReadyHeaderBuilder
kid = "00000000000000000000000000000000"
builder = PlayReadyHeaderBuilder(kid)
header = builder.build_header(
version="4.0",
header_spec=None,
encryption_scheme="cenc",
key_specs=[(kid, kid)],
)
print("PlayReady Header:", base64.b64encode(header).decode())
Creating a PlayReady Header from KID
from pypr import create_playready_header_from_kid
kid = "00000000000000000000000000000000"
header = create_playready_header_from_kid(
kid=kid,
key=kid,
version="4.0",
encryption_scheme="cenc",
)
print("PlayReady Header:", header)
Architecture
The project is structured into distinct layers:
-
Device Layer Handles PRD parsing and credential loading (certificate chain and private key)
-
CDM Layer Manages session lifecycle, challenge generation, and license parsing
-
PSSH Layer Extracts WRMHEADER and initialization data
-
License Workflow
- Parse PSSH and extract WRMHEADER
- Generate license challenge (SOAP/XML)
- Send request to license server
- Parse license response
- Extract content keys
Disclaimer
This tool is intended strictly for educational, research, and interoperability purposes. The author does not support or encourage the misuse of DRM systems or unauthorized access to protected content.
Issues and Support
For bug reports, feature requests, or general inquiries, please open an issue in the repository.
Support and updates are provided as availability permits.
Acknowledgements
This project is based on pyplayready and Bento4, drawing inspiration from their implementations and design approaches.