本帖最后由 alexw 于 2026-1-20 20:46 编辑
<?php
// ysp.php - 精简版本
error_reporting(E_ALL);
ini_set('display_errors', 1);
class YSPCkeyGenerator {
private const DELTA = 0x9e3779b9;
private const ROUNDS = 16;
private const SALT_LEN = 2;
private const ZERO_LEN = 7;
private const TEA_CKEY = "\x59\xb2\xf7\xcf\x72\x5e\xf4\x3c\x34\xfd\xd7\xc1\x23\x41\x1e\xd3";
private const XOR_KEY = [0x84, 0x2E, 0xED, 0x08, 0xF0, 0x66, 0xE6, 0xEA, 0x48, 0xB4, 0xCA, 0xA9, 0x91, 0xED, 0x6F, 0xF3];
private const ALPHABET_MAP = [
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-='
];
private static function teaEncryptECB($data, $key) {
if (strlen($data) < 8) $data = str_pad($data, 8, "\x00");
$unpacked = unpack('N2', $data);
if (!$unpacked) return false;
list($y, $z) = array_values($unpacked);
$k = array_values(unpack('N4', $key));
$sum = 0;
for ($i = 0; $i < self::ROUNDS; $i++) {
$sum = ($sum + self::DELTA) & 0xFFFFFFFF;
$y = ($y + ((($z << 4) + $k[0]) ^ ($z + $sum) ^ (($z >> 5) + $k[1]))) & 0xFFFFFFFF;
$z = ($z + ((($y << 4) + $k[2]) ^ ($y + $sum) ^ (($y >> 5) + $k[3]))) & 0xFFFFFFFF;
}
return pack('NN', $y, $z);
}
private static function encryptBlock($src_buf, &$iv_plain, &$iv_crypt, $key, &$output) {
for ($j = 0; $j < 8; $j++) $src_buf[$j] ^= $iv_crypt[$j];
$encrypted = self::teaEncryptECB(pack('C8', ...$src_buf), $key);
if (!$encrypted) return false;
$tempBytes = array_values(unpack('C8', $encrypted));
for ($j = 0; $j < 8; $j++) $tempBytes[$j] ^= $iv_plain[$j];
$iv_plain = $src_buf;
$iv_crypt = $tempBytes;
$output .= pack('C8', ...$tempBytes);
return true;
}
private static function oi_symmetry_encrypt2($data, $key) {
$len = strlen($data);
$totalLen = $len + 1 + self::SALT_LEN + self::ZERO_LEN;
$padlen = $totalLen % 8 ? 8 - ($totalLen % 8) : 0;
$src_buf = array_fill(0, 8, 0);
$src_buf[0] = (random_int(0, 255) & 0xF8) | $padlen;
$src_i = 1;
while ($padlen--) $src_buf[$src_i++] = random_int(0, 255);
$iv_plain = $iv_crypt = array_fill(0, 8, 0);
$output = '';
// 加密盐值
for ($i = 0; $i < self::SALT_LEN; $i++) {
$src_buf[$src_i++] = random_int(0, 255);
if ($src_i == 8) {
if (!self::encryptBlock($src_buf, $iv_plain, $iv_crypt, $key, $output)) return false;
$src_i = 0;
}
}
// 加密数据
for ($i = 0; $i < $len; $i++) {
$src_buf[$src_i++] = ord($data[$i]);
if ($src_i == 8) {
if (!self::encryptBlock($src_buf, $iv_plain, $iv_crypt, $key, $output)) return false;
$src_i = 0;
}
}
// 加密零填充
for ($i = 0; $i < self::ZERO_LEN; $i++) {
$src_buf[$src_i++] = 0;
if ($src_i == 8) {
if (!self::encryptBlock($src_buf, $iv_plain, $iv_crypt, $key, $output)) return false;
$src_i = 0;
}
}
return $output;
}
private static function calcSignature($data) {
$signature = 0;
$len = strlen($data);
for ($i = 0; $i < $len; $i++) {
$signature = (0x83 * $signature + ord($data[$i])) & 0x7FFFFFFF;
}
return $signature;
}
private static function xorArray($data) {
$result = '';
$len = strlen($data);
for ($i = 0; $i < $len; $i++) {
$result .= chr(ord($data[$i]) ^ self::XOR_KEY[$i & 0xF]);
}
return $result;
}
private static function customEncode($data) {
return rtrim(strtr(base64_encode($data), self::ALPHABET_MAP[0], self::ALPHABET_MAP[1]), '=');
}
private static function createStrData($value) {
$value = (string)$value;
return pack('n', strlen($value)) . $value;
}
private static function uuid4() {
$data = random_bytes(16);
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
public static function ckey42($platform, $timestamp, $sdtfrom, $vid = "600002264", $guid, $appVer) {
//$guid = bin2hex(random_bytes(16));
//$guardTime = bin2hex(random_bytes(66));
//$uid = bin2hex(random_bytes(8));
//$vid = "600002264";
//$uuid4 = self::uuid4();
$data = "\x00\x00\x00\x42\x00\x00\x00\x04\x00\x00\x04\xd2" .
pack('N', (int)$platform) .
"\x00\x00\x00\x00" .
pack('N', $timestamp) .
self::createStrData("fcgo") .// $sdtfrom
self::createStrData(base64_encode(random_bytes(18))) .
self::createStrData($appVer) .
self::createStrData($vid) .
self::createStrData(bin2hex(random_bytes(16))) .// $guid
pack('NN', 1, 1) .
self::createStrData(bin2hex(random_bytes(8))) .// $uid
self::createStrData("nil") .
self::createStrData(self::uuid4()) .
self::createStrData(bin2hex(random_bytes(66)));// $guardTime
$buffer = pack('n', strlen($data)) . $data;
$encryptData = self::oi_symmetry_encrypt2($buffer, self::TEA_CKEY);
if (!$encryptData) return false;
$encryptData .= pack('N', self::calcSignature($buffer));
return "--01" . self::customEncode(self::xorArray($encryptData));
}
}
function handleYspRequest() {
header('Content-Type: application/json; charset=utf-8');
if (!isset($_GET['cnlid']) || !isset($_GET['livepid'])) {
http_response_code(400);
echo json_encode(['error' => '缺少必要参数'], JSON_UNESCAPED_UNICODE);
return;
}
try {
$params = [
"atime" => "120",
"livepid" => $_GET['livepid'],
"cnlid" => $_GET['cnlid'],
"appVer" => "V3.2.2.23730",
"app_version" => "322237",
"caplv" => "1",
"cmd" => "2",
"defn" => $_GET['defn'] ?? 'fhd',
"device" => "iPhone",
"encryptVer" => "4.2",
"getpreviewinfo" => "0",
"hevclv" => "33",
"lang" => "zh-Hans_JP",
"livequeue" => "0",
"logintype" => "1",
"nettype" => "1",
"newnettype" => "1",
"newplatform" => "4330403",
"platform" => "4330403",
"playbacktime" => "0",
"sdtfrom" => "fcgo",
"spacode" => "23",
"spaudio" => "1",
"spdemuxer" => "6",
"spdrm" => "2",
"spdynamicrange" => "7",
"spflv" => "1",
"spflvaudio" => "1",
"sphdrfps" => "60",
"sphttps" => "0",
"spvcode" => "MSgzMDoyMTYwLDYwOjIxNjB8MzA6MjE2MCw2MDoyMTYwKTsyKDMwOjIxNjAsNjA6MjE2MHwzMDoyMTYwLDYwOjIxNjAp",
"spvideo" => "4",
"stream" => "1",
"system" => "1",
"sysver" => "ios18.2.1",
"uhd_flag" => "4",
];
$ckey = YSPCkeyGenerator::ckey42(
$params['platform'],
time(),
//'dcgh',
'fcgo',
$params['cnlid'],
bin2hex(random_bytes(16)),
$params['appVer']
);
if (!$ckey) throw new Exception("CKEY生成失败");
$params['cKey'] = $ckey;
$url = "https://liveinfo.ysp.cctv.cn?" . http_build_query($params);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['User-Agent: qqlive', 'Connection: Keep-Alive'],
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_TIMEOUT => 30
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) throw new Exception("HTTP请求失败: {$httpCode}");
$data = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) throw new Exception("JSON解析失败");
if ($playurl = $data['playurl'] ?? '') {
header("Location: " . $playurl);
exit;
}
throw new Exception("未找到播放地址");
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()], JSON_UNESCAPED_UNICODE);
}
}
// 主入口
if (isset($_GET['cnlid']) && isset($_GET['livepid'])) {
handleYspRequest();
} else {
echo "<h1>央视影音API接口</h1><p>使用方法: ?cnlid=频道ID&livepid=直播ID&defn=分辨率</p>";
}
?>
|