早期優酷播放器採用 ActionScript 3 加密邏輯,透過 ASV 反編譯可還原核心演算法。本文記錄從逆向分析到 PHP 移植的完整過程。
getPlayList/VideoIDS/{id} 獲取 JSONseed、key1、key2、加密 fileidfileid 與 key<?php
class YoukuDecoder {
private $randomSeed = 0;
private $cgStr = '';
public function __construct($seed) {
$this->randomSeed = (int)$seed;
}
// 線性同餘產生器
private function ran() {
$this->randomSeed = (($this->randomSeed * 211) + 30031) % 65536;
return $this->randomSeed / 65536; // 返回 [0,1) 比例
}
// 重建亂序字串表 (CG混)
private function cgHun() {
$this->cgStr = '';
$sttext = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/:._-1234567890';
$len = strlen($sttext);
for ($i = 0; $i < $len; $i++) {
$cuch = (int)($this->ran() * strlen($sttext));
$this->cgStr .= $sttext[$cuch];
$sttext = str_replace($sttext[$cuch], '', $sttext);
}
}
// 解密 fileid
public function decode($string) {
$this->cgHun();
$output = '';
$expl = explode('*', $string);
for ($i = 0; $i < count($expl) - 1; $i++) {
$output .= $this->cgStr[(int)$expl[$i]];
}
return $output;
}
// 解密 key
public function decodeKey($key1, $key2) {
$key = hexdec($key1);
$key = $key ^ -1520786011; // 0xA55AA5A5 的有號整數表示
return $key2 . dechex($key);
}
}
// 使用流程
$baseUrl = 'https://v.youku.com/player/getPlayList/VideoIDS/';
$videoId = $_GET['vid'] ?? 'XMjY0ODE1MDA0';
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $baseUrl . $videoId,
CURLOPT_HEADER => 1,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_REFERER => 'https://v.youku.com/v_show/id_' . $videoId,
CURLOPT_USERAGENT => $_SERVER['HTTP_USER_AGENT'],
CURLOPT_NOBODY => 0,
]);
$content = curl_exec($ch);
curl_close($ch);
// 正則提取關鍵字段 (早期 JSON 格式)
preg_match('~"seed"\s*:\s*(\d+)\s*,~iUs', $content, $seed);
preg_match('~{\s*"(flv|mp4)"\s*:\s*"(.*)"\s*}~iUs', $content, $encoded);
preg_match('~"key1"\s*:\s*"(.*)"\s*,~iUs', $content, $key1);
preg_match('~"key2"\s*:\s*"(.*)"\s*,~iUs', $content, $key2);
$decoder = new YoukuDecoder($seed[1]);
$fileid = $decoder->decode($encoded[2]);
$key = $decoder->decodeKey($key1[1], $key2[1]);
// 組合地址
$s7 = substr($fileid, 10);
$s5 = substr($fileid, 0, 8);
$s6 = substr($fileid, 6, 2);
$s4 = '00'; // 分段號 (HEX)
$sid = time() . mt_rand(10, 99) . '1000' . mt_rand(30, 80) . '00';
$dAddr = 'https://f.youku.com/player/getFlvPath/sid/' . $sid . '_' . $s4
. '/st/' . $encoded[1]
. '/fileid/' . $fileid
. '?K=' . $key;
echo $dAddr;
對應 AS3 的 cg_hun 函數:
ran() 產生 [0,1) 隨機數,乘以池長度取索引// AS3 原始邏輯
var key:Number = hexdec(key1);
key = key ^ 0xA55AA5A5; // XOR 魔數
return key2 + dechex(key);
PHP 中 0xA55AA5A5 超過 32 位有號整數範圍,使用負數 -1520786011 等價處理。
streamfileids 多畫質結構,每段獨立 K 值// 1. 獲取播放清單
// 2. 解析 streamfileids: {"flv":"enc1","mp4":"enc2","hd2":"enc3"}
// 3. 各畫質獨立解密 fileid
// 4. 從 segs 獲取分段:[{no,size,seconds,k},...]
// 5. 每段組合 URL:.../fileid/{fileid}?K={段k值}
// 6. 串流播放需按順序請求各分段