Files
pywv/README.md
T
2026-04-30 05:53:23 +02:00

5.7 KiB

pywv

pywv is a Python-based tool for parsing, analyzing, and interacting with Widevine (WVD) device files and DRM structures. It provides direct access to device data, cryptographic components, and license workflows for controlled inspection and processing.


Features

  • Parsing and loading of Widevine Device (WVD) files

  • Support for WVD version migration (v1 → v2)

  • Device loading from:

    • .wvd files
    • key + client_id blobs
    • device directories
  • Extraction of device attributes (system ID, security level)

  • Decoding of Widevine protobuf structures

  • Integration with cryptographic primitives (RSA, AES, CMAC, HMAC)

  • Parsing and manipulation of Widevine PSSH

  • License processing and content key extraction

  • Optional VMP (Verified Media Path) support

  • Built-in CDM logic (no external binaries required)


Requirements

  • Python 3.9 or higher
  • pycryptodome
  • construct
  • protobuf
  • pymp4
  • requests

Installation:

pip install pycryptodome construct protobuf pymp4 requests

Usage

python pywv.py <command> [options]

Commands

info

Inspect a device:

python pywv.py info --wvd device.wvd

Alternative input modes:

python pywv.py info --device-dir path/
python pywv.py info --key device_private_key --client-id device_client_id_blob

Optional:

--vmp device_vmp_blob

license

Execute the full license workflow (challenge generation, request submission, response parsing, key extraction):

python pywv.py license \
  --wvd device.wvd \
  --pssh BASE64_PSSH \
  --url LICENSE_URL

Using raw blobs:

python pywv.py license \
  --key device_private_key \
  --client-id device_client_id_blob \
  --pssh BASE64_PSSH \
  --url LICENSE_URL

Challenge-only mode:

python pywv.py license \
  --wvd device.wvd \
  --pssh BASE64_PSSH

create-wvd

Create a WVD file from raw device components:

python pywv.py create-wvd \
  --key device_private_key \
  --client-id device_client_id_blob \
  -o device.wvd

With VMP:

--vmp device_vmp_blob

export-wvd

Export a WVD file into raw device components:

python pywv.py export-wvd \
  --wvd device.wvd \
  -o output_dir

migrate-wvd

Migrate a WVD v1 file to WVD v2:

python pywv.py migrate-wvd \
  -i old.wvd \
  -o new.wvd

pssh

Inspect PSSH data:

python pywv.py pssh --pssh BASE64_PSSH

Rewrite key IDs:

python pywv.py pssh --pssh BASE64_PSSH --set-kid NEW_KID

Python Usage

Basic Usage

import requests
from pywv import PSSH, Device, Cdm

pssh = PSSH("AAAAW3Bzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADsIARIQ62dqu8s0Xpa7z2FmMPGj2hoNd2lkZXZpbmVfdGVzdCIQZmtqM2xqYVNkZmFsa3IzaioCSEQyAA==")

device = Device.load("device.wvd")

cdm = Cdm.from_device(device)
session_id = cdm.open()

try:
    challenge = cdm.get_license_challenge(session_id, pssh)

    response = requests.post(
        "https://cwip-shaka-proxy.appspot.com/no_auth",
        data=challenge,
    )
    response.raise_for_status()

    cdm.parse_license(session_id, response.content)

    for key in cdm.get_keys(session_id):
        print(f"[{key.type}] {key.kid.hex}:{key.key.hex()}")

finally:
    cdm.close(session_id)

Using Key / Certificate (no JSON)

import requests
from pathlib import Path
from pywv import PSSH, Device, Cdm

base = Path(__file__).resolve().parent

pssh = PSSH("AAAAW3Bzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADsIARIQ62dqu8s0Xpa7z2FmMPGj2hoNd2lkZXZpbmVfdGVzdCIQZmtqM2xqYVNkZmFsa3IzaioCSEQyAA==")

device = Device.from_files(
    certificate=base / "device_client_id_blob",
    key=base / "device_private_key",
    vmp=False
)

cdm = Cdm.from_device(device)
session_id = cdm.open()

try:
    challenge = cdm.get_license_challenge(session_id, pssh)

    response = requests.post(
        "https://cwip-shaka-proxy.appspot.com/no_auth",
        data=challenge,
    )
    response.raise_for_status()

    cdm.parse_license(session_id, response.content)

    for key in cdm.get_keys(session_id):
        print(f"[{key.type}] {key.kid.hex}:{key.key.hex()}")

finally:
    cdm.close(session_id)

Create WVD

from pywv import Device

device = Device.from_files(
    certificate="device_client_id_blob",
    key="device_private_key",
    vmp=False
)

device.dump("device.wvd")

Export WVD

from pathlib import Path
from pywv import Device

out = Path("exported")
out.mkdir(exist_ok=True)

device = Device.load("device.wvd")

(out / "device_private_key").write_bytes(
    device.private_key.export_key("DER")
)

(out / "device_client_id_blob").write_bytes(
    device.client_id.SerializeToString()
)

if device.client_id.vmp_data:
    (out / "device_vmp_blob").write_bytes(
        device.client_id.vmp_data
    )

Migrate WVD v1 → v2

from pywv import Device

with open("old.wvd", "rb") as f:
    data = f.read()

device = Device.migrate(data)
device.dump("new.wvd")

PSSH

from pywv import PSSH

pssh = PSSH("BASE64")

print(pssh.key_ids)

new_pssh = PSSH.new(
    system_id=PSSH.SystemId.Widevine,
    key_ids=pssh.key_ids,
    version=1
)

print(new_pssh.dumps())

Output

[CONTENT] kid:key

Disclaimer

This tool is intended strictly for educational, research, and interoperability purposes. The author does not support or encourage 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.


Acknowledgements

This project is based on pywidevine, adapted for standalone usage and improved WVD and PSSH workflows.