VideoHelp Forum



Support our site by donate $5 directly to us Thanks!!!

Try StreamFab Downloader and download streaming video from Netflix, Amazon!



+ Reply to Thread
Results 1 to 9 of 9
  1. Hi everyone,

    I'm working on a Python script to retrieve Widevine keys from a VdoCipher stream. The script seems to work well up to a certain point: it successfully parses the player URL, finds the MPD and the PSSH.

    However, when I send the POST request to the license server (https://license.vdocipher.com/auth), I consistently get a 403 Forbidden error with the message: {"code":2048,"message":"Authentication failed"}.

    I've tried to structure the final JSON payload and headers to mimic what a browser would send, but I must be missing something. I've confirmed that the player URL I'm using as input is active and works correctly when opened in a browser.

    Any help in figuring out what's wrong with my license request would be greatly appreciated.


    I'm using a real WVD, extracted from a device.


    Code:
    https://player.vdocipher.com/v2/?otp=20160313versUSE32305v64nCQtNBmVl8bAvs0U57QsEOH1IkKIADm3TfM6vJ5FT&playbackInfo=eyJ2aWRlb0lkIjoiMGQ4YzczYmYzODBhNDE5NWEyYTZiMjk3ZWZjNzhjNjEifQ%3D%3D&player=r36ybRfkhOyoRUZa&primaryColor=7a2940

    Code:
    # Arquivo: from pywidevine.py (VERSÃO FINAL COM PAYLOAD CORRETO)
    from pywidevine.device import Device
    from pywidevine.pssh import PSSH
    from pywidevine.cdm import Cdm
    from bs4 import BeautifulSoup
    from urllib.parse import urlparse, parse_qs
    import requests
    import base64
    import json
    import re
    import sys
    
    # Lembre-se de manter o caminho com a barra inicial
    wv_CDM = "/content/samsung_sm-a556e_18.0.0@342415000_2305dd0b_34137_l3.wvd"
    
    input_url = input('Cole a URL COMPLETA do player (a que funciona no navegador): ')
    
    # Strip leading/trailing whitespace from the input URL
    input_url = input_url.strip()
    
    # Replace & with & in the input URL
    input_url = input_url.replace('&', '&')
    
    try:
        parsed_url = urlparse(input_url)
        query_params = parse_qs(parsed_url.query)
        otp = query_params.get('otp', [None])[0]
        playback_info = query_params.get('playbackInfo', [None])[0]
        if not otp or not playback_info:
            raise ValueError("A URL não contém 'otp' ou 'playbackInfo'")
    except Exception as e:
        print(f"\n[ERRO FATAL] A URL fornecida é inválida ou está incompleta: {e}")
        sys.exit()
    
    # Add print statements to inspect otp and playback_info
    print(f"\n[DEBUG] Extracted OTP: {otp}")
    print(f"[DEBUG] Extracted PlaybackInfo: {playback_info}")
    
    
    iframe_url = input_url
    
    headers_player = {
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
        'Accept-Language': 'pt-BR,pt;q=0.8,en-US;q=0.5,en;q=0.3',
        'Connection': 'keep-alive',
        'Host': 'player.vdocipher.com',
        'Referer': iframe_url,
        'Sec-Fetch-Dest': 'iframe',
        'Sec-Fetch-Mode': 'navigate',
        'Sec-Fetch-Site': 'cross-site',
        'Upgrade-Insecure-Requests': '1',
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:100.0) Gecko/20100101 Firefox/100.0',
    }
    
    print(f"\n[INFO] Buscando player: {iframe_url}")
    try:
        response = requests.get(iframe_url, headers=headers_player)
        response.raise_for_status()
        iframe = response.content.decode()
    except requests.exceptions.RequestException as e:
        print(f"\n[ERRO DE REDE] Falha ao acessar a URL do player: {e}")
        sys.exit()
    
    soup = BeautifulSoup(iframe, 'html.parser')
    script_tag = soup.find('script', {'type': 'application/json'})
    
    if script_tag is None:
        print("\n[ERRO FATAL] Não foi possível encontrar os metadados do vídeo.")
        print("Causa Probável: A URL/OTP expirou. Tente com uma nova URL.")
        sys.exit()
    
    mpd_path = json.loads(script_tag.string)
    mpd_link = mpd_path['dash']['manifest']
    
    try:
        get_pssh = re.search('<cenc:pssh>(.*)</cenc:pssh>', requests.get(mpd_link).text).group(1)
    except (AttributeError, requests.exceptions.RequestException):
        print(f"\n[ERRO FATAL] Não foi possível encontrar o PSSH no MPD: {mpd_link}")
        sys.exit()
    
    print('\n--- INFORMAÇÕES ENCONTRADAS ---')
    print('PSSH:', get_pssh)
    print(f'MPD: {mpd_link}\n')
    
    # --- DADOS PARA O PEDIDO DE LICENÇA CORRIGIDOS ---
    # Adicionamos 'href' e 'tech' para uma autorização válida
    json_data = {
        "otp": otp,
        "playbackInfo": playback_info,
        "href": input_url,
        "tech": "wv"
    }
    # --- FIM DA CORREÇÃO ---
    
    pssh = PSSH(get_pssh)
    device = Device.load(wv_CDM)
    cdm = Cdm.from_device(device)
    session_id = cdm.open()
    challenge = cdm.get_license_challenge(session_id, pssh)
    
    json_data["licenseRequest"] = base64.b64encode(challenge).decode("utf-8")
    
    # Encode the entire json_data object and put it under the 'token' key - Reverting to this structure based on the "request unwrapping failed" error and re-examining the likely correct structure
    token_payload = {"token": base64.b64encode(json.dumps(json_data).encode("utf-8")).decode("utf-8")}
    
    
    headers_license = {
        'Accept': '*/*',
        'Accept-Language': 'pt-BR,pt;q=0.8,en-US;q=0.5,en;q=0.3',
        'content-type': 'application/json',
        'Host': 'license.vdocipher.com',
        'Origin': 'https://player.vdocipher.com',
        'Referer': iframe_url,
        'Sec-Fetch-Dest': 'empty',
        'Sec-Fetch-Mode': 'cors',
        'Sec-Fetch-Site': 'same-site',
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:100.0) Gecko/100101 Firefox/100.0', # Corrected User-Agent typo
        'vdo-sdk': 'VdoWeb/2.5.2'
    }
    
    print("[INFO] Solicitando licença...")
    # Sending json_data directly as the request body
    response = requests.post("https://license.vdocipher.com/auth", headers=headers_license, json=token_payload)
    if not response.ok:
        print(f'ERRO: {response}\n{response.text}')
        cdm.close(session_id)
        sys.exit()
    
    print("\n[SUCCESS] Licença recebida! Obtendo chaves...")
    license = base64.b64decode(response.json()["license"])
    cdm.parse_license(session_id, license)
    
    for key in cdm.get_keys(session_id):
        if key.type != 'SIGNING':
            print(f"✅ [KEY]: {key.kid.hex}:{key.key.hex()}")
    
    cdm.close(session_id)
    Quote Quote  
  2. Recently, VdoCipher started showing error 2074 (Please open in Android app) to license requests from Android CDMs, even in real browsers with OpenWV or Vineless. Probably makes no sense without something to do about integrity tokens.
    Quote Quote  
  3. got same error
    Quote Quote  
  4. just compare a license request (their DrmCertificate) from firefox with one from chrome (both on android). That's probably the reason why firefox doesn't work.

    Firefox:
    Code:
    type: DEVICE
    serial_number: "\261\332:\220\02...247]\200T"
    creation_time_seconds: 1716207363
    public_key: "0\202\001\n\002\202\00...2\000\037\002\003\001\000\001"
    system_id: 8159
    algorithm: RSA
    Chrome:
    Code:
    type: DEVICE
    serial_number: "\354\274d|.\200...316\246\214-"
    creation_time_seconds: 1754775788
    public_key: "0\202\001\n\002...251\334\227{\002\003\001\000\001"
    system_id: 8159
    algorithm: RSA
    rot_id {
      version: ROOT_OF_TRUST_ID_VERSION_1
      key_id: 0
      encrypted_unique_id: "\004\3225t\363p\022\...5-{\003"
      unique_id_hash: "\035\270\343\366\...;\275:\226\364\\U\226"
    }
    Bypass HMACs, One-time-tokens and Lic.Wrapping: https://github.com/DevLARLEY/WidevineProxy2
    Quote Quote  
  5. hmm, if you provision Chrome again it stops working even though there's still a RootOfTrust Id present. maybe they're checking the device age?
    Bypass HMACs, One-time-tokens and Lic.Wrapping: https://github.com/DevLARLEY/WidevineProxy2
    Quote Quote  
  6. I think they are checking the provision date rather than the device age.
    It would be good security on their part to block newly provisioned devices, which prevents new dev keyboxes/certificates from being used.
    discord=notaghost9997
    Quote Quote  
  7. that's what I meant actually.
    After doing some testing it turns out that they weren't using blocking newly provisioned devices (even though that would've made a lot of sense). Weirdly, the solution was to change my IP after provisioning my device again (by re-installing Chrome).
    Take this diff of a challenge generated for a normal DRM site using shaka-player without any special settings and one token from VdoCipher. VdoCipher seems to not have their own License Servers since they're using the license.widevine.com certificate from Google, which means that they should be limited to what the official WV License Server responds with.
    My phone with a Xiaomi Mi 9T (aka. Redmi K20) with working L1 support. Chrome seems to be able to use both my L3 Generic CDM (System ID 8159) and the L1 CDM (System ID 13398). I'm still investigating how this is chosen because debugging web pages/installing Add-Ons isn't that easy on Android.

    https://www.diffchecker.com/SB7KhtZZ/


    Update:

    VdoCipher seems to request a video robustness of HW_SECURE_ALL in Chrome, which will use my L1 CDM. This doesn't work in Firefox for some reason (EME Logger shows an error was returned when requesting MediaKeySystemAccess)
    Last edited by larley; 13th Sep 2025 at 11:30.
    Bypass HMACs, One-time-tokens and Lic.Wrapping: https://github.com/DevLARLEY/WidevineProxy2
    Quote Quote  
  8. Better report it to the Firefox android app developers.

    https://github.com/mozilla-mobile/firefox-android/wiki#upcoming-migration-to-mozilla-central
    https://hg-edge.mozilla.org/mozilla-central


    But then again it’ll not change much if the video needs L1.
    discord=notaghost9997
    Quote Quote  



Similar Threads

Visit our sponsor! Try DVDFab and backup Blu-rays!