4.0 KiB
pywv
pywv is a Python-based tool for parsing, analyzing, and interacting with Widevine (WVD) device files and associated DRM structures. It provides low-level access to device data, cryptographic components, and license-related metadata, enabling precise inspection and controlled handling of Widevine workflows.
Features
- Parsing and loading of Widevine Device (WVD) files
- Support for WVD version migration (v1 → v2)
- Extraction of device attributes (system ID, security level, client identification)
- Decoding of Widevine protobuf structures (ClientIdentification, certificates, licenses)
- Integration with cryptographic primitives (RSA, AES, CMAC, HMAC)
- Parsing and manipulation of PSSH (Widevine / PlayReady) containers
- License processing and content key extraction via CDM workflows
Requirements
- Python 3.9 or higher
pycryptodomeconstructprotobufpymp4requests
Installation:
pip install pycryptodome construct protobuf pymp4 requests
Usage
python pywv.py [options]
Examples
Basic Usage
import requests
from pywv import PSSH, Device, Cdm
pssh = PSSH("AAAAW3Bzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADsIARIQ62dqu8s0Xpa7z2FmMPGj2hoNd2lkZXZpbmVfdGVzdCIQZmtqM2xqYVNkZmFsa3IzaioCSEQyAA==")
device = Device.load("xiaomi_redmi_note_4x_15.0.0_e15a9e5f_8159_l3.wvd")
cdm = Cdm.from_device(device)
session_id = cdm.open()
try:
challenge = cdm.get_license_challenge(session_id, pssh)
license_response = requests.post(
"https://cwip-shaka-proxy.appspot.com/no_auth",
data=challenge,
)
license_response.raise_for_status()
cdm.parse_license(session_id, license_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 JSON Device Configuration
# wv.json example:
# {
# "security_level": 3,
# "session_id_type": "android",
# "private_key_available": true,
# "vmp": false
# }
import json
import requests
from pathlib import Path
from pywv import PSSH, Device, Cdm
base_path = Path(__file__).resolve().parent
with open(base_path / "wv.json", "r", encoding="utf-8") as f:
wv = json.load(f)
pssh = PSSH("AAAAW3Bzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADsIARIQ62dqu8s0Xpa7z2FmMPGj2hoNd2lkZXZpbmVfdGVzdCIQZmtqM2xqYVNkZmFsa3IzaioCSEQyAA==")
if not wv.get("private_key_available", True):
raise ValueError("This device profile does not include a private key")
device_kwargs = {
"private_key": base_path / "device_private_key",
"client_id": base_path / "device_client_id_blob",
"type_": wv.get("session_id_type", "ANDROID").upper(),
"security_level": wv.get("security_level", 3),
}
vmp_path = base_path / "device_vmp_blob"
if wv.get("vmp", False) and vmp_path.exists():
device_kwargs["vmp"] = vmp_path
device = Device.from_files(**device_kwargs)
cdm = Cdm.from_device(device)
session_id = cdm.open()
try:
challenge = cdm.get_license_challenge(session_id, pssh)
license_response = requests.post(
"https://cwip-shaka-proxy.appspot.com/no_auth",
data=challenge,
)
license_response.raise_for_status()
cdm.parse_license(session_id, license_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)
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 will be provided as availability permits.
Acknowledgements
This project is based on pywidevine, with modifications and restructuring to support standalone usage and extended functionality for working with WVD and PSSH data.