Hello peeps. Sorry If I can't explain this correctly.
My ISP Telekom has a streaming website (Magenta TV Go or something) where you can watch the local and not so local TV channels live 24/7. Even after the live ends, you can go back and watch previous hours of almost any of the TV channels, up to 7 days or so.
So let's say I want something that aired 10 hours ago, I just simply click the show I want and I can easily download it and decrypt it by getting the keys/commands with WidevineProxy extension.
Unfortunately, during the livestream (and also obviously afterwards when you can go back) the key changes every 12 hours or so.
So let's say the key changes 6pm/am. If the particular show/part I want to download aired between 06:01 am and 17:59 pm there's no trouble getting it, only 1 key, downloads and decrypts fine.
However, this particular show I want to download airs from 5pm to 8pm. In 6pm the key changes.
When I try to download with the first set of keys that are before the change, N_m3u8-RE downloads the 3 hours, decrypts it but when I play it, I can only see the decrypted content for the first hour (up until the key change in 6pm) and then the rest 2 hours are encrypted.
When I try to download with the 2nd set of keys that are after the change, N_m3u8-RE downloads, decrypts it but when I play it, everything is encrypted.
I tried to decrypt the stream with all of the keys, it doesn't work. Tried decrypting with both shaka and mp4decrypt, doesn't work.
What I find weird is that when the key changes, there doesn't seem to be an init file change or a manifest/mpd change.
When I download the init file from the beginning of the stream, concatenate it with a video segment from the first hour, then decrypt that small file with the first set of keys, it decrypts and plays fine.
When I try to concatenate a video segment that shows AFTER the key change to this init file, and try to decrypt it with any of the keys, it doesn't work.
I'm at a loss. Any help would be truly appreciated.
Have a great day.
+ Reply to Thread
Results 1 to 9 of 9
-
-
This sounds fun!
To debug this further, could you share both a segment before and after the key change and the init (and the keys)? Just changing decryption keys should be possible without a new init file.Bypass HMACs, One-time-tokens and Lic.Wrapping: https://github.com/DevLARLEY/WidevineProxy2 -
Hey larley, I like how this sounds fun to you yet brings a lot of frustration in me, haha.
You can get the files from here
https://www.swisstransfer.com/d/8b3a7f32-8e57-4761-adce-c63874508889 -
Ok, the solution is somewhat simple. All we need to do is switch out the key ID in the tenc box in the init:
https://www.swisstransfer.com/d/88b09935-0a33-48f9-934e-96634f0d7103
I'll get to writing a little python script for you.Bypass HMACs, One-time-tokens and Lic.Wrapping: https://github.com/DevLARLEY/WidevineProxy2 -
-
idk how I know this.
init always have the same discarded kid.
new segments have the newer kid when appended to init segment needs new keys.
I think N_m3u8DL-Re fails because it is appending each segment into one first segment.
when the keys change it needs to be appended into the init again.discord=notaghost9997 -
We take the the key ID from the moof/traf/sgpd SampleGroupDescriptionEntry of grouping_type "seig" and copy it to moov/trak/mdia/minf/stbl/stsd/{enc_sample_entry}/sinf/schi/tenc. That's all we need to change to re-use the init from the beginning of the stream.
Videos like these can be fixed with the script:
- init before the key change
- fragments after the key change
and they decrypt correctly.
But if we have a file like:
- init before key change
- fragments before key change
- init before key change
- fragments after key change
the script will still apply fixes like they should work but the resulting file is not decryptable. This may be caused by the fragments you provided not really coming after one another in the stream or just mp4decrypt/shaka-packager not supporting multiple inits per media file. So you'll need to still split the recorded video at the "key change boundary".
Usage:Code:import argparse from os import SEEK_CUR def peek_box(f) -> tuple[str, int]: box_size = int.from_bytes(f.read(4)) box_type = f.read(4).decode("latin-1") f.seek(-8, SEEK_CUR) return box_type, box_size def enter_box(f) -> int: start_pos = f.tell() box_size = int.from_bytes(f.read(4)) box_type = f.read(4).decode("latin-1") if box_size == 1: box_size = int.from_bytes(f.read(8)) if box_type == "uuid": f.seek(16, SEEK_CUR) special_containers = { "stsd": 4 + 4, "encv": 8 + 70, "enca": 8 + 20, "encm": 8, # enct, encu, encs "encf": 8, "encp": 8, "enc3": 8 + 32 } if box_type in special_containers: f.seek(special_containers[box_type], SEEK_CUR) return box_size - (f.tell() - start_pos) def edit_tenc(f, key_id: bytes) -> None: start_pos = f.tell() enter_box(f) f.seek(4, SEEK_CUR) f.seek(4, SEEK_CUR) f.write(key_id) f.seek(start_pos) def read_kid_sgpd_seig(f) -> bytes | None: start_pos = f.tell() enter_box(f) version = f.read(1)[0] f.seek(3, SEEK_CUR) grouping_type = f.read(4).decode("latin-1") if grouping_type != "seig": return None default_length = None if version >= 1: default_length = int.from_bytes(f.read(4)) if version >= 2: f.seek(4, SEEK_CUR) f.seek(4, SEEK_CUR) if version >= 1 and default_length == 0: f.seek(4, SEEK_CUR) # we only support a single SampleGroupDescriptionEntry byte1 = f.read(1)[0] multi_key_flag = (byte1 >> 7) & 0x01 f.seek(2, SEEK_CUR) if multi_key_flag > 0: f.seek(2, SEEK_CUR) # we only support a single key f.seek(1, SEEK_CUR) kid = f.read(16) f.seek(start_pos) return kid def find_box_iterative(f, target_box: str, max_depth: int = 128) -> int | None: iters = 0 while True: if iters >= max_depth: return None box_type, box_size = peek_box(f) if box_type == target_box: break f.seek(box_size, SEEK_CUR) iters += 1 return box_size def seek_to_box(f, path: str, max_depth: int = 128) -> dict[str, int] | None: containers = path.split("/") target_box = containers.pop(-1) # print("start:", containers, target_box) container_sizes = {} iters = 0 while len(containers) > 0: # print("containers", containers) if iters >= max_depth: return None box_type, box_size = peek_box(f) if box_type != containers[0]: f.seek(box_size, SEEK_CUR) else: container_sizes[box_type] = box_size # print(f.tell(), box_type) enter_box(f) containers.pop(0) iters += 1 find_box_iterative(f, target_box, max_depth) return container_sizes def fix_tenc(file: str, enc_sample_entry: str): with open(file, "r+b") as f: while True: moov_size = find_box_iterative(f, "moov", 128) if moov_size is None: break moov_pos = f.tell() f.seek(moov_size, SEEK_CUR) moof_sizes = seek_to_box(f, "moof/traf/sgpd") if moof_sizes is None: break key_id = read_kid_sgpd_seig(f) print("fixing key ID", key_id.hex()) f.seek(moov_pos) moov_sizes = seek_to_box(f, f"moov/trak/mdia/minf/stbl/stsd/{enc_sample_entry}/sinf/schi/tenc") if moov_sizes is None: break edit_tenc(f, key_id) f.seek(moov_pos + moov_sizes["moov"] + moof_sizes["moof"]) print("done") if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument("file", type=str) parser.add_argument("type", choices=["video", "audio"]) args = parser.parse_args() sample_entry = { "video": "encv", "audio": "enca" }[args.type.lower()] fix_tenc(args.file, sample_entry)
Code:usage: fix_tenc_stream.py [-h] file {video,audio} positional arguments: file {video,audio} options: -h, --help show this help message and exitBypass HMACs, One-time-tokens and Lic.Wrapping: https://github.com/DevLARLEY/WidevineProxy2
Similar Threads
-
Decrypting MPD videos without license url
By Sagnik in forum Video Streaming DownloadingReplies: 9Last Post: 19th May 2024, 06:55 -
help with new license server for get decryption keys. (i have cdm keys)
By savi4u in forum Video Streaming DownloadingReplies: 11Last Post: 11th May 2024, 03:30 -
Decrypting protected license url
By Mrachan in forum Video Streaming DownloadingReplies: 3Last Post: 17th Nov 2023, 12:42 -
Finding keys for an encrypted livestream
By samantas in forum Video Streaming DownloadingReplies: 8Last Post: 23rd Nov 2022, 20:28 -
decrypting the video with keys
By jamkiya in forum Video Streaming DownloadingReplies: 4Last Post: 30th Aug 2021, 02:11



Quote