liangzhidanta's picture
Add 2 files
183fa36 verified
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>隐写与解密工具</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.tab-btn.active {
background-color: #3b82f6;
color: white;
}
#imageCanvas {
max-width: 100%;
height: auto;
border: 1px solid #ddd;
border-radius: 0.375rem;
}
.drop-area {
border: 2px dashed #ccc;
border-radius: 0.5rem;
padding: 2rem;
text-align: center;
transition: all 0.3s;
}
.drop-area.highlight {
border-color: #3b82f6;
background-color: #f0f7ff;
}
.hidden {
display: none;
}
.result-box {
min-height: 150px;
border: 1px solid #ddd;
border-radius: 0.375rem;
padding: 1rem;
background-color: #f8fafc;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="container mx-auto px-4 py-8 max-w-4xl">
<header class="text-center mb-8">
<h1 class="text-3xl font-bold text-blue-600 mb-2">隐写与解密工具</h1>
<p class="text-gray-600">安全地在图片或文本中隐藏秘密信息</p>
</header>
<div class="bg-white rounded-lg shadow-md overflow-hidden mb-8">
<div class="flex border-b">
<button class="tab-btn active px-6 py-3 font-medium text-sm flex-1" data-tab="image-stego">图片隐写</button>
<button class="tab-btn px-6 py-3 font-medium text-sm flex-1" data-tab="text-stego">文本隐写</button>
</div>
<!-- 图片隐写部分 -->
<div id="image-stego" class="tab-content active p-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h2 class="text-xl font-semibold mb-4 text-blue-600">
<i class="fas fa-lock mr-2"></i>隐藏信息到图片
</h2>
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-2">选择图片</label>
<div id="imageDropArea" class="drop-area cursor-pointer">
<i class="fas fa-cloud-upload-alt text-4xl text-blue-400 mb-2"></i>
<p class="text-gray-500">拖放图片到此处或点击选择</p>
<input type="file" id="imageInput" accept="image/*" class="hidden">
</div>
</div>
<div class="mb-4">
<label for="secretMessage" class="block text-sm font-medium text-gray-700 mb-2">要隐藏的信息</label>
<textarea id="secretMessage" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500" placeholder="输入要隐藏的秘密信息..."></textarea>
</div>
<div class="mb-4">
<label for="encryptionKey" class="block text-sm font-medium text-gray-700 mb-2">加密密钥 (可选)</label>
<input type="text" id="encryptionKey" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500" placeholder="输入加密密钥...">
</div>
<button id="hideInImageBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-md transition duration-150 ease-in-out">
<i class="fas fa-key mr-2"></i>隐藏信息
</button>
</div>
<div>
<h2 class="text-xl font-semibold mb-4 text-blue-600">
<i class="fas fa-lock-open mr-2"></i>从图片提取信息
</h2>
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-2">选择包含隐藏信息的图片</label>
<div id="stegoImageDropArea" class="drop-area cursor-pointer">
<i class="fas fa-cloud-upload-alt text-4xl text-blue-400 mb-2"></i>
<p class="text-gray-500">拖放图片到此处或点击选择</p>
<input type="file" id="stegoImageInput" accept="image/*" class="hidden">
</div>
</div>
<div class="mb-4">
<label for="decryptionKey" class="block text-sm font-medium text-gray-700 mb-2">解密密钥 (如果加密过)</label>
<input type="text" id="decryptionKey" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500" placeholder="输入解密密钥...">
</div>
<button id="extractFromImageBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-md transition duration-150 ease-in-out">
<i class="fas fa-search mr-2"></i>提取信息
</button>
<div class="mt-4">
<label class="block text-sm font-medium text-gray-700 mb-2">提取结果</label>
<div id="extractedMessage" class="result-box"></div>
</div>
</div>
</div>
<div class="mt-6">
<canvas id="imageCanvas" class="mx-auto"></canvas>
</div>
</div>
<!-- 文本隐写部分 -->
<div id="text-stego" class="tab-content p-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h2 class="text-xl font-semibold mb-4 text-blue-600">
<i class="fas fa-lock mr-2"></i>隐藏信息到文本
</h2>
<div class="mb-4">
<label for="coverText" class="block text-sm font-medium text-gray-700 mb-2">载体文本</label>
<textarea id="coverText" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500" placeholder="输入公开的文本内容..."></textarea>
</div>
<div class="mb-4">
<label for="secretText" class="block text-sm font-medium text-gray-700 mb-2">要隐藏的信息</label>
<textarea id="secretText" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500" placeholder="输入要隐藏的秘密信息..."></textarea>
</div>
<div class="mb-4">
<label for="textEncryptionKey" class="block text-sm font-medium text-gray-700 mb-2">加密密钥 (可选)</label>
<input type="text" id="textEncryptionKey" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500" placeholder="输入加密密钥...">
</div>
<button id="hideInTextBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-md transition duration-150 ease-in-out">
<i class="fas fa-key mr-2"></i>隐藏信息
</button>
<div class="mt-4">
<label class="block text-sm font-medium text-gray-700 mb-2">结果文本</label>
<div id="stegoTextResult" class="result-box"></div>
<button id="copyTextBtn" class="mt-2 bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium py-2 px-4 rounded-md transition duration-150 ease-in-out hidden">
<i class="fas fa-copy mr-2"></i>复制文本
</button>
</div>
</div>
<div>
<h2 class="text-xl font-semibold mb-4 text-blue-600">
<i class="fas fa-lock-open mr-2"></i>从文本提取信息
</h2>
<div class="mb-4">
<label for="stegoTextInput" class="block text-sm font-medium text-gray-700 mb-2">包含隐藏信息的文本</label>
<textarea id="stegoTextInput" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500" placeholder="输入包含隐藏信息的文本..."></textarea>
</div>
<div class="mb-4">
<label for="textDecryptionKey" class="block text-sm font-medium text-gray-700 mb-2">解密密钥 (如果加密过)</label>
<input type="text" id="textDecryptionKey" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500" placeholder="输入解密密钥...">
</div>
<button id="extractFromTextBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-md transition duration-150 ease-in-out">
<i class="fas fa-search mr-2"></i>提取信息
</button>
<div class="mt-4">
<label class="block text-sm font-medium text-gray-700 mb-2">提取结果</label>
<div id="extractedTextMessage" class="result-box"></div>
</div>
</div>
</div>
</div>
</div>
<div class="bg-white rounded-lg shadow-md p-6 mb-8">
<h2 class="text-xl font-semibold mb-4 text-blue-600">
<i class="fas fa-info-circle mr-2"></i>关于隐写术
</h2>
<div class="text-gray-700 space-y-4">
<p>隐写术是一种将秘密信息隐藏在看似普通的载体(如图片、文本、音频或视频)中的技术,与加密不同,隐写术的重点是隐藏信息的存在。</p>
<div class="bg-blue-50 border-l-4 border-blue-500 p-4">
<h3 class="font-semibold text-blue-800 mb-2">图片隐写原理</h3>
<p>本工具使用LSB(最低有效位)算法,通过修改图片像素的最低有效位来隐藏信息。人眼几乎无法察觉这些微小变化,但计算机可以提取这些隐藏的位来重建原始信息。</p>
</div>
<div class="bg-green-50 border-l-4 border-green-500 p-4">
<h3 class="font-semibold text-green-800 mb-2">文本隐写原理</h3>
<p>文本隐写使用Unicode零宽度字符和特殊空格字符来隐藏信息。这些字符在大多数文本编辑器和浏览器中不可见,但可以被程序检测和提取。</p>
</div>
<p class="text-sm text-gray-500">注意:隐写术不能替代加密,敏感信息应先加密再隐藏。本工具仅供学习和合法用途。</p>
</div>
</div>
</div>
<script>
// 切换标签页
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
btn.classList.add('active');
document.getElementById(btn.dataset.tab).classList.add('active');
});
});
// 图片隐写功能
const imageInput = document.getElementById('imageInput');
const imageDropArea = document.getElementById('imageDropArea');
const stegoImageInput = document.getElementById('stegoImageInput');
const stegoImageDropArea = document.getElementById('stegoImageDropArea');
const canvas = document.getElementById('imageCanvas');
const ctx = canvas.getContext('2d');
let originalImageData = null;
// 处理拖放事件
function setupDropArea(dropArea, input) {
['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, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, unhighlight, false);
});
function highlight() {
dropArea.classList.add('highlight');
}
function unhighlight() {
dropArea.classList.remove('highlight');
}
dropArea.addEventListener('drop', handleDrop, false);
dropArea.addEventListener('click', () => input.click());
function handleDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
input.files = files;
handleImageUpload(files[0], input === imageInput);
}
}
setupDropArea(imageDropArea, imageInput);
setupDropArea(stegoImageDropArea, stegoImageInput);
imageInput.addEventListener('change', (e) => {
if (e.target.files.length) {
handleImageUpload(e.target.files[0], true);
}
});
stegoImageInput.addEventListener('change', (e) => {
if (e.target.files.length) {
handleImageUpload(e.target.files[0], false);
}
});
function handleImageUpload(file, isOriginal) {
const reader = new FileReader();
reader.onload = function(event) {
const img = new Image();
img.onload = function() {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
if (isOriginal) {
originalImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
}
};
img.src = event.target.result;
};
reader.readAsDataURL(file);
}
// 图片隐写 - 隐藏信息
document.getElementById('hideInImageBtn').addEventListener('click', () => {
if (!originalImageData) {
alert('请先选择一张图片');
return;
}
const secretMessage = document.getElementById('secretMessage').value;
if (!secretMessage) {
alert('请输入要隐藏的信息');
return;
}
const encryptionKey = document.getElementById('encryptionKey').value;
let messageToHide = secretMessage;
if (encryptionKey) {
messageToHide = simpleEncrypt(secretMessage, encryptionKey);
}
const stegoImageData = hideMessageInImage(originalImageData, messageToHide);
ctx.putImageData(stegoImageData, 0, 0);
// 显示下载按钮
const downloadBtn = document.createElement('a');
downloadBtn.href = canvas.toDataURL('image/png');
downloadBtn.download = 'stego-image.png';
downloadBtn.className = 'mt-4 inline-block bg-green-600 hover:bg-green-700 text-white font-medium py-2 px-4 rounded-md transition duration-150 ease-in-out';
downloadBtn.innerHTML = '<i class="fas fa-download mr-2"></i>下载隐藏信息的图片';
const existingDownloadBtn = document.querySelector('.download-stego-btn');
if (existingDownloadBtn) {
existingDownloadBtn.replaceWith(downloadBtn);
} else {
downloadBtn.classList.add('download-stego-btn');
canvas.parentNode.appendChild(downloadBtn);
}
});
// 图片隐写 - 提取信息
document.getElementById('extractFromImageBtn').addEventListener('click', () => {
if (!canvas.width || !canvas.height) {
alert('请先选择一张包含隐藏信息的图片');
return;
}
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const extractedMessage = extractMessageFromImage(imageData);
const decryptionKey = document.getElementById('decryptionKey').value;
let finalMessage = extractedMessage;
if (decryptionKey && extractedMessage) {
finalMessage = simpleDecrypt(extractedMessage, decryptionKey);
}
const resultDiv = document.getElementById('extractedMessage');
if (finalMessage) {
resultDiv.innerHTML = `<p class="text-green-600 font-medium">成功提取隐藏信息:</p><p class="mt-2">${finalMessage}</p>`;
} else {
resultDiv.innerHTML = '<p class="text-red-600">未检测到隐藏信息或密钥错误</p>';
}
});
// LSB隐写算法
function hideMessageInImage(imageData, message) {
const data = imageData.data;
const messageBits = stringToBits(message + '\0'); // 添加null终止符
if (messageBits.length > data.length * 4) {
alert('图片太小无法隐藏这么多信息');
return imageData;
}
for (let i = 0; i < messageBits.length; i++) {
const bytePos = Math.floor(i / 8);
const bitPos = i % 8;
const mask = 1 << bitPos;
const bit = (message.charCodeAt(bytePos) & mask) ? 1 : 0;
const pixelPos = Math.floor(i / 3);
const channel = i % 3; // 0=R, 1=G, 2=B (跳过Alpha通道)
// 修改最低有效位
if (bit) {
data[pixelPos * 4 + channel] |= 1;
} else {
data[pixelPos * 4 + channel] &= ~1;
}
}
return imageData;
}
function extractMessageFromImage(imageData) {
const data = imageData.data;
let message = '';
let currentByte = 0;
let bitCount = 0;
for (let i = 0; i < data.length; i += 4) {
// 检查RGB三个通道
for (let channel = 0; channel < 3; channel++) {
const bit = data[i + channel] & 1;
currentByte = (currentByte << 1) | bit;
bitCount++;
if (bitCount === 8) {
if (currentByte === 0) { // 遇到null终止符
return message;
}
message += String.fromCharCode(currentByte);
currentByte = 0;
bitCount = 0;
}
}
}
return message || null;
}
function stringToBits(str) {
let bits = [];
for (let i = 0; i < str.length; i++) {
const charCode = str.charCodeAt(i);
for (let j = 0; j < 8; j++) {
bits.push((charCode >> (7 - j)) & 1);
}
}
return bits;
}
// 文本隐写功能
document.getElementById('hideInTextBtn').addEventListener('click', () => {
const coverText = document.getElementById('coverText').value;
const secretText = document.getElementById('secretText').value;
if (!coverText) {
alert('请输入载体文本');
return;
}
if (!secretText) {
alert('请输入要隐藏的信息');
return;
}
const encryptionKey = document.getElementById('textEncryptionKey').value;
let messageToHide = secretText;
if (encryptionKey) {
messageToHide = simpleEncrypt(secretText, encryptionKey);
}
const stegoText = hideMessageInText(coverText, messageToHide);
const resultDiv = document.getElementById('stegoTextResult');
resultDiv.textContent = stegoText;
const copyBtn = document.getElementById('copyTextBtn');
copyBtn.classList.remove('hidden');
copyBtn.onclick = () => {
navigator.clipboard.writeText(stegoText).then(() => {
const originalText = copyBtn.innerHTML;
copyBtn.innerHTML = '<i class="fas fa-check mr-2"></i>已复制';
setTimeout(() => {
copyBtn.innerHTML = originalText;
}, 2000);
});
};
});
document.getElementById('extractFromTextBtn').addEventListener('click', () => {
const stegoText = document.getElementById('stegoTextInput').value;
if (!stegoText) {
alert('请输入包含隐藏信息的文本');
return;
}
const extractedMessage = extractMessageFromText(stegoText);
const decryptionKey = document.getElementById('textDecryptionKey').value;
let finalMessage = extractedMessage;
if (decryptionKey && extractedMessage) {
finalMessage = simpleDecrypt(extractedMessage, decryptionKey);
}
const resultDiv = document.getElementById('extractedTextMessage');
if (finalMessage) {
resultDiv.innerHTML = `<p class="text-green-600 font-medium">成功提取隐藏信息:</p><p class="mt-2">${finalMessage}</p>`;
} else {
resultDiv.innerHTML = '<p class="text-red-600">未检测到隐藏信息或密钥错误</p>';
}
});
// 文本隐写算法 - 使用零宽度字符
function hideMessageInText(coverText, secretText) {
// 将秘密文本转换为二进制
let binarySecret = '';
for (let i = 0; i < secretText.length; i++) {
binarySecret += secretText.charCodeAt(i).toString(2).padStart(8, '0');
}
// 使用零宽度字符表示二进制位
const zeroWidthSpace = '\u200B'; // 表示0
const zeroWidthJoiner = '\u200D'; // 表示1
const zeroWidthNonJoiner = '\u200C'; // 用作分隔符
let hiddenChars = '';
for (let bit of binarySecret) {
hiddenChars += bit === '0' ? zeroWidthSpace : zeroWidthJoiner;
}
// 将隐藏字符插入到载体文本中
return coverText + zeroWidthNonJoiner + hiddenChars;
}
function extractMessageFromText(stegoText) {
const zeroWidthSpace = '\u200B';
const zeroWidthJoiner = '\u200D';
const zeroWidthNonJoiner = '\u200C';
// 查找分隔符
const separatorPos = stegoText.indexOf(zeroWidthNonJoiner);
if (separatorPos === -1) return null;
const hiddenPart = stegoText.slice(separatorPos + 1);
let binaryString = '';
for (let char of hiddenPart) {
if (char === zeroWidthSpace) {
binaryString += '0';
} else if (char === zeroWidthJoiner) {
binaryString += '1';
} else {
// 遇到非零宽度字符,停止解析
break;
}
}
// 将二进制字符串转换回文本
let result = '';
for (let i = 0; i < binaryString.length; i += 8) {
const byte = binaryString.slice(i, i + 8);
if (byte.length < 8) break;
result += String.fromCharCode(parseInt(byte, 2));
}
return result || null;
}
// 简单的加密/解密函数 (仅用于演示)
function simpleEncrypt(text, key) {
let result = '';
for (let i = 0; i < text.length; i++) {
const charCode = text.charCodeAt(i) ^ key.charCodeAt(i % key.length);
result += String.fromCharCode(charCode);
}
return result;
}
function simpleDecrypt(text, key) {
return simpleEncrypt(text, key); // XOR加密是可逆的
}
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=liangzhidanta/simple-steganography-tool" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>