PHP分段加密MP4适配HTTP Range请求问题求助
问题根源
你的加密操作改变了文件的逻辑字节映射关系:原文件前102400字节(逻辑长度)被加密成21528字节(物理长度),剩下的部分物理和逻辑长度一致。但带Range的脚本里,你依然用加密文件的物理字节数来处理客户端的Range请求(客户端请求的是解密后的逻辑字节位置),导致字节完全不匹配,解密失效。
必须修改的核心点
- 区分物理文件大小(加密后的实际文件大小)和逻辑文件大小(解密后的真实视频大小),响应头里必须返回逻辑大小。
- 把客户端请求的逻辑Range范围转换为加密文件的物理Range范围。
- 针对不同的Range请求位置,处理加密段的解密和字节截取。
修改后的可运行代码
<?php $video_img_root = ""; // 替换成你的实际路径 $file = $video_img_root . 'newfileMFIRST.mp4'; $content_type = "video/mp4"; // 定义加密段的关键参数 define('ENCRYPTED_PHYSICAL_LEN', 21528); // 加密后文件中第一段的物理字节数 define('DECRYPTED_LOGICAL_LEN', 102400); // 这段解密后的逻辑字节数 if (!file_exists($file)) { header("HTTP/1.1 404 Not Found"); echo "File does not exist"; exit; } $fp = @fopen($file, 'rb'); $physical_file_size = filesize($file); $logical_total_size = ($physical_file_size - ENCRYPTED_PHYSICAL_LEN) + DECRYPTED_LOGICAL_LEN; // 解密后的总逻辑大小 // 初始化Range参数(逻辑字节) $logical_start = 0; $logical_end = $logical_total_size - 1; $logical_length = $logical_total_size; header('Content-type: ' . $content_type); header("Accept-Ranges: bytes"); // 处理HTTP Range请求 if (isset($_SERVER['HTTP_RANGE'])) { list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2); if (strpos($range, ',') !== false) { header('HTTP/1.1 416 Requested Range Not Satisfiable'); header("Content-Range: bytes $logical_start-$logical_end/$logical_total_size"); exit; } $range_parts = explode('-', $range); $c_start = $range_parts[0] === '' ? ($logical_total_size - (int)$range_parts[1]) : (int)$range_parts[0]; $c_end = isset($range_parts[1]) && $range_parts[1] !== '' ? (int)$range_parts[1] : $logical_total_size - 1; // 验证Range是否合法 $c_end = min($c_end, $logical_end); if ($c_start > $c_end || $c_start >= $logical_total_size) { header('HTTP/1.1 416 Requested Range Not Satisfiable'); header("Content-Range: bytes $logical_start-$logical_end/$logical_total_size"); exit; } $logical_start = $c_start; $logical_end = $c_end; $logical_length = $logical_end - $logical_start + 1; header('HTTP/1.1 206 Partial Content'); } // 返回正确的逻辑Range响应头 header("Content-Range: bytes $logical_start-$logical_end/$logical_total_size"); header("Content-Length: " . $logical_length); $maxbuf = 100; $buffer = 1024 * $maxbuf; // 处理不同的Range请求情况 if ($logical_start < DECRYPTED_LOGICAL_LEN) { // 请求范围涉及加密段,先读取并解密加密段 fseek($fp, 0); $encrypted_data = fread($fp, ENCRYPTED_PHYSICAL_LEN); $decrypted_segment = secured_decrypt($encrypted_data); // 截取解密段中需要的部分 $segment_start = max(0, $logical_start); $segment_end = min(DECRYPTED_LOGICAL_LEN - 1, $logical_end); $output_segment = substr($decrypted_segment, $segment_start, $segment_end - $segment_start + 1); echo $output_segment; flush(); // 如果请求范围还包含非加密段的内容 if ($logical_end >= DECRYPTED_LOGICAL_LEN) { // 计算非加密段的物理起始位置和逻辑结束对应的物理位置 $physical_start = ENCRYPTED_PHYSICAL_LEN; $physical_end = ENCRYPTED_PHYSICAL_LEN + ($logical_end - DECRYPTED_LOGICAL_LEN); fseek($fp, $physical_start); while (!feof($fp) && ($p = ftell($fp)) <= $physical_end) { $current_buf = min($buffer, $physical_end - $p + 1); echo fread($fp, $current_buf); flush(); } } } else { // 请求范围完全在非加密段,直接转换物理位置读取 $physical_start = ENCRYPTED_PHYSICAL_LEN + ($logical_start - DECRYPTED_LOGICAL_LEN); $physical_end = ENCRYPTED_PHYSICAL_LEN + ($logical_end - DECRYPTED_LOGICAL_LEN); fseek($fp, $physical_start); while (!feof($fp) && ($p = ftell($fp)) <= $physical_end) { $current_buf = min($buffer, $physical_end - $p + 1); echo fread($fp, $current_buf); flush(); } } fclose($fp); exit; ?>
关键修改说明
- 逻辑/物理大小区分:新增
logical_total_size,这是解密后视频的真实大小,必须作为响应头里的总长度返回给客户端。 - Range转换:把客户端请求的逻辑字节位置,转换为加密文件的物理字节位置,比如逻辑位置>=102400时,物理位置=21528 + (逻辑位置-102400)。
- 加密段处理:只要请求范围涉及加密段的逻辑部分,就先解密整个加密段,再截取需要的部分输出,避免只读取部分加密数据导致解密失败(加密算法通常需要完整的加密块才能解密)。
- 响应头修正:
Content-Range和Content-Length都使用逻辑字节数据,确保客户端能正确解析Range请求。
内容的提问来源于stack exchange,提问作者Tom
相关产品推荐
相关产品推荐

