Files
WeChat-Channels-Video-File-…/index.html
2025-10-15 06:36:51 -07:00

1259 lines
49 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- SEO 优化 -->
<title>微信视频号解密工具 - 在线解密 WeChat Channels Video | Isaac64 WASM</title>
<meta name="description" content="免费在线微信视频号视频解密工具,基于 Isaac64 PRNG 算法和官方 WASM 模块支持浏览器内一键解密无需上传保护隐私。WeChat Channels Video Decryption Tool.">
<meta name="keywords" content="微信视频号,视频解密,WeChat Channels,Isaac64,WASM,在线解密工具,视频号下载,微信视频解密">
<meta name="author" content="Evil0ctal">
<meta name="robots" content="index, follow">
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:url" content="https://evil0ctal.github.io/WeChat-Channels-Video-File-Decryption/">
<meta property="og:title" content="微信视频号解密工具 - 在线解密 WeChat Channels Video">
<meta property="og:description" content="免费在线微信视频号视频解密工具,基于 Isaac64 算法,支持浏览器内一键解密,保护隐私安全">
<meta property="og:image" content="https://evil0ctal.github.io/WeChat-Channels-Video-File-Decryption/preview.png">
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image">
<meta property="twitter:url" content="https://evil0ctal.github.io/WeChat-Channels-Video-File-Decryption/">
<meta property="twitter:title" content="微信视频号解密工具">
<meta property="twitter:description" content="免费在线微信视频号视频解密工具">
<!-- Canonical URL -->
<link rel="canonical" href="https://evil0ctal.github.io/WeChat-Channels-Video-File-Decryption/">
<!-- 结构化数据 (Schema.org) -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "微信视频号解密工具",
"applicationCategory": "MultimediaApplication",
"operatingSystem": "Web Browser",
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "CNY"
},
"author": {
"@type": "Person",
"name": "Evil0ctal",
"url": "https://github.com/Evil0ctal"
},
"description": "基于 Isaac64 PRNG 算法的微信视频号加密视频在线解密工具",
"softwareVersion": "2.0",
"datePublished": "2025-01-15"
}
</script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
line-height: 1.6;
color: #333;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1000px;
margin: 0 auto;
background: white;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
text-align: center;
}
.header h1 {
font-size: 28px;
margin-bottom: 10px;
}
.header p {
font-size: 14px;
opacity: 0.9;
}
.content {
padding: 30px;
}
.section {
margin-bottom: 30px;
}
.section h2 {
font-size: 18px;
margin-bottom: 15px;
color: #667eea;
border-bottom: 2px solid #f0f0f0;
padding-bottom: 10px;
}
.input-group {
margin-bottom: 20px;
}
.input-group label {
display: block;
font-size: 14px;
font-weight: 500;
margin-bottom: 8px;
color: #555;
}
.input-group input[type="text"],
.input-group input[type="file"] {
width: 100%;
padding: 12px;
font-size: 16px;
border: 2px solid #e0e0e0;
border-radius: 6px;
transition: border-color 0.3s;
}
.input-group input[type="text"] {
font-family: 'Monaco', 'Menlo', monospace;
}
.input-group input:focus {
outline: none;
border-color: #667eea;
}
.file-upload-area {
border: 2px dashed #667eea;
border-radius: 8px;
padding: 30px;
text-align: center;
background: #f8f9fa;
cursor: pointer;
transition: all 0.3s;
}
.file-upload-area:hover {
background: #e9ecef;
border-color: #764ba2;
}
.file-upload-area.drag-over {
background: #667eea;
color: white;
}
.file-info {
margin-top: 10px;
padding: 10px;
background: #e7f3ff;
border-radius: 6px;
font-size: 14px;
color: #0066cc;
}
.button-group {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
button {
padding: 12px 24px;
font-size: 14px;
font-weight: 500;
border: none;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s;
display: flex;
align-items: center;
gap: 8px;
}
button:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-success {
background: #28a745;
color: white;
}
.btn-warning {
background: #ff9800;
color: white;
}
.btn-secondary {
background: #6c757d;
color: white;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
.progress-container {
display: none;
margin: 20px 0;
padding: 15px;
background: #f8f9fa;
border-radius: 6px;
}
.progress-bar {
width: 100%;
height: 30px;
background: #e0e0e0;
border-radius: 15px;
overflow: hidden;
position: relative;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
transition: width 0.3s;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 600;
font-size: 12px;
}
.progress-text {
margin-top: 8px;
font-size: 13px;
color: #666;
text-align: center;
}
#output {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 6px;
padding: 20px;
min-height: 200px;
max-height: 600px;
overflow-y: auto;
font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
font-size: 13px;
line-height: 1.6;
white-space: pre-wrap;
word-break: break-all;
}
#output:empty::before {
content: '等待操作...';
color: #999;
font-style: italic;
}
.success {
color: #28a745;
font-weight: 600;
}
.error {
color: #dc3545;
font-weight: 600;
}
.info {
color: #0066cc;
}
.warning {
color: #ff9800;
}
.tabs {
display: flex;
border-bottom: 2px solid #e0e0e0;
margin-bottom: 20px;
}
.tab {
padding: 12px 24px;
cursor: pointer;
border: none;
background: none;
font-size: 15px;
color: #666;
border-bottom: 3px solid transparent;
transition: all 0.3s;
}
.tab:hover {
color: #667eea;
}
.tab.active {
color: #667eea;
border-bottom-color: #667eea;
font-weight: 600;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.tech-details {
background: #f8f9fa;
border-left: 4px solid #667eea;
padding: 15px 20px;
margin-top: 20px;
border-radius: 4px;
}
.tech-details h3 {
font-size: 16px;
color: #667eea;
margin-bottom: 10px;
}
.tech-details ul {
margin: 10px 0;
padding-left: 20px;
}
.tech-details li {
margin: 5px 0;
font-size: 13px;
color: #555;
}
.tech-details code {
background: #e9ecef;
padding: 2px 6px;
border-radius: 3px;
font-family: 'Monaco', 'Menlo', monospace;
font-size: 12px;
}
.sponsor {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
border-radius: 8px;
margin-top: 20px;
text-align: center;
}
.sponsor h3 {
font-size: 18px;
margin-bottom: 10px;
}
.sponsor p {
font-size: 14px;
margin: 8px 0;
opacity: 0.95;
}
.sponsor a {
color: white;
text-decoration: none;
font-weight: 600;
border-bottom: 2px solid rgba(255, 255, 255, 0.5);
transition: border-color 0.3s;
}
.sponsor a:hover {
border-bottom-color: white;
}
.author-info {
background: white;
border: 2px solid #e0e0e0;
border-radius: 8px;
padding: 15px 20px;
margin-top: 20px;
display: flex;
align-items: center;
gap: 15px;
}
.author-info .avatar {
font-size: 40px;
}
.author-info .details h4 {
font-size: 16px;
margin-bottom: 5px;
color: #333;
}
.author-info .details p {
font-size: 13px;
color: #666;
margin: 3px 0;
}
.author-info .details a {
color: #667eea;
text-decoration: none;
}
.author-info .details a:hover {
text-decoration: underline;
}
.footer {
background: #f8f9fa;
padding: 20px;
text-align: center;
font-size: 13px;
color: #666;
border-top: 1px solid #dee2e6;
}
.footer a {
color: #667eea;
text-decoration: none;
}
.footer a:hover {
text-decoration: underline;
}
@media (max-width: 768px) {
.container {
margin: 10px;
}
.header h1 {
font-size: 22px;
}
.button-group {
flex-direction: column;
}
button {
width: 100%;
justify-content: center;
}
.tabs {
flex-wrap: wrap;
}
.tab {
flex: 1 1 50%;
min-width: 120px;
}
}
</style>
</head>
<body>
<div class="container">
<header class="header">
<h1>🔐 微信视频号解密工具</h1>
<p>基于 Isaac64 WASM 的在线解密工具 - 完全浏览器内处理,保护隐私</p>
</header>
<main class="content">
<!-- 标签页 -->
<div class="tabs">
<button class="tab active" onclick="switchTab('decrypt')">
🎬 一键解密
</button>
<button class="tab" onclick="switchTab('keystream')">
🔑 仅生成密钥流
</button>
</div>
<!-- 一键解密标签页 -->
<div id="decrypt-tab" class="tab-content active">
<div class="section">
<h2>步骤 1输入解密密钥</h2>
<div class="input-group">
<label for="decodeKeyFull">Decode Key从 API 响应中获取)</label>
<input
type="text"
id="decodeKeyFull"
placeholder="例如2136343393"
value=""
>
<div style="margin-top: 8px; padding: 10px; background: #fff3cd; border-left: 4px solid #ffc107; border-radius: 4px; font-size: 13px; color: #856404;">
<strong>⚠️ 重要提示:</strong> 微信接口每次请求都会返回新的加密文件链接和 decode_key即使是同一个视频。请确保使用的 decode_key 与下载的加密视频文件是同一次 API 响应中获取的,否则解密将会失败。
</div>
</div>
</div>
<div class="section">
<h2>步骤 2选择加密视频</h2>
<div class="file-upload-area" id="dropArea" onclick="document.getElementById('videoInput').click()">
<div style="font-size: 48px; margin-bottom: 10px;">📹</div>
<div style="font-size: 16px; font-weight: 500; margin-bottom: 5px;">
点击选择或拖放加密视频文件
</div>
<div style="font-size: 13px; color: #666;">
支持 .mp4 格式,文件不会上传到服务器
</div>
</div>
<input
type="file"
id="videoInput"
accept="video/*"
style="display: none;"
onchange="handleFileSelect(event)"
>
<div id="fileInfo" class="file-info" style="display: none;"></div>
</div>
<div class="section">
<h2>步骤 3开始解密</h2>
<div class="button-group">
<button class="btn-primary" onclick="decryptVideo()" id="decryptBtn" disabled>
<span>🚀</span>
<span>开始解密</span>
</button>
<button class="btn-success" onclick="downloadDecrypted()" id="downloadBtn" disabled>
<span>💾</span>
<span>下载解密视频</span>
</button>
<button class="btn-secondary" onclick="clearAll()">
<span>🗑️</span>
<span>清空重置</span>
</button>
</div>
<!-- 进度条 -->
<div id="progressContainer" class="progress-container">
<div class="progress-bar">
<div id="progressFill" class="progress-fill" style="width: 0%">0%</div>
</div>
<div id="progressText" class="progress-text">准备中...</div>
</div>
</div>
</div>
<!-- 仅生成密钥流标签页 -->
<div id="keystream-tab" class="tab-content">
<div class="section">
<h2>生成密钥流文件</h2>
<div class="input-group">
<label for="decodeKey">Decode Key解密密钥</label>
<input
type="text"
id="decodeKey"
placeholder="请输入从 API 响应中获取的 decode_key"
value=""
>
<div style="margin-top: 8px; padding: 10px; background: #fff3cd; border-left: 4px solid #ffc107; border-radius: 4px; font-size: 13px; color: #856404;">
<strong>⚠️ 重要提示:</strong> 微信接口每次请求都会返回新的加密文件链接和 decode_key即使是同一个视频。请确保使用的 decode_key 与下载的加密视频文件是同一次 API 响应中获取的,否则解密将会失败。
</div>
</div>
<div class="button-group">
<button class="btn-primary" onclick="generateKeystream()">
<span>🚀</span>
<span>生成密钥流</span>
</button>
<button class="btn-success" onclick="exportKeystream()" id="exportBtn" disabled>
<span>💾</span>
<span>导出密钥流</span>
</button>
<button class="btn-secondary" onclick="clearOutput()">
<span>🗑️</span>
<span>清空日志</span>
</button>
</div>
</div>
</div>
<!-- 操作日志 -->
<div class="section">
<h2>操作日志</h2>
<div id="output"></div>
</div>
<!-- 技术细节 -->
<div class="section">
<div class="tech-details">
<h3>🔧 技术细节</h3>
<ul>
<li><strong>加密算法</strong>Isaac64 PRNG密码学安全的伪随机数生成器</li>
<li><strong>实现方式</strong>:使用微信官方 WASM 模块(<code>wasm_video_decode.wasm</code></li>
<li><strong>加密范围</strong>:仅加密视频前 131,072 bytes128 KB</li>
<li><strong>解密方式</strong>XOR 运算(<code>decrypted = encrypted ^ keystream</code></li>
<li><strong>关键步骤</strong>:密钥流必须经过 <code>reverse()</code> 操作</li>
<li><strong>隐私保护</strong>:所有操作在浏览器本地完成,不上传任何数据</li>
</ul>
</div>
</div>
<!-- 赞助商 -->
<div class="section">
<div class="sponsor">
<h3>🚀 项目赞助方</h3>
<p>本项目由 <a href="https://tikhub.io" target="_blank" rel="noopener">TikHub.io</a> 赞助支持</p>
<p>TikHub - 专业的社交媒体数据 API 服务平台</p>
<p>支持抖音、快手、小红书、微信公众号、微信视频号、TikTok、Instagram、YouTube 等多平台数据获取与分析</p>
</div>
</div>
<!-- 作者信息 -->
<div class="section">
<div class="author-info">
<div class="avatar">👨‍💻</div>
<div class="details">
<h4>作者Evil0ctal</h4>
<p>GitHub: <a href="https://github.com/Evil0ctal" target="_blank" rel="noopener">@Evil0ctal</a></p>
<p>项目地址: <a href="https://github.com/Evil0ctal/WeChat-Channels-Video-File-Decryption" target="_blank" rel="noopener">WeChat-Channels-Video-File-Decryption</a></p>
</div>
</div>
</div>
</main>
<footer class="footer">
<p>
开源项目 ·
<a href="https://github.com/Evil0ctal/WeChat-Channels-Video-File-Decryption" target="_blank" rel="noopener">GitHub</a> ·
仅供学习研究使用
</p>
<p style="margin-top: 8px; font-size: 12px;">
© 2025 Evil0ctal · MIT License
</p>
</footer>
</div>
<!-- WASM 模块配置 -->
<script>
window.VTS_WASM_URL = 'wechat_files/wasm_video_decode.wasm';
window.MAX_HEAP_SIZE = 33554432;
</script>
<script src="wechat_files/wasm_video_decode.js"></script>
<!-- 主程序 -->
<script>
// 全局变量
let keystreamData = null;
let encryptedVideoFile = null;
let decryptedVideoData = null;
const KEYSTREAM_SIZE = 131072; // 128 KB
/**
* 切换标签页
*/
function switchTab(tabName) {
// 更新标签按钮状态
document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('active'));
event.target.classList.add('active');
// 更新内容显示
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
document.getElementById(tabName + '-tab').classList.add('active');
}
/**
* 日志输出函数
*/
function log(message) {
const output = document.getElementById('output');
output.innerHTML += message + '\n';
output.scrollTop = output.scrollHeight;
}
/**
* 更新进度
*/
function updateProgress(percent, text) {
const container = document.getElementById('progressContainer');
const fill = document.getElementById('progressFill');
const textEl = document.getElementById('progressText');
container.style.display = 'block';
fill.style.width = percent + '%';
fill.textContent = Math.round(percent) + '%';
textEl.textContent = text;
}
/**
* 隐藏进度
*/
function hideProgress() {
document.getElementById('progressContainer').style.display = 'none';
}
/**
* 清空日志
*/
function clearOutput() {
document.getElementById('output').innerHTML = '';
keystreamData = null;
document.getElementById('exportBtn').disabled = true;
}
/**
* 清空所有
*/
function clearAll() {
clearOutput();
encryptedVideoFile = null;
decryptedVideoData = null;
document.getElementById('fileInfo').style.display = 'none';
document.getElementById('decryptBtn').disabled = true;
document.getElementById('downloadBtn').disabled = true;
hideProgress();
log('<span class="info">✅ 已重置所有状态</span>\n');
}
/**
* 文件拖放处理
*/
const dropArea = document.getElementById('dropArea');
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
['dragenter', 'dragover'].forEach(eventName => {
dropArea.addEventListener(eventName, () => {
dropArea.classList.add('drag-over');
}, false);
});
['dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, () => {
dropArea.classList.remove('drag-over');
}, false);
});
dropArea.addEventListener('drop', (e) => {
const files = e.dataTransfer.files;
if (files.length > 0) {
handleFile(files[0]);
}
}, false);
/**
* 文件选择处理
*/
function handleFileSelect(event) {
const file = event.target.files[0];
if (file) {
handleFile(file);
}
}
/**
* 处理文件
*/
function handleFile(file) {
encryptedVideoFile = file;
// 显示文件信息
const fileInfo = document.getElementById('fileInfo');
fileInfo.style.display = 'block';
fileInfo.innerHTML = `
<strong>✅ 已选择文件:</strong><br>
文件名: ${file.name}<br>
大小: ${(file.size / 1024 / 1024).toFixed(2)} MB<br>
类型: ${file.type || '未知'}
`;
// 启用解密按钮
const decodeKey = document.getElementById('decodeKeyFull').value.trim();
if (decodeKey) {
document.getElementById('decryptBtn').disabled = false;
}
log(`<span class="success">✅ 已加载文件: ${file.name} (${(file.size / 1024 / 1024).toFixed(2)} MB)</span>\n`);
}
/**
* 导出密钥流到文件
*/
function exportKeystream() {
if (!keystreamData) {
alert('⚠️ 请先生成密钥流!');
return;
}
// 转换为十六进制字符串
const hexString = Array.from(keystreamData)
.map(b => b.toString(16).padStart(2, '0'))
.join(' ');
// 创建下载
const blob = new Blob([hexString], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'keystream_131072_bytes.txt';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
log('\n<span class="success">✅ 密钥流已导出</span>');
log(` 文件名: keystream_131072_bytes.txt`);
log(` 大小: ${keystreamData.length.toLocaleString()} bytes`);
log(` 格式: 十六进制,空格分隔`);
}
/**
* 下载解密后的视频
*/
function downloadDecrypted() {
if (!decryptedVideoData) {
alert('⚠️ 请先解密视频!');
return;
}
const blob = new Blob([decryptedVideoData], { type: 'video/mp4' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'decrypted_video.mp4';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
log('\n<span class="success">✅ 解密视频已下载</span>');
log(` 文件名: decrypted_video.mp4`);
log(` 大小: ${(decryptedVideoData.length / 1024 / 1024).toFixed(2)} MB`);
}
/**
* WASM 回调函数 - 接收生成的密钥流
*/
window.wasm_isaac_generate = function(ptr, size) {
log('<span class="info">📝 接收 WASM 生成的密钥流</span>');
log(` 内存指针: ${ptr}`);
log(` 数据大小: ${size.toLocaleString()} bytes`);
// 创建数组保存密钥流
keystreamData = new Uint8Array(size);
// 从 WASM 内存读取
const wasmArray = new Uint8Array(Module.HEAPU8.buffer, ptr, size);
// 关键步骤:反转数组(微信加密算法要求)
keystreamData.set(wasmArray.slice().reverse());
log('<span class="info">🔄 已应用 reverse() 操作</span>');
log(` 前 16 字节: ${Array.from(keystreamData.slice(0, 16))
.map(b => b.toString(16).padStart(2, '0')).join(' ')}`);
};
/**
* 生成密钥流(仅密钥流模式)
*/
async function generateKeystream() {
clearOutput();
const decodeKey = document.getElementById('decodeKey').value.trim();
if (!decodeKey) {
alert('⚠️ 请输入 decode_key');
return;
}
log('═══════════════════════════════════════════════════════════');
log(`🔑 Decode Key: ${decodeKey}`);
log('═══════════════════════════════════════════════════════════\n');
try {
await generateKeystreamInternal(decodeKey);
if (keystreamData) {
log('═══════════════════════════════════════════════════════════');
log('<span class="success">🎉 密钥流生成成功!</span>');
log('═══════════════════════════════════════════════════════════\n');
log('<span class="info">📊 密钥流信息:</span>');
log(` 大小: ${keystreamData.length.toLocaleString()} bytes (${(keystreamData.length / 1024).toFixed(2)} KB)`);
log(` 前 64 字节:`);
for (let i = 0; i < 64; i += 16) {
const chunk = Array.from(keystreamData.slice(i, i + 16))
.map(b => b.toString(16).padStart(2, '0')).join(' ');
log(` [${i.toString().padStart(5, '0')}]: ${chunk}`);
}
log('\n<span class="success">✅ 请点击"导出密钥流"按钮保存文件</span>');
// 启用导出按钮
document.getElementById('exportBtn').disabled = false;
}
} catch (error) {
log(`\n<span class="error">❌ 错误: ${error.message}</span>`);
console.error('详细错误:', error);
}
}
/**
* 内部密钥流生成函数
*/
async function generateKeystreamInternal(decodeKey) {
// 检查 WASM 模块
if (typeof Module === 'undefined' || !Module.WxIsaac64) {
log('<span class="warning">⏳ WASM 模块未就绪,等待加载...</span>');
// 等待最多 10 秒
let loaded = false;
for (let i = 0; i < 100; i++) {
await new Promise(resolve => setTimeout(resolve, 100));
if (typeof Module !== 'undefined' && Module.WxIsaac64) {
loaded = true;
log('<span class="success">✅ WASM 模块已加载</span>\n');
break;
}
}
if (!loaded) {
throw new Error('WASM 模块加载超时');
}
}
// 第一步:创建 Isaac64 实例
log('<span class="info">步骤 1/3: 创建 Isaac64 实例</span>');
const decryptor = new Module.WxIsaac64(decodeKey);
log('<span class="success">✅ 实例创建成功</span>\n');
// 第二步:生成密钥流
log(`<span class="info">步骤 2/3: 生成 ${KEYSTREAM_SIZE.toLocaleString()} bytes 密钥流</span>`);
await decryptor.generate(KEYSTREAM_SIZE);
log('<span class="success">✅ 密钥流生成完成</span>\n');
// 第三步:清理资源
log('<span class="info">步骤 3/3: 清理资源</span>');
await decryptor.delete();
log('<span class="success">✅ 完成</span>\n');
// 验证结果
if (!keystreamData) {
throw new Error('密钥流数据为空');
}
}
/**
* 格式化字节数组为十六进制显示带ASCII
*/
function formatHexDump(data, offset, length, bytesPerLine = 16) {
let result = '';
for (let i = 0; i < length; i += bytesPerLine) {
const lineOffset = offset + i;
const lineData = data.slice(i, Math.min(i + bytesPerLine, length));
// 偏移量
const offsetStr = lineOffset.toString(16).padStart(8, '0');
// 十六进制数据
const hexStr = Array.from(lineData)
.map(b => b.toString(16).padStart(2, '0'))
.join(' ')
.padEnd(bytesPerLine * 3 - 1, ' ');
// ASCII 表示
const asciiStr = Array.from(lineData)
.map(b => (b >= 32 && b <= 126) ? String.fromCharCode(b) : '.')
.join('');
result += `${offsetStr} ${hexStr} |${asciiStr}|\n`;
}
return result;
}
/**
* 分析 MP4 文件头
*/
function analyzeMp4Header(data) {
const analysis = [];
// Box size (前4字节)
const boxSize = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
analysis.push(` 📦 Box Size: ${boxSize} bytes (0x${boxSize.toString(16)})`);
// Box type (4-7字节)
const boxType = String.fromCharCode(data[4], data[5], data[6], data[7]);
analysis.push(` 🏷️ Box Type: '${boxType}' (${Array.from(data.slice(4, 8)).map(b => '0x' + b.toString(16).padStart(2, '0')).join(' ')})`);
// Major brand (8-11字节)
const majorBrand = String.fromCharCode(data[8], data[9], data[10], data[11]);
analysis.push(` 🎬 Major Brand: '${majorBrand}'`);
// Minor version (12-15字节)
const minorVersion = (data[12] << 24) | (data[13] << 16) | (data[14] << 8) | data[15];
analysis.push(` 📌 Minor Version: ${minorVersion} (0x${minorVersion.toString(16).padStart(8, '0')})`);
// Compatible brands
const compatBrands = [];
for (let i = 16; i < 32 && i < data.length; i += 4) {
const brand = String.fromCharCode(data[i], data[i+1], data[i+2], data[i+3]);
if (brand.trim()) compatBrands.push(brand);
}
if (compatBrands.length > 0) {
analysis.push(` 🔗 Compatible Brands: ${compatBrands.join(', ')}`);
}
return analysis.join('\n');
}
/**
* 完整解密视频
*/
async function decryptVideo() {
if (!encryptedVideoFile) {
alert('⚠️ 请先选择加密视频文件!');
return;
}
const decodeKey = document.getElementById('decodeKeyFull').value.trim();
if (!decodeKey) {
alert('⚠️ 请输入 decode_key');
return;
}
clearOutput();
document.getElementById('decryptBtn').disabled = true;
try {
log('╔═══════════════════════════════════════════════════════════╗');
log('║ 微信视频号解密工具 - 完整解密流程 ║');
log('╚═══════════════════════════════════════════════════════════╝\n');
log('<span class="info">📋 解密配置信息:</span>');
log(` 🔑 Decode Key: ${decodeKey}`);
log(` 📹 输入文件: ${encryptedVideoFile.name}`);
log(` 📊 文件大小: ${(encryptedVideoFile.size / 1024 / 1024).toFixed(2)} MB (${encryptedVideoFile.size.toLocaleString()} bytes)`);
log(` 🔒 加密范围: 前 131,072 bytes (128 KB)`);
log(` 🔐 解密算法: Isaac64 PRNG + XOR\n`);
// ========== 步骤 1: 生成密钥流 ==========
log('┌─────────────────────────────────────────────────────────┐');
log('│ 步骤 1/4: 生成 Isaac64 密钥流 │');
log('└─────────────────────────────────────────────────────────┘');
updateProgress(10, '正在生成密钥流...');
await generateKeystreamInternal(decodeKey);
log('\n<span class="success">✅ 密钥流生成成功</span>');
log(` 📊 密钥流大小: ${keystreamData.length.toLocaleString()} bytes`);
log(` 🔄 已应用 reverse() 操作`);
log(` 📝 密钥流前 32 字节:\n`);
log(formatHexDump(keystreamData, 0, 32));
updateProgress(30, '密钥流生成完成');
// ========== 步骤 2: 读取并分析加密文件 ==========
log('┌─────────────────────────────────────────────────────────┐');
log('│ 步骤 2/4: 读取并分析加密视频文件 │');
log('└─────────────────────────────────────────────────────────┘');
updateProgress(40, '正在读取加密文件...');
const arrayBuffer = await encryptedVideoFile.arrayBuffer();
const encrypted = new Uint8Array(arrayBuffer);
log(`\n<span class="info">📂 加密文件信息:</span>`);
log(` 文件名: ${encryptedVideoFile.name}`);
log(` 文件大小: ${encrypted.length.toLocaleString()} bytes`);
log(` 文件类型: ${encryptedVideoFile.type || '未知'}\n`);
log('<span class="warning">🔒 加密文件头(前 64 字节):</span>\n');
log(formatHexDump(encrypted, 0, 64));
log('<span class="info">💡 说明: 可以看到文件头已被加密,无法识别为 MP4 格式</span>\n');
updateProgress(50, '文件读取完成');
// ========== 步骤 3: XOR 解密 ==========
log('┌─────────────────────────────────────────────────────────┐');
log('│ 步骤 3/4: 执行 XOR 解密运算 │');
log('└─────────────────────────────────────────────────────────┘');
updateProgress(55, '正在执行 XOR 解密...');
const decryptLen = Math.min(KEYSTREAM_SIZE, encrypted.length);
log(`\n<span class="info">🔐 解密参数:</span>`);
log(` 加密范围: 前 ${decryptLen.toLocaleString()} bytes (${(decryptLen / 1024).toFixed(2)} KB)`);
log(` 未加密范围: ${(encrypted.length - decryptLen).toLocaleString()} bytes (${((encrypted.length - decryptLen) / 1024 / 1024).toFixed(2)} MB)`);
log(` 解密算法: decrypted[i] = encrypted[i] XOR keystream[i]\n`);
// 创建解密后的数组
decryptedVideoData = new Uint8Array(encrypted.length);
// 显示 XOR 运算示例
log('<span class="info">📐 XOR 运算示例(前 8 字节):</span>');
for (let i = 0; i < 8; i++) {
const enc = encrypted[i];
const key = keystreamData[i];
const dec = enc ^ key;
log(` [${i}] 0x${enc.toString(16).padStart(2, '0')} XOR 0x${key.toString(16).padStart(2, '0')} = 0x${dec.toString(16).padStart(2, '0')} ('${String.fromCharCode(dec)}')`);
}
log('');
// XOR 前 128KB
const startTime = performance.now();
for (let i = 0; i < decryptLen; i++) {
decryptedVideoData[i] = encrypted[i] ^ keystreamData[i];
// 更新进度
if (i % 10000 === 0) {
const percent = 55 + (i / decryptLen) * 30;
updateProgress(percent, `XOR 解密: ${((i / decryptLen) * 100).toFixed(1)}%`);
}
}
const endTime = performance.now();
// 复制剩余未加密部分
decryptedVideoData.set(encrypted.slice(decryptLen), decryptLen);
log(`<span class="success">✅ XOR 解密完成</span>`);
log(` ⏱️ 耗时: ${(endTime - startTime).toFixed(2)} ms`);
log(` 📊 处理速度: ${(decryptLen / (endTime - startTime) * 1000 / 1024 / 1024).toFixed(2)} MB/s\n`);
updateProgress(85, '解密运算完成');
// ========== 步骤 4: 验证解密结果 ==========
log('┌─────────────────────────────────────────────────────────┐');
log('│ 步骤 4/4: 验证解密结果 │');
log('└─────────────────────────────────────────────────────────┘');
updateProgress(90, '验证解密结果...');
log('\n<span class="success">🔓 解密后文件头(前 64 字节):</span>\n');
log(formatHexDump(decryptedVideoData, 0, 64));
// 分析 MP4 文件头
log('<span class="info">📋 MP4 文件头分析:</span>');
log(analyzeMp4Header(decryptedVideoData));
log('');
// 检查 MP4 'ftyp' 签名 (偏移 4-7)
const ftypBytes = decryptedVideoData.slice(4, 8);
const ftypString = String.fromCharCode(...ftypBytes);
log('<span class="info">🔍 MP4 格式验证:</span>');
if (ftypString === 'ftyp') {
log(` <span class="success">✅ 'ftyp' 签名验证通过 @ 偏移 4</span>`);
log(` <span class="success">✅ 文件格式: MP4 (ISO Base Media)</span>`);
log(` <span class="success">✅ 解密成功!文件可以正常播放</span>`);
} else {
log(` <span class="error">❌ 未找到 'ftyp' 签名</span>`);
log(` <span class="warning">检测到: '${ftypString}' (hex: ${Array.from(ftypBytes).map(b => b.toString(16).padStart(2, '0')).join(' ')})</span>`);
log(` <span class="warning">⚠️ 解密可能失败,请检查 decode_key 是否正确</span>`);
}
// 对比加密前后
log('\n<span class="info">📊 加密前后对比:</span>');
log(` 加密文件前16字节: ${Array.from(encrypted.slice(0, 16)).map(b => b.toString(16).padStart(2, '0')).join(' ')}`);
log(` 解密文件前16字节: ${Array.from(decryptedVideoData.slice(0, 16)).map(b => b.toString(16).padStart(2, '0')).join(' ')}`);
log(` 密钥流前16字节: ${Array.from(keystreamData.slice(0, 16)).map(b => b.toString(16).padStart(2, '0')).join(' ')}\n`);
updateProgress(100, '解密完成!');
log('╔═══════════════════════════════════════════════════════════╗');
log('║ 🎉 解密完成! ║');
log('╚═══════════════════════════════════════════════════════════╝\n');
log('<span class="success">📊 解密统计:</span>');
log(` 📁 原始文件: ${encrypted.length.toLocaleString()} bytes`);
log(` 🔓 解密范围: ${decryptLen.toLocaleString()} bytes (${(decryptLen / encrypted.length * 100).toFixed(2)}%)`);
log(` ⏱️ 总耗时: ${(endTime - startTime).toFixed(2)} ms`);
log(` 💾 输出文件: decrypted_video.mp4\n`);
log('<span class="success">✅ 请点击"下载解密视频"按钮保存文件</span>');
log('<span class="info">💡 解密后的视频保存在浏览器内存中,未上传到任何服务器</span>\n');
// 启用下载按钮
document.getElementById('downloadBtn').disabled = false;
// 2秒后隐藏进度条
setTimeout(hideProgress, 2000);
} catch (error) {
log(`\n<span class="error">╔═══════════════════════════════════════════════════════════╗</span>`);
log(`<span class="error">║ ❌ 解密失败 ║</span>`);
log(`<span class="error">╚═══════════════════════════════════════════════════════════╝</span>\n`);
log(`<span class="error">错误信息: ${error.message}</span>`);
log(`<span class="error">错误堆栈: ${error.stack}</span>\n`);
log(`<span class="warning">💡 可能的原因:</span>`);
log(` 1. decode_key 不正确`);
log(` 2. 文件不是微信视频号加密文件`);
log(` 3. 文件已损坏`);
log(` 4. WASM 模块未正确加载\n`);
console.error('详细错误:', error);
hideProgress();
alert('解密失败: ' + error.message);
} finally {
document.getElementById('decryptBtn').disabled = false;
}
}
// 页面加载完成后初始化
window.addEventListener('DOMContentLoaded', function() {
log('<span class="info">📦 正在加载 WASM 模块...</span>');
// 检查 WASM 加载状态
const checkInterval = setInterval(function() {
if (typeof Module !== 'undefined' && Module.WxIsaac64) {
clearInterval(checkInterval);
log('<span class="success">✅ WASM 模块加载成功</span>');
log('<span class="info">👉 选择"一键解密"标签页开始使用</span>\n');
}
}, 100);
// 30 秒超时
setTimeout(function() {
clearInterval(checkInterval);
if (typeof Module === 'undefined' || !Module.WxIsaac64) {
log('<span class="error">❌ WASM 模块加载超时</span>');
log(' 请刷新页面重试,或检查浏览器控制台查看错误');
}
}, 30000);
// 监听 decode key 输入
document.getElementById('decodeKeyFull').addEventListener('input', function() {
const decodeKey = this.value.trim();
if (decodeKey && encryptedVideoFile) {
document.getElementById('decryptBtn').disabled = false;
} else {
document.getElementById('decryptBtn').disabled = true;
}
});
});
</script>
</body>
</html>