Files
pypr/README.md
T

239 lines
7.6 KiB
Markdown
Raw Normal View History

2026-04-24 07:52:20 +02:00
# pypr
2026-04-24 07:59:03 +02:00
**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
2026-05-15 05:59:47 +02:00
* Python 3.10 or higher
2026-04-24 07:59:03 +02:00
* `pycryptodome`
* `construct==2.8.8`
* `lxml`
* `requests`
Install dependencies:
```bash
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
```bash
python pypr.py [options]
```
---
## Examples
### Using Exported Device Files
```python
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
```python
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
```python
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
```python
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**
1. Parse PSSH and extract WRMHEADER
2. Generate license challenge (SOAP/XML)
3. Send request to license server
4. Parse license response
5. 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.