Files
2026-05-15 05:59:47 +02:00

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.10 or higher
  • pycryptodome
  • construct==2.8.8
  • lxml
  • requests

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

    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.