You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP分段加密MP4适配HTTP Range请求问题求助

问题根源

你的加密操作改变了文件的逻辑字节映射关系:原文件前102400字节(逻辑长度)被加密成21528字节(物理长度),剩下的部分物理和逻辑长度一致。但带Range的脚本里,你依然用加密文件的物理字节数来处理客户端的Range请求(客户端请求的是解密后的逻辑字节位置),导致字节完全不匹配,解密失效。

必须修改的核心点
  1. 区分物理文件大小(加密后的实际文件大小)和逻辑文件大小(解密后的真实视频大小),响应头里必须返回逻辑大小。
  2. 把客户端请求的逻辑Range范围转换为加密文件的物理Range范围。
  3. 针对不同的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 20:04:53